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, orError. - Outcome is
Pass,Warning, orFail.
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.