Skip to main content

Entity assessments

An entity assessment describes the current quality, readiness, risk, or health of an entity. Assessments live beside the entity rather than inside its JSON, so evaluating a product's sellability or an order's fraud risk does not change the entity version, history, search document, or ordinary entity events.

Each entity can have several named assessments. Assessment names are unique within an entity type, so Readiness can be used independently for products, pages, and orders. A result can apply to the whole entity or to a combination of registered Vox contexts such as a market and language.

Evaluated assessments​

Implement IVoxEntityAssessment<TEntity> when Vox should calculate the result. Evaluators are discovered at startup and use the same dependency-aware reads as calculations. Vox reevaluates only the consumers affected by an entity, relation, context-membership, or custom dependency change.

[VoxAssessment(Name = "Sellability", Revision = "1", ContextTypes = [typeof(Market)])]
public class ProductSellability : IVoxEntityAssessment<Product>
{
public async Task AssessAsync(
Product product,
IVoxAssessmentEvaluationContext context,
CancellationToken cancellationToken)
{
var prices = await context.LoadRelatedAsync<Price>(product, cancellationToken);

foreach (var market in product.Markets)
{
var result = context.ForContext(VoxAssessmentContext.For<Market>(market.Id));
var marketPrices = prices.Where(price => price.Market.Id == market.Id).ToList();

if (marketPrices.Count == 0)
{
result.SetOutcome(VoxAssessmentOutcome.Fail)
.AddFinding("missing-price", VoxAssessmentFindingSeverity.Error,
"The product has no price in this market.", path: "prices");
}
else
{
result.SetOutcome(VoxAssessmentOutcome.Pass)
.AddMetric("price-count", marketPrices.Count, "prices");
}
}
}
}

Use ForEntity() for an entity-wide result. A run may produce zero or more contextual results; after a successful run, that complete set replaces the evaluator's previous set. Producing no results means “not applicable.”

IVoxAssessmentEvaluationContext also exposes RecalculateAt and the calculation dependency API. Use RecalculateAt for evaluator-owned time boundaries. ValidUntil(...) belongs to a particular result and controls when that stored result becomes stale.

Reported assessments​

Use a reported assessment when another system owns the answer, such as a fraud provider. Register its contract explicitly so typos cannot create assessment types at runtime:

services.AddVox(/* configuration */)
.AddReportedAssessment<Order>("FraudRisk", revision: "1");

Report through IVoxAssessmentWriter or the assessment HTTP endpoints. ReportAsync updates one context without removing the others; ReplaceAllAsync atomically replaces the complete context set. SourceResultId makes retries idempotent, while SourceVersion and SourceTimestampUtc reject provably older reports.

Reported results require the {EntityType}:Assess:{AssessmentName} permission over HTTP. Reporting never creates an assessment definition and never mutates the assessed entity.

Results, findings, and metrics​

Evaluation state and outcome are separate:

  • State is Pending, Current, Stale, or Error.
  • Outcome is Pass, Warning, or Fail.

This distinction lets Vox retain the last successful outcome and diagnostics while a later attempt is pending or has errored. An evaluator exception is operational state, not a failed business assessment.

A finding has a stable Code, severity, message, and optional JSON path, key, related entity, and JSON details. Its occurrence identity is (Code, Path, Key), which lets changed events distinguish added, changed, and resolved findings. Metrics are named decimal values with an optional unit and can be filtered or sorted in reports.

ValidUntilUtc schedules a durable delayed Nexus message when the result is stored. The message contains the result revision, so a refresh supersedes the old expiry safely. On expiry, the matching result becomes stale and emits a change; evaluated assessments are also queued for reevaluation. Vox does not periodically scan all assessment rows.

Querying and events​

The assessment API exposes registered types, entity-scoped reads, cross-entity queries, reporting, and manual reevaluation. VoxAssessmentQuery filters by entity, assessment, content scope, assessment context, state, outcome, findings, metrics, and timestamps. Read authorization is applied in the database before counting and paging.

The built-in Admin UI does not render assessment results yet. The API and metadata are deliberately sufficient for a custom PIM, CMS, commerce console, or future generic Admin UI report to present them without reading private storage tables or changing entity schemas.

Assessment changes are delivered independently from ordinary entity events. Implement IVoxAssessmentChangedHandler to react to state/outcome changes, finding differences, stale transitions, or removed contexts. Delivery is durable and at least once, so handlers should be idempotent. This is the integration point for health monitoring or a Slack/Teams bridge; configurable entity subscriptions and policy gates are intentionally separate concerns.

Assessment storage is current-state sidecar storage, not an audit log. Queryable assessment history can be added as a separate capability if a compliance use case requires it.

Content scopes​

Published, draft, and release assessments are stored independently. Both is only a read composition and is never a stored scope. This lets CMS readiness follow the exact content perspective being previewed without contaminating the published result.