Skip to main content
POST
Seal a review submission

Authorizations

Authorization
string
header
required

MeshQu API key passed as a bearer token: Authorization: Bearer mqu_…. Mint one in the console (Settings → API keys).

X-MeshQu-Tenant-Id
string
header
required

Tenant UUID for multi-tenant isolation. Required on all authenticated routes — validated before authentication (middleware/tenant.ts), so a missing or non-UUID header returns 400 (MISSING_TENANT_ID / INVALID_TENANT_ID) before the API key is checked.

Path Parameters

id
string<uuid>
required

Policy id.

version
integer
required

Policy version number.

Required range: x >= 1

Body

application/json

Everything needed to assemble the ten ruled component slots. Four slots — the corpus (source_manifest_items / source_units), clause_ledger, candidate_policy and source_rule_map — must be genuinely present: a source-bound review package with no corpus, ledger, candidate or map is not one, and every totality and bidirectionality rule over it would be vacuously true.

Everything needed to assemble the ten ruled component slots. Four slots — the corpus (source_manifest_items / source_units), clause_ledger, candidate_policy and source_rule_map — must be genuinely present: a source-bound review package with no corpus, ledger, candidate or map is not one, and every totality and bidirectionality rule over it would be vacuously true.

source_manifest_items
required
source_units
required
clause_ledger
required
candidate_policy
required

The candidate policy as projected into the package. Any field beyond candidate_rules is carried VERBATIM into the component and therefore into its digest — the assembler adds nothing and removes nothing.

source_rule_map
required

The bidirectional source-to-rule map. BOTH directions are stored and must be exact transposes. Storing one and deriving the other would make disagreement impossible to observe: two directions that must agree is a check, one direction plus a derivation is a restatement.

assumptions
required
internal_controls
required
case_pack
required

Independently authored cases and expected outcomes. Carried verbatim into the digest.

validation_results
required

The displayed server/tool validation results. Separated from case_pack deliberately, so changing a RESULT changes the review subject rather than silently reusing the digest of the expectations alone.

case_execution_results
required

The displayed case-execution results. Separated from case_pack for the same reason.

model_trace
required

The model trace, as shown. Carried verbatim into the digest — except that when an authoring capability admitted this request, the slot is emitted as { authoring_capability, agent_model_trace }: the server-resolved capability binding, and this value beneath it byte for byte (OPD-108). The two keys authoring_capability and agent_model_trace are RESERVED — supplying either at the top level of this object is refused with MODEL_TRACE_RESERVED_KEY, so a caller cannot write a slot that reads like a server binding.

expected_draft_revision
integer

The draft_revision the client displayed when it assembled this package. When supplied it must equal the stored draft_revision at seal time, read under the seal's own row lock on the policy version; a disagreement is refused 409 STALE_DRAFT_REVISION with expected_revision and current_revision in details, exactly as PATCH and ratify refuse one, and NOTHING is sealed. Omitting it is permitted and changes no other behaviour — in particular the candidate_policy currentness check below runs either way. It enters no component, no digest and no stored column: it RECORDS nothing and PROVES nothing, it only buys the refusal.

Required range: x >= 0
expected_latest_submission_id
string<uuid> | null

The id of the latest sealed submission of THIS policy version that the caller read before deciding to seal — the first item of GET /v1/policies/{id}/versions/{version}/review-submissions — or null when that list was empty. Compared case-insensitively, under the seal's own row locks, with the server's latest submission for the version; a disagreement is refused 409 STALE_LATEST_SUBMISSION with expected_latest_submission_id in details, and NOTHING is sealed. Because the first of two requests carrying the same value moves the latest, the second is always refused: an unchanged re-send by a caller that kept its earlier read cannot seal twice. REQUIRED when an authoring capability admitted the request (400 EXPECTED_LATEST_SUBMISSION_REQUIRED when absent); optional otherwise. It enters no component, no digest and no stored column: it RECORDS nothing and PROVES nothing, it only buys the refusal.

authoring_capability_id
string<uuid>

