diff --git a/docs/architecture.md b/docs/architecture.md index 96696c0..3e14a74 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -740,9 +740,10 @@ The WMS backend also stores request records and revisions, request-to-change- set/build-item links, priority audit events and snapshots, and durable blocked-work resolution-submission or acknowledgement records. A submission has its own revision and approval/digest binding; writing or replaying it -does not change the work-item lifecycle state or contract version. The -Materializer consumes an eligible submission through the authoritative -Validation Rules boundary. +does not change the work-item lifecycle state, contract version, or +dependencies. The Materializer consumes the currently-active submission +through authoritative `resolve-block`; a superseded submission's Gate +approval is revoked and cannot be applied. **Where it lives:** The configured WMS backend, one per project. diff --git a/docs/architecture/components.md b/docs/architecture/components.md index 008df6a..8d66efb 100644 --- a/docs/architecture/components.md +++ b/docs/architecture/components.md @@ -652,6 +652,13 @@ The API surface includes: mutate the implementation branch. A stale owner or duplicate claim fails without mutation even if the item later cycles through the same state. +- **Blocked-work resolution submissions:** Durable + `blocked-work.submit-resolution` and `blocked-work.acknowledge` records + as defined in the [Drafting Table WMS Integration + Contract](drafting-table-wms.md). These writes do not change work-item + lifecycle state, `contract_version`, or `dependencies`. +- **Lifecycle preflight:** Advisory `lifecycle.preflight` using the shared + Validation Rules evaluator; never an authoritative mutation. - **Queries:** Read items by ID, state, dependency, owner, or idempotency key, including "all ready items" and "all blocked items." - **Git references:** Record source specification and code commits, @@ -1745,14 +1752,18 @@ input: undefined behavior. The work item is marked blocked in the WMS. 2. **Any team member** writes the missing spec changes and opens a linked change-set PR against main (the normal contribution flow). -3. **Reviewer** merges the PR. Its build work item becomes an explicit - dependency of the blocked item when implementation is required. -4. After the dependency completes, the original item follows the full - contract-refresh policy. It returns to `ready-for-building` only after - all pre-claim checks pass; otherwise it remains blocked for an impact - amendment or is superseded. A previously executing item follows the - blocked-item resume path, obtains a new fenced claim before touching - its branch, and reruns all execution gates. +3. **Reviewer** merges the PR. The blocked-work resolution submission + records a planned dependency on the linked change-set build work when + implementation is required; that edge is not written onto the blocked + work item. +4. After the planned dependency completes, Materializer `resolve-block` + observes it during full refresh and performs the single + `blocked -> ready-for-building` transition. The original item returns + to `ready-for-building` only after all pre-claim checks pass; otherwise + it remains blocked for an impact amendment or is superseded. A + previously executing item follows the blocked-item resume path, + obtains a new fenced claim before touching its branch, and reruns all + execution gates. This uses the same PR → merge flow as initial contributions — no special escalation mechanism needed. diff --git a/docs/architecture/drafting-table-wms.md b/docs/architecture/drafting-table-wms.md index 7c8b706..a6944d8 100644 --- a/docs/architecture/drafting-table-wms.md +++ b/docs/architecture/drafting-table-wms.md @@ -1,6 +1,8 @@ # ProtoBot: Drafting Table WMS Integration Contract > Interface contract — issue #31 — September 2026 +> Document revision: `wms-contract-doc/v1` (document revision, +> distinct from the per-work-item `contract_version` field) > > Defines the backend-neutral WMS operations available to the Drafting > Table during backlog refinement and blocked-work resolution. @@ -111,18 +113,32 @@ For a blocked-work submission, `human_approval_id` and `approval_resolution_digest` are required inputs. The WMS Adapter resolves the approval from trusted Gate state and verifies the approved subject, delegated principal, work-item ID, resolution kind, expected state/version, -request fingerprint, policy version, expiry, and single-use status. Missing, -unknown, cross-item, wrong-kind, digest-mismatched, expired, or consumed -approvals return `UNAUTHORIZED_ACTION` before any resource write. The same -checks apply to an informational acknowledgement. +request fingerprint, policy version, expiry, and single-use status. For +`blocked-work.submit-resolution`, the delegated principal must match the +configured Materializer subject from trusted project/Gate configuration (the +same identity `resolve-block` will later present). For +`blocked-work.acknowledge`, the delegated principal must match the trusted +Drafting Table subject that writes the acknowledgement. Missing, unknown, +cross-item, wrong-kind, digest-mismatched, expired, consumed, revoked, or +delegated-principal-mismatched approvals return `UNAUTHORIZED_ACTION` +before any resource write. The same checks apply to an informational +acknowledgement. Acceptance of `blocked-work.submit-resolution` verifies the approval binding -and stores it with the durable submission; it does not consume or reserve the -single-use approval. A new reviewed submission may supersede the one pending -submission, leaving the prior approval unused. The Materializer rechecks and -consumes the selected approval only when authoritative `resolve-block` -succeeds. `blocked-work.acknowledge` has no later lifecycle consumer, so its -approval is consumed atomically when the acknowledgement is written. +and stores it with the durable submission; it does not consume or reserve +the single-use approval. A new reviewed submission may supersede the one +pending submission; that write marks the prior submission `superseded` and +revokes its Gate approval so the approval is terminal. Authoritative +`resolve-block` must name the currently-active `resolution_submission_id` +and consume only that submission's approval. A superseded submission's +revoked approval cannot unblock the item. +_(Note: Corrected from prior contract text, which required only +`human_approval_id` and `approval_resolution_digest`; authoritative +`resolve-block` now additionally requires the currently-active +`resolution_submission_id` to bind approval consumption to that specific +active submission and prevent replay of superseded approvals.)_ +`blocked-work.acknowledge` has no later lifecycle consumer, so its approval +is consumed atomically when the acknowledgement is written. --- @@ -160,11 +176,14 @@ work-item contract version. owned by the WMS Adapter. It has its own `resolution_submission_id`, `resolution_submission_revision`, work-item ID, resolution kind, approval ID/digest, and submission status. Creating or replaying this record does not -mutate the work item's lifecycle state or `contract_version`; the Materializer -later consumes it through the authoritative `resolve-block` operation. The -submission record carries the planned dependency or confirmation reference; -the authoritative lifecycle mutation and any dependency-state update happen -only during Materializer processing. +mutate the work item's lifecycle state, `contract_version`, or +`dependencies`. The submission record carries the planned dependency or +confirmation reference. The only authoritative work-item mutation is later +Materializer `resolve-block`, whose full refresh observes that recorded +planned dependency without a pre-transition work-item write. +_(Note: Corrected from prior contract text, which implied Materializer +processing created or refreshed a dependency directly onto the blocked work +item for `add-requirement`.)_ ### Work-item read projection @@ -200,7 +219,7 @@ HTTP, or local adapter binding may choose a transport-specific spelling. | Operation | Caller and authorization | Expected version / idempotency | Success result | Failure behavior | | --- | --- | --- | --- | --- | | `request.create` | Drafting Table under a project-scoped `drafting-table` context. | New request; required idempotency key. | Complete request at `request_revision: 1`; no change set or work item is created implicitly. | Exact key/fingerprint retry returns `replayed`; a different fingerprint returns `IDEMPOTENCY_CONFLICT`; a semantic duplicate under a new key returns `DUPLICATE_REQUEST`; invalid fields or WMS failure leave no partial record. | -| `request.refine` | Drafting Table submits refinement under a Gate-bound human approval for classification, scope, relationships, owner, and intent. | `expected_request_revision`, `human_approval_id`, `approval_refinement_digest`, and idempotency key. | Updated request, refinement state, classification, links, and incremented request revision. | Reject missing/invalid approval, stale revision, invalid relationship/classification, unauthorized target, duplicate request, or unavailable WMS. | +| `request.refine` | Drafting Table submits refinement under a Gate-bound human approval for classification, scope, relationships, owner, and intent. | `expected_request_revision`, `human_approval_id`, `approval_refinement_digest`, and idempotency key. | Updated request, refinement state, classification, links, incremented request revision, and `approval_status: consumed`. | Reject missing, mismatched, expired, or already-consumed approval, stale revision, invalid relationship/classification, unauthorized target, duplicate request, or unavailable WMS. | | `request.update-priority` | `human-maintainer` only. | `expected_request_revision` and idempotency key. | New business priority, request revision, audit event, and an atomic priority snapshot update for linked proposed change sets/build items. | Reject agent/service caller, stale revision, invalid priority, or failed atomic WMS update. | | `request.link-change-set` | Drafting Table or human maintainer with visibility to both records. | Expected request revision, target change-set revision when mutable, and idempotency key. | Existing request-to-change-set link and updated request revision. | Reject missing/unauthorized target, duplicate link, stale endpoint, or invalid change-set state. This does not create a change set. | | `request.link-build-work-item` | Drafting Table may link only to an existing authorized build item; Materializer may create the automatic link as part of materialization. | Expected request revision, existing target ID, and idempotency key. | Existing request-to-build-item link and updated request revision. | Reject missing target, duplicate link, stale source, or any attempt to materialize the target as a side effect. | @@ -209,9 +228,9 @@ HTTP, or local adapter binding may choose a transport-specific spelling. | `work-item.get` | Drafting Table with project read visibility. | `work_item_id`; return current contract version. | One sanitized work-item projection. | Return visibility-safe `NOT_FOUND` or `WMS_UNAVAILABLE`; never mutate state. | | `work-item.query` | Drafting Table with project read visibility. | No expected version. | Filtered sanitized work-item projections by state, owner, dependency, or priority; each result includes its current contract version. | Return `INVALID_REQUEST`, visibility-safe not-found, or `WMS_UNAVAILABLE`; never mutate state. | | `blocked-work.query` | Drafting Table on session start/resume or explicit user request. | No expected version; every item includes current state and contract version. | Sanitized blocked items with reason class, next action, dependencies, and resolution options. | Mark blocked-work status unavailable if WMS is unavailable; never claim review is complete. | -| `lifecycle.preflight` | Drafting Table, Job Site, or Materializer before an authoritative operation. | Caller snapshot includes expected state/version; no mutation key is required for a read-only preflight. | `authority: preflight` decision and diagnostics from the shared Validation Rules evaluator. | Advisory rejection only; the caller must still submit the authoritative operation. | -| `blocked-work.submit-resolution` | Drafting Table submits `add-requirement`, `out-of-scope`, or `impact-amendment` with Gate-bound human approval. | `expected_state: blocked`, `expected_contract_version`, approval-resolution digest, and idempotency key. | A durable resolution-submission record at the next `resolution_submission_revision` (or its existing revision on replay); a new reviewed submission may supersede one pending submission atomically. The work item remains `blocked` and its `contract_version` is unchanged until Materializer processing. | Return `UNAUTHORIZED_ACTION`, `STALE_STATE`, `STALE_CONTRACT_VERSION`, `PRECONDITION_FAILED`, or replay the exact prior result. | -| `blocked-work.acknowledge` | Drafting Table submits an informational acknowledgement with Gate-bound human approval. | `expected_state: blocked`, `expected_contract_version`, approval-resolution digest, and idempotency key. | A durable acknowledgement record at `resolution_submission_revision: 1`; the work item remains `blocked`. The approval is consumed atomically with this resource write; an exact retry replays without consuming it again. | Apply the same authorization, stale-state/version, precondition, and replay behavior as `blocked-work.submit-resolution`. | +| `lifecycle.preflight` | Drafting Table, Job Site, or Materializer before an authoritative operation. | Caller snapshot includes expected state/version; no mutation key is required for a read-only preflight. A pre-submission `add-requirement` preflight payload may carry `change_set_id` directly (rather than `resolution_submission_id`) to preview the planned-dependency check, and the evaluator treats it as the hypothetical planned dependency for that preview only. | `authority: preflight` decision and diagnostics from the shared Validation Rules evaluator. | Advisory rejection only; the caller must still submit the authoritative operation. | +| `blocked-work.submit-resolution` | Drafting Table submits `add-requirement`, `out-of-scope`, or `impact-amendment` with Gate-bound human approval. | `expected_state: blocked`, `expected_contract_version`, `human_approval_id`, `approval_resolution_digest`, and idempotency key. | A durable resolution-submission record at the next `resolution_submission_revision` (or its existing revision on replay); a new reviewed submission may supersede one pending submission atomically and revoke that submission's Gate approval. The work item remains `blocked` and its `contract_version` is unchanged until Materializer `resolve-block`. | Return `UNAUTHORIZED_ACTION`, `STALE_STATE`, `STALE_CONTRACT_VERSION`, `PRECONDITION_FAILED`, or replay the frozen original result. | +| `blocked-work.acknowledge` | Drafting Table submits an informational acknowledgement with Gate-bound human approval. | `expected_state: blocked`, `expected_contract_version`, `human_approval_id`, `approval_resolution_digest`, and idempotency key. | A durable acknowledgement record at `resolution_submission_revision: 1`; the work item remains `blocked`. The approval is consumed atomically with this resource write; an exact retry replays without consuming it again. | Apply the same authorization, stale-state/version, precondition, and replay behavior as `blocked-work.submit-resolution`. | The operation set is intentionally disjoint from Job Site execution. A fake adapter must reject a Drafting Table caller attempting `claim`, @@ -273,7 +292,13 @@ For `request.refine`, the Gate binds `human_approval_id` and `approval_refinement_digest` to the approved human subject, request ID, proposed refinement fields, delegated Drafting Table subject, expiry, and single-use state. The adapter rejects a missing, mismatched, expired, or -already-consumed approval before changing the request. +already-consumed approval before changing the request. A successful refine +consumes `human_approval_id` in the same durable write as the request +revision; an exact idempotent replay does not consume it again. +_(Note: Corrected from prior contract text, which did not explicitly +require `approval_status: consumed` on successful refinement or verify that +a consumed refine approval cannot be replayed under a different idempotency +key.)_ ### Result envelope @@ -328,7 +353,7 @@ contract. The choices have distinct WMS behavior: | User choice | Submission payload | Immediate WMS effect | | --- | --- | --- | -| Add a requirement | `resolution_kind: add-requirement`, linked change-set ID, resolution digest | Submit reviewed resolution; Materializer later validates and refreshes the blocked item. | +| Add a requirement | `resolution_kind: add-requirement`, linked change-set ID, resolution digest | Submit reviewed resolution; no work-item mutation until Materializer `resolve-block`. | | Approve out of scope | `resolution_kind: out-of-scope`, approved declaration/change-set ID, digest | Submit reviewed declaration; no direct state change. | | Amend impact | `resolution_kind: impact-amendment`, linked change-set ID, digest | Submit reviewed impact amendment; expected state/version remain required. | | Defer | No adapter operation; session-local decision only. | No WMS call or lifecycle mutation; item remains `blocked` and the session may continue. | @@ -348,37 +373,49 @@ but does not pass it to the Validation Rules evaluator as `resolve-block`. The submission result is not a Validation Rules decision and carries no authoritative lifecycle outcome. A successful submission writes only the resolution-submission record; it is not an unblock. The Materializer later -invokes the authoritative `resolve-block` lifecycle operation only after its -full refresh preconditions -pass, consumes the approval atomically, and produces the single -`blocked -> ready-for-building` transition defined by Validation Rules. -Before that operation can be applied, the submission records these -resolution-specific prerequisites: +invokes the authoritative `resolve-block` lifecycle operation, names the +currently-active `resolution_submission_id`, and produces the single +`blocked -> ready-for-building` transition defined by Validation Rules +only after that command's full refresh preconditions pass. `resolve-block` +consumes only the named active submission's approval, atomically with the +transition. There is no same-state `blocked` work-item mutation and no +`blocked -> waiting` transition. Before a resolution is submitted, a caller +may run `lifecycle.preflight` for `resolve-block` to preview refresh +preconditions; for an `add-requirement` preview where no +`resolution_submission_id` exists yet, the preflight payload may carry +`change_set_id` directly, and the evaluator treats it as the hypothetical +planned dependency for that preview only. Before `resolve-block` can be +applied, the active submission records these resolution-specific refresh +preconditions: - `add-requirement`: the submission records a planned dependency on the - linked change-set build work; the original item remains `blocked` until - Materializer processing creates or refreshes that dependency and it - completes; + linked change-set build work. That planned dependency is not written + onto the work item. `resolve-block`'s full refresh observes it on the + named submission; if it is incomplete, the evaluator returns + `PRECONDITION_FAILED` and the item remains `blocked`; - `out-of-scope`: the item remains `blocked` until the required independent Inspector confirmation is recorded; -- `impact-amendment`: the linked change set must validate and its full refresh - must pass; and -- `acknowledge`: the acknowledgement records the informational condition and - remains `blocked`; it does not clear the condition or invoke `resolve-block`. - Any later lifecycle resolution must use its own approved submission and - Validation Rules preconditions. +- `impact-amendment`: the linked change set must validate and its full + refresh must pass; and +- `acknowledge`: the acknowledgement records the informational condition + and remains `blocked`; it does not clear the condition or invoke + `resolve-block`. Any later lifecycle resolution must use its own + approved submission and Validation Rules preconditions. Any failed refresh leaves the item `blocked` and returns the shared diagnostic. The Materializer, not the Drafting Table, owns the lifecycle transition and contract-version increment. Only one nonterminal lifecycle resolution submission may be active for a -work item at a time. An exact retry replays its existing submission; a new -reviewed lifecycle resolution atomically marks the prior pending submission -`superseded` and becomes the active submission. Informational acknowledgements -are separate audit records and do not compete with the lifecycle submission. -A submission becomes `consumed` only when the Materializer's authoritative -`resolve-block` succeeds; a superseded submission leaves its approval unused. +work item at a time. An exact key/fingerprint retry returns the frozen +original result without a second mutation; current submission status +(`superseded` or `consumed`) is visible only on a subsequent read of the +submission record. A new reviewed lifecycle resolution atomically marks +the prior pending submission `superseded`, revokes that submission's Gate +approval, and becomes the active submission. Informational acknowledgements +are separate audit records and do not compete with the lifecycle +submission. A submission becomes `consumed` only when the Materializer's +authoritative `resolve-block` succeeds against it. --- @@ -440,12 +477,19 @@ The fixture asserts: - preflight is advisory and the authoritative resolution preserves `blocked` state/version checks; - resolution submissions own a separate revision and leave the work item - blocked until Materializer processing; -- a new lifecycle resolution supersedes the pending one atomically, while an - acknowledgement remains an independent audit record; + blocked until Materializer `resolve-block`; +- a new lifecycle resolution supersedes the pending one atomically and + revokes the prior approval, while an acknowledgement remains an + independent audit record; - approval checks reject missing, forged, cross-item, wrong-digest, expired, - and consumed approvals; -- an exact resolution retry replays without a second mutation; + consumed, revoked, and delegated-principal-mismatched approvals; +- an exact resolution retry replays the frozen original result; +- Materializer `resolve-block` independently rejects a revoked prior + approval and a non-active submission ID; +- Materializer `resolve-block` rejects an active `add-requirement` + submission whose planned dependency is incomplete with + `PRECONDITION_FAILED` while ordinary dependencies are satisfied, + leaving approval unused and work-item dependencies unchanged; - stale resolution state/version is rejected; - a Drafting Table caller cannot claim, execute, complete, schedule, or mutate findings, and direct `resolve-block` is rejected; and diff --git a/docs/architecture/fixtures/drafting-table-wms-golden.jsonl b/docs/architecture/fixtures/drafting-table-wms-golden.jsonl index 54be80e..279896f 100644 --- a/docs/architecture/fixtures/drafting-table-wms-golden.jsonl +++ b/docs/architecture/fixtures/drafting-table-wms-golden.jsonl @@ -1,12 +1,13 @@ -{"step":"base-state","project_id":"fixture-project","requests":[],"work_items":[{"id":"wi-001","state":"blocked","contract_version":7,"dependencies":["wi-000"]}],"fake_gate_contexts":{"drafting-table":{"subject":"agent-001","role":"drafting-table","allowed_actions":["request.create","request.refine","request.link-change-set","request.link-build-work-item","request.get","request.query","work-item.get","work-item.query","blocked-work.query","lifecycle.preflight","blocked-work.submit-resolution","blocked-work.acknowledge"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"human-maintainer":{"subject":"human-001","role":"human-maintainer","allowed_actions":["request.update-priority"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"job-site":{"subject":"job-site-001","role":"job-site","allowed_actions":["claim","tests-pass","record-merge"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"materializer":{"subject":"materializer-001","role":"materializer","allowed_actions":["materialize","refresh-dependencies","revalidate","resolve-block"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"reconciler":{"subject":"reconciler-001","role":"reconciler","allowed_actions":["recover-lease","merge-conflict","merge-not-applied","record-merge"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"}},"approval_records":{"refine-approval-001":{"approved_subject":"human-001","delegated_principal":"agent-001","request_id":"request-001","resolution_digest":"sha256:refine-001","action":"request.refine","status":"unused"},"resolution-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-001","action":"resolve-block","status":"unused"},"ack-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:ack-001","action":"acknowledge","status":"unused"}}} -{"step":"approval-validation-state","evaluation_time":"2026-09-18T12:00:00Z","approval_records":{"refine-approval-001":{"approved_subject":"human-001","delegated_principal":"agent-001","request_id":"request-001","resolution_digest":"sha256:refine-001","action":"request.refine","policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"resolution-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-001","action":"resolve-block","resolution_kind":"add-requirement","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"ack-approval-001":{"approved_subject":"human-001","delegated_principal":"agent-001","work_item_id":"wi-001","resolution_digest":"sha256:ack-001","action":"blocked-work.acknowledge","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"stale-state-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-stale-state-001","action":"resolve-block","resolution_kind":"impact-amendment","expected_state":"ready-for-building","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"stale-version-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-stale-version-001","action":"resolve-block","resolution_kind":"impact-amendment","expected_state":"blocked","expected_contract_version":6,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"expired-approval":{"approved_subject":"human-001","delegated_principal":"agent-001","work_item_id":"wi-001","resolution_digest":"sha256:expired-approval","action":"blocked-work.acknowledge","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-10T12:00:00Z","status":"expired"},"consumed-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:consumed-approval","action":"resolve-block","resolution_kind":"out-of-scope","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"consumed"},"cross-item-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-999","resolution_digest":"sha256:cross-item-approval","action":"resolve-block","resolution_kind":"out-of-scope","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"impact-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:impact-approval-001","action":"resolve-block","resolution_kind":"impact-amendment","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"ack-wrong-digest-approval":{"approved_subject":"human-001","delegated_principal":"agent-001","work_item_id":"wi-001","resolution_digest":"sha256:ack-expected","action":"blocked-work.acknowledge","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"}},"replaces_base_approval_records":true,"assert":{"approval_checks":["subject","delegated_principal","work_item_id","resolution_kind","digest","expected_state","expected_contract_version","policy_version","evaluation_time","single_use"]}} +{"step":"base-state","project_id":"fixture-project","requests":[],"work_items":[{"id":"wi-000","state":"completed","contract_version":1,"dependencies":[]},{"id":"wi-001","state":"blocked","contract_version":7,"dependencies":["wi-000"]}],"fake_gate_contexts":{"drafting-table":{"subject":"agent-001","role":"drafting-table","allowed_actions":["request.create","request.refine","request.link-change-set","request.link-build-work-item","request.get","request.query","work-item.get","work-item.query","blocked-work.query","lifecycle.preflight","blocked-work.submit-resolution","blocked-work.acknowledge"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"human-maintainer":{"subject":"human-001","role":"human-maintainer","allowed_actions":["request.update-priority"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"job-site":{"subject":"job-site-001","role":"job-site","allowed_actions":["claim","tests-pass","record-merge"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"materializer":{"subject":"materializer-001","role":"materializer","allowed_actions":["materialize","refresh-dependencies","revalidate","resolve-block"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"},"reconciler":{"subject":"reconciler-001","role":"reconciler","allowed_actions":["recover-lease","merge-conflict","merge-not-applied","record-merge"],"allowed_refs":["project:fixture-project"],"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z"}},"approval_records":{"refine-approval-001":{"approved_subject":"human-001","delegated_principal":"agent-001","request_id":"request-001","resolution_digest":"sha256:refine-001","action":"request.refine","status":"unused"},"resolution-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-001","action":"resolve-block","status":"unused"},"ack-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:ack-001","action":"acknowledge","status":"unused"}}} +{"step":"approval-validation-state","evaluation_time":"2026-09-18T12:00:00Z","approval_records":{"refine-approval-001":{"approved_subject":"human-001","delegated_principal":"agent-001","request_id":"request-001","resolution_digest":"sha256:refine-001","action":"request.refine","policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"resolution-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-001","action":"resolve-block","resolution_kind":"add-requirement","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"ack-approval-001":{"approved_subject":"human-001","delegated_principal":"agent-001","work_item_id":"wi-001","resolution_digest":"sha256:ack-001","action":"blocked-work.acknowledge","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"stale-state-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-stale-state-001","action":"resolve-block","resolution_kind":"impact-amendment","expected_state":"ready-for-building","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"stale-version-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-stale-version-001","action":"resolve-block","resolution_kind":"impact-amendment","expected_state":"blocked","expected_contract_version":6,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"expired-approval":{"approved_subject":"human-001","delegated_principal":"agent-001","work_item_id":"wi-001","resolution_digest":"sha256:expired-approval","action":"blocked-work.acknowledge","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-10T12:00:00Z","status":"expired"},"consumed-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:consumed-approval","action":"resolve-block","resolution_kind":"out-of-scope","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"consumed"},"cross-item-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-999","resolution_digest":"sha256:cross-item-approval","action":"resolve-block","resolution_kind":"out-of-scope","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"impact-approval-001":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:impact-approval-001","action":"resolve-block","resolution_kind":"impact-amendment","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"ack-wrong-digest-approval":{"approved_subject":"human-001","delegated_principal":"agent-001","work_item_id":"wi-001","resolution_digest":"sha256:ack-expected","action":"blocked-work.acknowledge","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"ack-mismatch-approval":{"approved_subject":"human-001","delegated_principal":"materializer-001","work_item_id":"wi-001","resolution_digest":"sha256:ack-mismatch","action":"blocked-work.acknowledge","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"},"resolution-mismatch-approval":{"approved_subject":"human-001","delegated_principal":"agent-001","work_item_id":"wi-001","resolution_digest":"sha256:resolution-mismatch","action":"resolve-block","resolution_kind":"add-requirement","expected_state":"blocked","expected_contract_version":7,"policy_version":"wms-policy/v1","expires_at":"2026-09-20T12:00:00Z","status":"unused"}},"replaces_base_approval_records":true,"assert":{"approval_checks":["subject","delegated_principal","work_item_id","resolution_kind","digest","expected_state","expected_contract_version","policy_version","evaluation_time","single_use"]}} {"step":"wms-tool-manifest","wms_tools":["request_create","request_refine","request_link_change_set","request_link_build_work_item","request_get","request_query","work_item_get","work_item_query","blocked_work_query","lifecycle_preflight","blocked_work_submit_resolution","blocked_work_acknowledge"],"assert":{"canonical_separator_mapping":{"request.create":"request_create","request.update-priority":"request_update_priority","blocked-work.submit-resolution":"blocked_work_submit_resolution"},"human_maintainer_operations":["request_update_priority"],"unknown_operations_rejected":true}} {"step":"operation-partition","drafting_table":["request.create","request.refine","request.link-change-set","request.link-build-work-item","request.get","request.query","work-item.get","work-item.query","blocked-work.query","lifecycle.preflight","blocked-work.submit-resolution","blocked-work.acknowledge"],"human_maintainer":["request.update-priority"],"adapter_api":["request.create","request.refine","request.update-priority","request.link-change-set","request.link-build-work-item","request.get","request.query","work-item.get","work-item.query","blocked-work.query","lifecycle.preflight","blocked-work.submit-resolution","blocked-work.acknowledge","materialize","refresh-dependencies","revalidate","resolve-block","claim","renew-lease","tests-pass","raise-spec-question","refresh-active","return-to-building","begin-merge","merge-conflict","merge-not-applied","record-merge","recover-lease","abandon","finding.create"],"job_site":["claim","renew-lease","tests-pass","raise-spec-question","refresh-active","return-to-building","begin-merge","merge-conflict","record-merge","finding.create"],"materializer":["materialize","refresh-dependencies","revalidate","resolve-block"],"reconciler":["recover-lease","merge-conflict","merge-not-applied","record-merge"],"assert":{"drafting_table_subset_of_adapter":true,"drafting_table_job_site_intersection":[],"unknown_operations":["schedule"],"backend_specific_fields":false}} {"step":"request-create","operation":"request.create","actor_context_ref":"drafting-table","idempotency_key":"request-create-001","payload":{"intent":"Add a status view","rationale":"The user needs visibility into active work","affected_interfaces":["drafting-table"],"affected_scopes":["status"]},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"request_id":"request-001","request_revision":1}} {"step":"request-create-replay","operation":"request.create","actor_context_ref":"drafting-table","idempotency_key":"request-create-001","payload":{"intent":"Add a status view","rationale":"The user needs visibility into active work","affected_interfaces":["drafting-table"],"affected_scopes":["status"]},"result":{"ok":true,"outcome":"replayed","mutation":"none","diagnostics":[],"request_id":"request-001","request_revision":1}} {"step":"request-create-conflict","operation":"request.create","actor_context_ref":"drafting-table","idempotency_key":"request-create-001","payload":{"intent":"Change a different behavior","rationale":"Conflicting request fingerprint","affected_interfaces":["drafting-table"],"affected_scopes":["status"]},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"IDEMPOTENCY_CONFLICT"}}} {"step":"request-refine-missing-approval","operation":"request.refine","actor_context_ref":"drafting-table","request_id":"request-001","expected_request_revision":1,"idempotency_key":"request-refine-missing-approval-001","payload":{"classification":"undefined","refinement_state":"ready-for-dimensioning","owner":"human-001","affected_scopes":["status","wms"]},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} -{"step":"request-refine","operation":"request.refine","actor_context_ref":"drafting-table","request_id":"request-001","expected_request_revision":1,"idempotency_key":"request-refine-001","payload":{"classification":"undefined","refinement_state":"ready-for-dimensioning","owner":"human-001","affected_scopes":["status","wms"],"human_approval_id":"refine-approval-001","approval_refinement_digest":"sha256:refine-001"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"request_id":"request-001","request_revision":2}} +{"step":"request-refine","operation":"request.refine","actor_context_ref":"drafting-table","request_id":"request-001","expected_request_revision":1,"idempotency_key":"request-refine-001","payload":{"classification":"undefined","refinement_state":"ready-for-dimensioning","owner":"human-001","affected_scopes":["status","wms"],"human_approval_id":"refine-approval-001","approval_refinement_digest":"sha256:refine-001"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"request_id":"request-001","request_revision":2,"approval_status":"consumed"}} +{"step":"request-refine-consumed-approval","operation":"request.refine","actor_context_ref":"drafting-table","request_id":"request-001","expected_request_revision":2,"idempotency_key":"request-refine-consumed-001","payload":{"classification":"undefined","refinement_state":"ready-for-dimensioning","owner":"human-001","affected_scopes":["status","wms"],"human_approval_id":"refine-approval-001","approval_refinement_digest":"sha256:refine-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"priority-agent-rejected","operation":"request.update-priority","actor_context_ref":"drafting-table","request_id":"request-001","expected_request_revision":2,"idempotency_key":"priority-agent-001","payload":{"business_priority":"high"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"priority-human-accepted","operation":"request.update-priority","actor_context_ref":"human-maintainer","request_id":"request-001","expected_request_revision":2,"idempotency_key":"priority-human-001","payload":{"business_priority":"high"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"request_revision":3,"audit_event":"priority-updated"}} {"step":"link-change-set","operation":"request.link-change-set","actor_context_ref":"drafting-table","request_id":"request-001","expected_request_revision":3,"idempotency_key":"link-change-set-001","payload":{"change_set_id":"CS-00001","target_revision":"proposed"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"request_revision":4,"link":{"change_set_id":"CS-00001"}}} @@ -15,16 +16,21 @@ {"step":"status-query","operation":"work-item.query","actor_context_ref":"drafting-table","payload":{"state":"blocked"},"result":{"ok":true,"outcome":"read","mutation":"none","diagnostics":[],"items":[{"id":"wi-001","state":"blocked","contract_version":7,"dependencies":["wi-000"]}]}} {"step":"blocked-query","operation":"blocked-work.query","actor_context_ref":"drafting-table","result":{"ok":true,"outcome":"read","mutation":"none","diagnostics":[],"items":[{"id":"wi-001","state":"blocked","contract_version":7,"reason_kind":"undefined-behavior","resolution_options":["add-requirement","out-of-scope","impact-amendment","defer","acknowledge"]}]}} {"step":"defer-session-local","operation":null,"adapter_call":false,"result":{"ok":true,"outcome":"read","mutation":"not-applicable","diagnostics":[],"session_action":"defer","work_item_state":"blocked"}} -{"step":"resolution-preflight","operation":"lifecycle.preflight","actor_context_ref":"drafting-table","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"payload":{"operation":"resolve-block","resolution_kind":"add-requirement","change_set_id":"CS-00001"},"result":{"ok":true,"outcome":"read","mutation":"none","diagnostics":[],"decision":{"outcome":"rejected","authority":"preflight","operation":"resolve-block","rule_version":"validation-rules/v1","before":{"state":"blocked","contract_version":7},"after":null,"rejection":{"code":"PRECONDITION_FAILED","failed_precondition":"dependency wi-000 is not completed"}}}} -{"step":"resolution-submit","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"resolution-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-001"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"submission":"accepted","approval_status":"unused","resolution_submission_id":"resolution-submission-001","resolution_submission_revision":1,"work_item_state":"blocked","contract_version":7}} -{"step":"resolution-supersede","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"impact-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-impact-supersede-001","payload":{"resolution_kind":"impact-amendment","change_set_id":"CS-00001","approval_resolution_digest":"sha256:impact-approval-001"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"submission":"accepted","prior_resolution_submission_id":"resolution-submission-001","prior_submission_status":"superseded","approval_status":"unused","resolution_submission_id":"resolution-submission-002","resolution_submission_revision":2,"work_item_state":"blocked","contract_version":7}} +{"step":"resolution-preflight","operation":"lifecycle.preflight","actor_context_ref":"drafting-table","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"payload":{"operation":"resolve-block","resolution_kind":"add-requirement","change_set_id":"CS-00001"},"result":{"ok":true,"outcome":"read","mutation":"none","diagnostics":[],"decision":{"outcome":"rejected","authority":"preflight","operation":"resolve-block","rule_version":"validation-rules/v1","before":{"state":"blocked","contract_version":7},"after":null,"rejection":{"code":"PRECONDITION_FAILED","failed_precondition":"planned dependency CS-00001 build work is not completed"}}}} +{"step":"resolution-submit","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"resolution-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-001"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"submission":"accepted","approval_status":"unused","resolution_submission_id":"resolution-submission-001","resolution_submission_revision":1,"planned_dependency":{"change_set_id":"CS-00001","status":"incomplete"},"work_item_state":"blocked","contract_version":7}} +{"step":"post-submit-work-item-query","operation":"work-item.query","actor_context_ref":"drafting-table","payload":{"state":"blocked"},"result":{"ok":true,"outcome":"read","mutation":"none","diagnostics":[],"items":[{"id":"wi-001","state":"blocked","contract_version":7,"dependencies":["wi-000"]}]}} +{"step":"resolve-block-incomplete-planned-dependency","operation":"resolve-block","actor_context_ref":"materializer","human_approval_id":"resolution-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolve-block-incomplete-planned-dependency-001","payload":{"resolution_kind":"add-requirement","resolution_submission_id":"resolution-submission-001","approval_resolution_digest":"sha256:resolution-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"approval_status":"unused","error":{"code":"PRECONDITION_FAILED","failed_precondition":"planned dependency CS-00001 build work is not completed"}}} +{"step":"post-resolve-block-incomplete-query","operation":"work-item.query","actor_context_ref":"drafting-table","payload":{"state":"blocked"},"result":{"ok":true,"outcome":"read","mutation":"none","diagnostics":[],"items":[{"id":"wi-001","state":"blocked","contract_version":7,"dependencies":["wi-000"]}]}} +{"step":"resolution-supersede","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"impact-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-impact-supersede-001","payload":{"resolution_kind":"impact-amendment","change_set_id":"CS-00001","approval_resolution_digest":"sha256:impact-approval-001"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"submission":"accepted","prior_resolution_submission_id":"resolution-submission-001","prior_submission_status":"superseded","prior_approval_status":"revoked","approval_status":"unused","resolution_submission_id":"resolution-submission-002","resolution_submission_revision":2,"work_item_state":"blocked","contract_version":7}} +{"step":"resolve-block-revoked-approval","operation":"resolve-block","actor_context_ref":"materializer","human_approval_id":"resolution-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolve-block-revoked-approval-001","payload":{"resolution_kind":"impact-amendment","resolution_submission_id":"resolution-submission-002","approval_resolution_digest":"sha256:resolution-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} +{"step":"resolve-block-non-active-submission","operation":"resolve-block","actor_context_ref":"materializer","human_approval_id":"impact-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolve-block-non-active-submission-001","payload":{"resolution_kind":"impact-amendment","resolution_submission_id":"resolution-submission-001","approval_resolution_digest":"sha256:impact-approval-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"resolution-missing-approval","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-missing-approval-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:missing-approval"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"resolution-forged-approval","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"unknown-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-forged-approval-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:forged-approval"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"acknowledge-submit","operation":"blocked-work.acknowledge","actor_context_ref":"drafting-table","human_approval_id":"ack-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"ack-001","payload":{"reason":"control-plane informational condition","approval_resolution_digest":"sha256:ack-001"},"result":{"ok":true,"outcome":"applied","mutation":"applied","diagnostics":[],"approval_status":"consumed","resolution_submission_id":"acknowledgement-001","resolution_submission_revision":1,"work_item_state":"blocked","contract_version":7}} {"step":"acknowledge-expired-approval","operation":"blocked-work.acknowledge","actor_context_ref":"drafting-table","human_approval_id":"expired-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"ack-expired-001","payload":{"reason":"expired informational condition","approval_resolution_digest":"sha256:expired-approval"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"resolution-stale-state","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"stale-state-approval","work_item_id":"wi-001","expected_state":"ready-for-building","expected_contract_version":7,"idempotency_key":"resolution-stale-state-001","payload":{"resolution_kind":"impact-amendment","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-stale-state-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"STALE_STATE","retry":"refresh"}}} {"step":"resolution-stale-version","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"stale-version-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":6,"idempotency_key":"resolution-stale-version-001","payload":{"resolution_kind":"impact-amendment","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-stale-version-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"STALE_CONTRACT_VERSION","retry":"refresh"}}} -{"step":"resolution-replay","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"resolution-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-001"},"result":{"ok":true,"outcome":"replayed","mutation":"none","diagnostics":[],"idempotency":"replayed","submission_status":"superseded","resolution_submission_id":"resolution-submission-001","resolution_submission_revision":1,"work_item_state":"blocked","contract_version":7}} +{"step":"resolution-replay","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"resolution-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-001"},"result":{"ok":true,"outcome":"replayed","mutation":"none","diagnostics":[],"submission":"accepted","approval_status":"unused","resolution_submission_id":"resolution-submission-001","resolution_submission_revision":1,"planned_dependency":{"change_set_id":"CS-00001","status":"incomplete"},"work_item_state":"blocked","contract_version":7}} {"step":"job-site-claim-rejected","operation":"claim","actor_context_ref":"drafting-table","work_item_id":"wi-001","expected_state":"ready-for-building","expected_contract_version":7,"idempotency_key":"claim-drafting-table-001","result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"materialization-rejected","operation":"materialize","actor_context_ref":"drafting-table","idempotency_key":"materialize-drafting-table-001","payload":{"materialization_key":"fixture-materialization-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"forged-context-rejected","operation":"materialize","actor_context_ref":"drafting-table","untrusted_actor":{"role":"materializer","allowed_actions":["materialize"],"human_approval_id":"resolution-approval-001"},"idempotency_key":"forged-context-001","payload":{"materialization_key":"fixture-materialization-002"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} @@ -34,6 +40,9 @@ {"step":"job-site-finding-rejected","operation":"finding.create","actor_context_ref":"drafting-table","work_item_id":"wi-001","idempotency_key":"finding-drafting-table-001","result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"resolution-cross-item-approval","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"cross-item-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-cross-item-001","payload":{"resolution_kind":"out-of-scope","approval_resolution_digest":"sha256:cross-item-approval"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"resolution-consumed-approval","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"consumed-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-consumed-001","payload":{"resolution_kind":"out-of-scope","approval_resolution_digest":"sha256:consumed-approval"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} +{"step":"resolution-revoked-approval","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"resolution-approval-001","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-revoked-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} +{"step":"resolution-principal-mismatch","operation":"blocked-work.submit-resolution","actor_context_ref":"drafting-table","human_approval_id":"resolution-mismatch-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolution-mismatch-001","payload":{"resolution_kind":"add-requirement","change_set_id":"CS-00001","approval_resolution_digest":"sha256:resolution-mismatch"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} +{"step":"acknowledge-principal-mismatch","operation":"blocked-work.acknowledge","actor_context_ref":"drafting-table","human_approval_id":"ack-mismatch-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"ack-mismatch-001","payload":{"reason":"delegated principal mismatch","approval_resolution_digest":"sha256:ack-mismatch"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"acknowledge-digest-mismatch","operation":"blocked-work.acknowledge","actor_context_ref":"drafting-table","human_approval_id":"ack-wrong-digest-approval","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"ack-wrong-digest-001","payload":{"reason":"digest mismatch","approval_resolution_digest":"sha256:ack-other"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"schedule-drafting-table-rejected","operation":"schedule","actor_context_ref":"drafting-table","idempotency_key":"schedule-drafting-table-001","payload":{"work_item_id":"wi-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} {"step":"resolve-block-drafting-table-rejected","operation":"resolve-block","actor_context_ref":"drafting-table","work_item_id":"wi-001","expected_state":"blocked","expected_contract_version":7,"idempotency_key":"resolve-block-drafting-table-001","payload":{"resolution_kind":"impact-amendment","approval_resolution_digest":"sha256:impact-approval-001"},"result":{"ok":false,"outcome":"rejected","mutation":"none","diagnostics":[],"error":{"code":"UNAUTHORIZED_ACTION"}}} diff --git a/docs/architecture/git-integration.md b/docs/architecture/git-integration.md index 9577c6c..aacee84 100644 --- a/docs/architecture/git-integration.md +++ b/docs/architecture/git-integration.md @@ -606,7 +606,7 @@ change_set_id + NUL + merge_commit)`; the same registration retry therefore reuses both keys and the same request fingerprint. Repeating it with the same merge commit returns the prior result. If the merge succeeds and the registration write -fails, the retry is the same registration call - never a second merge. A +fails, the retry is the same registration call — never a second merge. A registration that arrives with a different merge commit for the same change set is rejected for reconciliation. diff --git a/docs/architecture/user-interaction-flow.md b/docs/architecture/user-interaction-flow.md index 326514a..ee71548 100644 --- a/docs/architecture/user-interaction-flow.md +++ b/docs/architecture/user-interaction-flow.md @@ -922,15 +922,18 @@ flowchart LR undefined behavior, it is surfaced to the user as an agent-suggested requirement. This **blocks the work item** until the user either adds a requirement (which enters Dimensioning as a linked change set) or -approves an explicit out-of-scope specification declaration. The blocked -work item depends on that change set's build work if implementation is -required. -After the dependency completes, the control plane reruns impact/refresh -eligibility and appends a new contract version. The item returns to -`ready-for-building` only if those checks pass. A Job Site must obtain a -new fenced claim before refreshing its branch from main and rerunning all -gates. Any newly discovered obligation requires a reviewed impact -amendment before dispatch. +approves an explicit out-of-scope specification declaration. The +blocked-work resolution submission records a planned dependency on +that change set's build work if implementation is required; the work +item remains `blocked` and does not receive a same-state dependency +write. After the planned dependency completes, Materializer +`resolve-block` observes it during full refresh and appends a new +contract version only on the `blocked -> ready-for-building` +transition. The item returns to `ready-for-building` only if those +checks pass. A Job Site must obtain a new fenced claim before +refreshing its branch from main and rerunning all gates. Any newly +discovered obligation requires a reviewed impact amendment before +dispatch. --- diff --git a/docs/architecture/validation-rules.md b/docs/architecture/validation-rules.md index 9ceb45b..3871dd9 100644 --- a/docs/architecture/validation-rules.md +++ b/docs/architecture/validation-rules.md @@ -212,10 +212,12 @@ floor: For blocked-work resolution, the Drafting Table submits the user's reviewed resolution and `human_approval_id` without changing lifecycle -state. The Materializer verifies that approval, refreshes the authoritative -record, and performs `resolve-block` through the WMS boundary. This keeps -the conversational surface useful without granting it a direct unblock -authority. +state. The Materializer names the currently-active +`resolution_submission_id`, verifies that submission's approval, refreshes +the authoritative record (including any planned dependency recorded on +that submission), and performs `resolve-block` through the WMS boundary. +This keeps the conversational surface useful without granting it a direct +unblock authority. There is no same-state `blocked` mutation. The approval binding is checked and consumed in the same transaction as `resolve-block`. A mismatched, expired, revoked, or already-consumed @@ -233,10 +235,13 @@ The requested operation must be in both the role's exclusive command family and Gate-issued `allowed_actions`. Payload refs must be a subset of `allowed_refs`. A caller cannot select or downgrade `policy_version`. Every authoritative `resolve-block` request must also contain -`human_approval_id` and `approval_resolution_digest`. Their binding and -single-use consumption occur after an idempotency replay check, so an exact -lost-response retry can return the original result without consuming the -approval twice. +`human_approval_id`, `approval_resolution_digest`, and the currently-active +`resolution_submission_id`. The named submission must be the work item's +active nonterminal lifecycle resolution, and the approval must belong to +that submission. Their binding and single-use consumption occur after an +idempotency replay check, so an exact lost-response retry can return the +original result without consuming the approval twice. A superseded +submission's revoked approval cannot satisfy this binding. The authorization context contains no downstream credential. In hosted deployments, the Bridge/Gate obtains a scoped credential only after the @@ -374,7 +379,7 @@ into an arbitrary update. | `materialize` | `initial` at version 0 | `waiting`, `ready-for-building`, `blocked`, or `omitted` | A unique `materialization_key` and complete contract are supplied; the result follows dependency, impact, readiness, and implementation-effect checks. | | `refresh-dependencies` | `waiting` | `ready-for-building` | All dependencies are completed and the full pre-claim refresh passes. | | `revalidate` | `waiting` or `ready-for-building` | `blocked` | Refresh finds unresolved impact, specification, policy, or reconciliation work. | -| `resolve-block` | `blocked` | `ready-for-building` | Every authoritative request includes a Gate-bound `human_approval_id` and matching `approval_resolution_digest`; the approval is consumed only after a full refresh passes. Conversation alone cannot perform this transition. | +| `resolve-block` | `blocked` | `ready-for-building` | Every authoritative request includes a Gate-bound `human_approval_id`, matching `approval_resolution_digest`, and the currently-active `resolution_submission_id`; the approval is consumed only after a full refresh passes, including any planned dependency recorded on that submission. Conversation alone cannot perform this transition. There is no same-state `blocked` mutation. | | `claim` | `ready-for-building` | `building` | Atomic expected-state/version check passes; a new owner, lease, and fencing token are recorded. | | `renew-lease` | `building` or `inspecting` | Same state | Current owner presents the current fencing token and an unexpired authorization context. | | `tests-pass` | `building` | `inspecting` | Current owner presents the expected state/version/fence and the Building gate has passed. | @@ -423,6 +428,13 @@ reconciliation is unresolved, `revalidate` returns `blocked`. A complete contract with no implementation effect returns `omitted` and creates no executable work item. +`resolve-block` uses the same full refresh. For an `add-requirement` +submission, that refresh also requires the planned dependency recorded on +the named `resolution_submission_id` to be completed. The planned +dependency is not copied onto the work item before the transition; an +incomplete planned dependency returns `PRECONDITION_FAILED` and leaves +the item `blocked`. + `refresh-active` is the pre-merge revalidation path for a Job Site that still holds an active lease. A passing refresh records a new contract version, incorporates the compatible source/policy state, invalidates the @@ -525,8 +537,9 @@ reports the first failed check using a deterministic check order: 2. role-family, `allowed_actions`, and payload-ref subset checks; 3. idempotency-key replay or conflict; 4. `materialize` create-or-return by `materialization_key`; -5. `resolve-block` approval binding and single-use consumption, when that - operation is requested; +5. `resolve-block` active `resolution_submission_id` and approval binding, + when that operation is requested; single-use consumption is recorded + only if the command is later allowed; 6. terminal-state check; 7. active-owner contention for `claim` (`DUPLICATE_CLAIM`); 8. expected state; @@ -606,7 +619,7 @@ authorities: | State | Owner | Validation Rules responsibility | | --- | --- | --- | -| Request backlog, request revisions, and blocked-resolution submissions | WMS Adapter/backend | Validate request-namespace preconditions and supply durable submission records; consume only the selected approval and lifecycle fields during authoritative `resolve-block`. | +| Request backlog, request revisions, and blocked-resolution submissions | WMS Adapter/backend | Validate request-namespace preconditions and supply durable submission records; consume only the currently-active submission's approval and lifecycle fields during authoritative `resolve-block`. | | Work-item state, contract versions, leases, and fencing tokens | WMS Adapter and its claim coordinator | Validate all reads used for a mutation and require atomic compare-and-swap semantics. | | Idempotency results, materialization reservations, and lifecycle audit events | WMS Adapter / external coordinator | Ensure retries return the original result and never duplicate a mutation, including `omitted` outcomes. | | Specification records and impact dispositions | Git through `ears-manager` | Consume successful validation/check evidence; do not parse or mutate records. | @@ -655,7 +668,7 @@ rejection or replay, plus one audit event for each accepted mutation. | `VR-020` | `building` or `inspecting` owner runs `refresh-active` with the current fence and compatible latest main. | Contract version increments, the old fence is rejected, and a new fence is issued for continued `building`. | | `VR-021` | An `inspecting` owner returns to work after an in-contract defect. | `return-to-building` issues a new fence; the old fence cannot mutate the new attempt. | | `VR-022` | Drafting Table submits a blocked resolution with missing or forged `human_approval_id`. | Rejected with `UNAUTHORIZED_ACTION`; the item remains `blocked`. | -| `VR-023` | Drafting Table submits a valid reviewed resolution; Materializer performs `resolve-block`. | Approval is verified, `blocked -> ready-for-building` succeeds, and the Drafting Table itself performs no lifecycle mutation. | +| `VR-023` | Drafting Table submits a valid reviewed resolution; Materializer performs `resolve-block` naming the active submission whose refresh preconditions pass. | Approval is verified and consumed, `blocked -> ready-for-building` succeeds, and the Drafting Table itself performs no lifecycle mutation. | | `VR-024` | Materialize at `initial` version 0 with a new `materialization_key`, then repeat with the same source contract and a new command key. | The first result creates version 1; the second returns the existing item without a duplicate. | | `VR-025` | Reuse a `materialization_key` with a different source commit or contract payload. | Rejected with `IDEMPOTENCY_CONFLICT`; no second item is created. | | `VR-026` | A reconciler runs `recover-lease` for an expired/missing-fence `building` item whose `current_record` has matching `lease-recovered`/`none` evidence. | Allowed; the item returns to `ready-for-building` without a fencing-token rejection, and the old lease cannot write. | @@ -664,7 +677,7 @@ rejection or replay, plus one audit event for each accepted mutation. | `VR-029` | A request has empty or wildcard allowlists, or attempts to select a weaker `policy_version`. | Rejected with `UNAUTHORIZED_ACTION`; no mutation. | | `VR-030` | A valid `human_approval_id` bound to another work item or resolution is used for `resolve-block`. | Rejected with `UNAUTHORIZED_ACTION`; the approval is not consumed and the item remains `blocked`. | | `VR-031` | A new `resolve-block` request uses an approval that is expired, revoked, already consumed, or has a mismatched resolution digest. | Rejected with `UNAUTHORIZED_ACTION`; no lifecycle mutation occurs. | -| `VR-032` | A role-valid Materializer requests `resolve-block` without `human_approval_id` or `approval_resolution_digest`. | Rejected with `UNAUTHORIZED_ACTION`; the item remains `blocked`. | +| `VR-032` | A role-valid Materializer requests `resolve-block` without `human_approval_id`, `approval_resolution_digest`, or the currently-active `resolution_submission_id`. | Rejected with `UNAUTHORIZED_ACTION`; the item remains `blocked`. | | `VR-033` | A successful `resolve-block` response is lost; the Materializer retries the exact request with the same key after the approval was consumed. | The original allowed result is replayed before approval consumption is checked again; no second transition occurs. | | `VR-034` | A trusted `reconciler` handles a merge conflict without a Job Site fence while `current_record` contains matching `conflict`/non-merged evidence. | `merging -> ready-for-building` succeeds without issuing a fence to the reconciler; caller proof fields are ignored. | | `VR-035` | Git merge is recorded, but the WMS record remains `merging` because the completion write did not commit; `current_record` contains a matching merge envelope and a `reconciler` calls `record-merge`. | Completion succeeds without a live Job Site fence; an identical reconciler retry returns `replayed: true`. | @@ -675,6 +688,8 @@ rejection or replay, plus one audit event for each accepted mutation. | `VR-040` | `recover-lease` or `abandon` is requested with missing, malformed, or mismatched reconciliation evidence on `current_record`. | Rejected with `PRECONDITION_FAILED`; no mutation occurs even when the caller has the correct role. | | `VR-041` | A claim uses `expected_state: blocked` against a current blocked item with no active owner. | Rejected with `INVALID_TRANSITION`; the expected state is current but the item must be resolved before claiming. | | `VR-042` | A Materializer uses a valid approval whose delegated principal or approved human subject does not match the trusted authorization context. | Rejected with `UNAUTHORIZED_ACTION`; the approval is not consumed and the item remains `blocked`. | +| `VR-043` | A role-valid Materializer requests `resolve-block` with a `human_approval_id` belonging to a superseded resolution submission, or names a `resolution_submission_id` that is not currently active. | Rejected with `UNAUTHORIZED_ACTION`; no lifecycle mutation occurs. | +| `VR-044` | A role-valid Materializer requests `resolve-block` naming the active `add-requirement` submission whose planned dependency is not completed. | Rejected with `PRECONDITION_FAILED`; the item remains `blocked` and the approval is not consumed. | The matrix covers the required stale-write, duplicate-claim, unauthorized-mutation, and idempotent-retry cases. Backend adapter tests