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.

Source: docs/guides/authoring-product-profile.md