The authoring capability the caller asserts admitted this request. REQUIRED when a capability admitted it, REFUSED when none did. It is checked against the capability the credential resolved to and is never stored as supplied — the server records its own resolution. Records which capability the API resolved at seal time; it does not establish anything about the model behind the credential.

answers_record_id
string<uuid>

The review record this submission answers. REQUIRED when the prior submission of this policy version has been RETURNED, and REFUSED when it has not — so "which record does this revision answer" can never be answered by silence. It is checked against the return and never stored as supplied. It enters NO component and no digest preimage moves.

prior_source_manifest_items
object[]

The source_manifest_items of the immediately preceding sealed submission, when one exists. Present ONLY so "the bytes moved while the declared identity stayed still" is detectable. It is not part of any component and never enters a digest.

Response

Default Response

id
string<uuid>
required

The submission’s own identity — distinct per submission even when the policy version and the bytes are unchanged (ruling B1).

tenant_id
string<uuid>
required
policy_id
string<uuid>
required
policy_version_id
string<uuid>
required

The mutable workflow container the candidate came from. NOT the review identity: one policy version yields many sealed subjects over time.

candidate_revision
integer
required

A server-derived counter of DISTINCT CONSECUTIVE active-rule projections sealed for this policy version, starting at 1. It increments when the new submission’s active_rules_hash differs from the previous submission’s, and carries forward when it does not. Named for what it counts: it is NOT policy_versions.draft_revision (which does exist, and which the seal compares expected_draft_revision against), it is NOT a count of author edits, and it does not move for an edit that leaves the active-rule projection unchanged (a renamed rule, a changed description, a different case pack). Two submissions may therefore share a candidate_revision and still be different subjects — they are told apart by id and review_package_digest, which is why the currentness check has three legs.

Required range: x >= 1
submission_sequence
integer
required

Submission ordinal within the policy version, from 1. An ORDERING fact, not an identity — id is the identity.

Required range: x >= 1
review_package_digest
string
required

sha256(UTF8("meshqu-review-package/v1") || 0x00 || UTF8(canonicalJsonV2(envelope))). RECORDS which exact canonical bytes the reviewer was shown. PROVES byte equality under that named profile and nothing else — not source truth, completeness, correct interpretation, human review or authority. NOT a receipt field.

Pattern: ^[0-9a-f]{64}$
component_digests
object
required

The ten ruled component digests, keyed by component name. A closed registry: an eleventh key is invalid and a missing key is invalid.

active_rules_hash
string
required

sha256(canonicalJson(projectActiveRules(rules))) under meshqu-canonical/v0 — NOT v2, and deliberately OUTSIDE the ten component slots (ruling D2), so that comparing it against a recomputation from the product path is a real check rather than one artefact validating itself. It means "this is the projection the evaluator will run", which is a different claim from the package digest’s "these are the bytes a human read". Neither implies the other.

Pattern: ^[0-9a-f]{64}$
sealed_at
string<date-time>
required

When the seal was created. Not when a human read it.

sealed_by
string
required

The principal the server attributed the submit act to. Records attribution; proves nothing about human authorship, competence or independent judgment.

authoring_capability_id
string<uuid> | null
required

The authoring capability that ADMITTED this seal, as the server resolved it from the authenticating credential — not read from the package and not chosen by the caller (OPD-108). null where none did: a seal made through the interim policies:write path has no capability behind it, and inventing one would claim an authority nothing checked.

RECORDS which capability record the credential resolved to at the moment of sealing. It does NOT establish that the model named in that capability's self-declared agent_declaration produced these bytes — a capability's subject is the API key.

answers_record_id
string<uuid> | null
required

The review record this submission ANSWERS, as the server resolved it from the return of the prior submission of this policy version (OPD-130). null for a first submission and for every row sealed before the field existed.

IT IS IN NO COMPONENT'S CANONICAL BYTES and no digest preimage moves: a package sealed with this link digests to exactly what the same package digests to without one.

RECORDS which record the server verified this package was sealed in answer to. It does NOT establish that the package addresses that record's findings or corrections — nothing reads the package against the record.