Authoring a product profile#
A product profile is an ordinary SurfaceExtension plus the claims, policies,
and evidence receipts its product already owns. It is not a Surface runtime,
derivation hook, schema field, or new package. Start in the product repository;
extract a package only after a second independent consumer proves that it needs
the same stable vocabulary and policy templates.
examples/answer-provenance.json is the reference bundle. Its
answer-provenance facet has three product-owned claim types:
product.answer.llm-answer— a concrete answer-content assertion that may carry semantic support.product.answer.routing-receipt— request-routing context.product.answer.tool-use— tool invocation context.
Routing and tool claims do not entail an answer merely because they share a request, model, metadata key, or facet. Add declared entailing evidence against the exact answer claim only when the receipt establishes that answer; otherwise keep it as context or separate cited evidence.
Register the profile#
Use the existing reference factory and registry; metadata is for authoring and inspection, never status derivation.
import {
createAnswerAssessmentReferenceExtension,
registerExtension,
} from "@kontourai/surface";
const reference = createAnswerAssessmentReferenceExtension({
name: "my-answer-product",
displayName: "My Answer Product",
vocab: {},
theme: { brandName: "My Answer Product" },
});
registerExtension({
...reference,
claimTypes: [
{
id: "product.answer.llm-answer",
displayName: "LLM answer content",
description: "A concrete content assertion with frozen-source and owner-review backing.",
defaultImpact: "medium",
defaultFacet: "answer-provenance",
policyTemplateId: "product.answer.llm-answer-policy/v1",
metadataFields: [
{ key: "conversationId", label: "Conversation ID", type: "string" },
{ key: "messageId", label: "Message ID", type: "string" },
{ key: "model", label: "Model", type: "string" },
],
},
{
id: "product.answer.routing-receipt",
displayName: "Routing receipt",
description: "Request context; it is not answer support.",
defaultImpact: "low",
defaultFacet: "answer-provenance",
},
],
policyTemplates: [
...(reference.policyTemplates ?? []),
{
id: "product.answer.llm-answer-policy/v1",
template: {
claimType: "product.answer.llm-answer",
requiredEvidence: ["source_excerpt", "human_attestation"],
requiredMethods: ["observation", "attestation"],
acceptanceCriteria: [
"A frozen source excerpt states the exact answer content.",
"The owner review confirms the content against that frozen source.",
],
reviewAuthority: "answer-product-owner",
validityRule: { kind: "duration", durationDays: 7 },
stalenessTriggers: ["frozen source is replaced", "content review is superseded"],
conflictRules: ["A blocking failed content review disputes the answer claim."],
impactLevel: "medium",
},
},
],
});
registerExtension is an authoring/presentation registry only. It cannot make
a claim verified, infer review, or create a semantic relationship.
Positive answer assessment#
The owner-built Basis assessment needs a resolved product policy, a verified
nonstale concrete content assertion, and explicitly declared, non-failed
supportStrength: "entails" evidence for that exact answer. Required evidence
types/methods must be met; blocking evidence or gaps prevent success. Cited-only
receipts and failed nonblocking records remain visible but are not positive
support. Rebuild via buildAnswerAssessmentProjection(report, claimId); do not
construct a policy verdict or supply a caller boolean.
The reference profile deliberately uses a frozen source_excerpt plus a
human_attestation for its positive content assertion. Its retained
runtime_observation routing and tool receipts are context claims, not proof of
answer semantics. This is a profile choice, not a global ban on runtime
observations: a runtime fact can support content when the product's own claim
and policy make that direct relation explicit.
const report = buildTrustReport(bundle, {
id: authorizedImmutableBundleHandle,
now,
});
const assessment = buildAnswerAssessmentProjection(report, "answer.supported");
The explicit id is an authorized immutable bundle handle. A producer name,
source string, or generated clock value is not an immutable identity.
Interoperability and packaging#
Claim types are product vocabulary. Similar answer, routing, or tool claims from different products do not merge automatically because their metadata or facets resemble one another. Keep them distinct until a real shared consumer needs a common contract, then propose an Ops vocabulary convention with examples from both products. Do not silently promote one product's type into suite-wide meaning.
requiresCorroboration counts eligible evidence records. A product derivation
condition such as minActors is separate: record count never proves distinct
actors. Keep profile code beside the product's receipt and authorization
boundary. A second consumer sharing stable IDs, policy semantics, and authoring
fields is the evidence needed to extract a profile package.