Seal a review submission
Assembles the ten ruled review-package components from a declared source corpus, a TOTAL disposition ledger and a bidirectional source-to-rule map, then seals them as an immutable review subject together with their exact canonical bytes.
RECORDS which bytes a reviewer will be shown, in which named slot, under which policy version, in what order, at what time, attributed to which principal.
PROVES byte equality under meshqu-review-package/v1, and nothing else. It does not prove source truth, source completeness, correct interpretation, human review or authority.
Source custody. A manifest item naming custodian urn:meshqu:custodian:meshqu asserts that MeshQu HOLDS those bytes, and the seal refuses unless it does: the server looks for a finalised, read-back-verified object at that tenant’s content address, in the same transaction that seals, and returns source_item_not_finalised_in_custody if there is none or source_custody_attestation_disagrees_with_manifest if the digest or length disagrees. Custody is MeshQu-controlled and tenant-scoped; upload the bytes through /v1/source-uploads first. Items naming any other custodian are unchecked here — MeshQu resolves no external locator and makes no claim about a custodian’s store.
The availability field sealed into the manifest stays NOT_CHECKED in every case, deliberately: availability is a CHECKED OBSERVATION made at a moment, and sealing one would freeze a time-varying fact into an immutable subject. Current availability is reported separately by GET /v1/review-submissions/{submissionId}?check_source_availability=true.
The candidate this seal binds to. Both checks below run inside the sealing transaction, under a FOR UPDATE row lock on the policy version, so they and the seal see one state of it.
expected_draft_revision is OPTIONAL. When supplied it must equal the stored draft_revision; a disagreement is 409 STALE_DRAFT_REVISION with expected_revision and current_revision in details — the same refusal PATCH and ratify answer — and nothing is sealed. RECORDS nothing: the field enters no component, no digest preimage and no stored column.
expected_latest_submission_id — a duplicate cannot seal twice (1.24.0). The id of the latest sealed submission of this version the caller read before deciding to seal (the first item of the list route), or null when there was none. It is compared (case-insensitively), under the same locks, with the server’s latest submission for the version; a disagreement is 409 STALE_LATEST_SUBMISSION with expected_latest_submission_id in details, and nothing is sealed. Two requests carrying the same value can never both seal, in either arrival order: the first to commit moves the latest. So an unchanged re-send by a caller that kept its earlier read cannot seal twice; a re-send refused STALE_LATEST_SUBMISSION means another seal — possibly the first copy of this very request — committed first, and its details.latest_is_this_package says whether the latest seal is exactly what THIS request would seal now: the server assembles the refused request as a seal would and compares the package digest and the active rules. true: the intended package is sealed (by whoever sent it); false: it is not. It judges against the draft’s current rules — send expected_draft_revision to bind the rules you saw. A legitimate further seal (after a return, say) names the submission that is latest now. REQUIRED when an authoring capability admitted the request — absent is 400 EXPECTED_LATEST_SUBMISSION_REQUIRED — and optional for every other caller, which is checked when it sends the field and unchanged when it does not. RECORDS nothing. PROVES only that no other seal of this version committed between the caller’s read and this seal; it says nothing about which request the caller meant to be the one that sealed.
Supplied or not, the body’s candidate_policy rule codes are compared with the stored version’s active-rule projection (projectActiveRules, the same predicate active_rules_hash is taken over) and a disagreement is 422 CANDIDATE_POLICY_NOT_CURRENT, whose details carry one candidate_policy_not_current entry naming the first differing rule code. That code is this route’s own and is NOT part of the assembler’s closed vocabulary. PROVES only that the two name the same ordered set of executable rule CODES: a rule whose condition, severity or when changed while its code stayed put does not differ here — it moves active_rules_hash, which is computed from the same locked read, so the hash is current either way.
Resubmission creates a NEW subject. Posting identical bytes again returns a different id with the next submission_sequence; it never returns or updates the earlier one. A judgment recorded against one subject can never be applied to another.
Assembly failures return 422 carrying every violation found, each with its own distinct code (unit_without_disposition, mapped_unit_without_rule, internal_control_claims_source_mapping, and the rest of the closed set) and the offending identifier, so a defect class is diagnosable rather than reported as “validation failed”.
This route ratifies nothing, activates nothing and records no decision. It is not a receipt and no receipt binds its output.
Bounded write (MCP-034): the server abandons this write if any one lock wait exceeds 2000 ms, or if it is not ready to commit 4000 ms after the server began handling the request. An abandoned write is rolled back — nothing is written — and answered 503 AUTHORING_WRITE_CONTENDED with details.committed: false and a Retry-After header. That answer is a definite non-commit, safe to retry; it is distinct from a lost response or any other 5xx, whose outcome is unknown. The bound is counted from when THIS SERVER began handling the request, which a client cannot observe, and nothing bounds how late a request reaches the server: a client whose request timed out cannot conclude from any wait that it did not commit.
Authorizations
MeshQu API key passed as a bearer token: Authorization: Bearer mqu_…. Mint one in the console (Settings → API keys).
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
Policy id.
Policy version number.
x >= 1Body
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.
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.
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.
Independently authored cases and expected outcomes. Carried verbatim into the digest.
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.
The displayed case-execution results. Separated from case_pack for the same reason.
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.
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.
x >= 0The 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.
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.
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.
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
The submission’s own identity — distinct per submission even when the policy version and the bytes are unchanged (ruling B1).
The mutable workflow container the candidate came from. NOT the review identity: one policy version yields many sealed subjects over time.
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.
x >= 1Submission ordinal within the policy version, from 1. An ORDERING fact, not an identity — id is the identity.
x >= 1sha256(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.
^[0-9a-f]{64}$The ten ruled component digests, keyed by component name. A closed registry: an eleventh key is invalid and a missing key is invalid.
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.
^[0-9a-f]{64}$When the seal was created. Not when a human read it.
The principal the server attributed the submit act to. Records attribution; proves nothing about human authorship, competence or independent judgment.
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.
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.