Skip to content

Commit 9ffbdb1

Browse files
committed
Require review-scope accounting before broad audit completion
Broad review and review-and-fix sessions need a runtime-owned scope ledger so a single closed finding cannot silently stand in for a full declared audit. This change adds structured scope declarations, worker/final-review ledgers, completion and recovery gates, OpenCode/prompt contract exposure, and regression coverage while preserving narrow implementation-mode one-file completions. Constraint: Add audit-scope completion accounting without requiring edits to every declared target file Constraint: Keep final-review reviewedSurfaces artifact-derived rather than turning it into whole-audit scope Constraint: Keep zod aligned with @opencode-ai/plugin; no dependency-version changes Rejected: Treat broad review completion as mutation-count coverage | legitimate audits may fix one file while accounting for reviewed, deferred, out-of-scope, or blocked targets Rejected: Infer audit breadth from natural-language goals at completion time | structured reviewScope/fileTargets give the runtime an auditable source of truth Rejected: Let failed historical attempts satisfy final finding-closed scope entries | rejected attempts can contain unsupported closure refs and must not become completion evidence Confidence: high Scope-risk: moderate Directive: For review and review_and_fix plans, declare scope explicitly and close it with reviewScopeLedger; use deferred, out_of_scope, or blocked for honest residual-risk accounting rather than narrowing silently Tested: bun run check; prior fresh bun run lint, bun run typecheck, and bun test passed with 543 pass, 0 fail, 1 snapshot Not-tested: Live GitHub-hosted CI/release workflow for tag v2.0.11 before push
1 parent 9c56243 commit 9ffbdb1

32 files changed

Lines changed: 1495 additions & 29 deletions

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,29 @@
22

33
## [Unreleased]
44

5+
## [2.0.11] - 2026-05-06
6+
7+
Require review-scope accounting before broad audit completion
8+
9+
Flow 2.0.11 hardens broad review and review-and-fix workflows with a runtime-owned review scope ledger. Review-shaped plans must now declare an effective scope through `reviewScope` or `fileTargets`, and final completion cannot reduce a full audit request to one closed finding unless every declared target is accounted as reviewed with no findings, finding closed, deferred, out of scope, or blocked with evidence and residual risk.
10+
11+
The release keeps artifact-derived final-review coverage separate from audit-scope closure. `reviewScopeLedger` is carried through worker results, execution history, and final reviewer approvals, while implementation-mode one-file workflows remain valid without the new ledger. Historical completed feature closures can satisfy final review-and-fix scope where appropriate, but failed historical attempts cannot be cited as completion evidence.
12+
13+
The OpenCode adapter, descriptors, prompt contracts, recovery guidance, generated completion-gate projections, architecture notes, and prompt snapshots now surface the new scope-accounting contract. Regression coverage models broad one-file fixes, multi-feature historical closures, failed-attempt evidence rejection, plan scope requirements, effective scope-id collisions, and the preserved implementation-mode path.
14+
15+
Constraint: Add audit-scope completion accounting without requiring edits to every declared target file
16+
Constraint: Keep final-review `reviewedSurfaces` artifact-derived; do not overload it into a whole-audit ledger
17+
Constraint: Keep `zod` aligned with `@opencode-ai/plugin`; no dependency-version changes in this patch
18+
Rejected: Treat broad review completion as mutation-count coverage | legitimate audits may fix one file while still reviewing or deferring the rest of the declared scope
19+
Rejected: Infer audit breadth from natural-language goals at completion time | structured `reviewScope` / `fileTargets` gives the runtime an auditable source of truth
20+
Rejected: Let failed historical attempts satisfy final reviewer `finding_closed` scope entries | rejected attempts can contain unsupported closure refs and must not become completion evidence
21+
Confidence: high
22+
Scope-risk: moderate
23+
Reversibility: clean
24+
Directive: For `review` and `review_and_fix` plans, declare scope explicitly and close it with `reviewScopeLedger`; use `deferred`, `out_of_scope`, or `blocked` for honest residual-risk accounting rather than narrowing silently
25+
Tested: `bun run lint`; `bun run typecheck`; `bun test` (543 pass, 0 fail, 1 snapshot, 17064 expect calls); Oracle review follow-ups fixed and revalidated with targeted completion, final-review, prompt, plan, schema, protocol, recovery, and snapshot suites
26+
Not-tested: Live GitHub-hosted CI/release workflow runs for tag `v2.0.11` before push
27+
528
## [2.0.10] - 2026-05-06
629

730
Require behavior-grounded final review approvals

docs/architecture/role-protocol-projections.md

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,38 @@ The table below is mechanically projected from `src/runtime/transitions/completi
4545
| default | final | 5 | final_review_passed | - | failing_final_review | finalReviewFailureMessage | completion.gates.required_order, recovery.next_action.binding |
4646
| default | final | 6 | final_review_payload | final_review_payload | missing_final_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
4747
| default | final | 7 | reviewer_decision | final_reviewer_decision | missing_reviewer_decision | finalReviewerDecisionFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
48+
| review | feature | 1 | validation_evidence | - | missing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
49+
| review | feature | 2 | validation_passed | - | failing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
50+
| review | feature | 3 | review_scope_accounting | review_scope_ledger | missing_review_scope_accounting | reviewScopeLedgerFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
51+
| review | feature | 4 | reviewer_decision | feature_reviewer_decision | missing_reviewer_decision | finalReviewerDecisionFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
52+
| review | feature | 5 | validation_scope | targeted_validation_result | missing_validation_scope | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
53+
| review | feature | 6 | feature_review | - | failing_feature_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
54+
| review | feature | 7 | final_review_passed | - | failing_final_review | finalReviewFailureMessage | completion.gates.required_order, recovery.next_action.binding |
55+
| review | final | 1 | validation_evidence | - | missing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
56+
| review | final | 2 | validation_passed | - | failing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
57+
| review | final | 3 | review_scope_accounting | review_scope_ledger | missing_review_scope_accounting | reviewScopeLedgerFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
58+
| review | final | 4 | validation_scope | broad_validation_result | missing_validation_scope | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
59+
| review | final | 5 | feature_review | - | failing_feature_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
60+
| review | final | 6 | final_review_passed | - | failing_final_review | finalReviewFailureMessage | completion.gates.required_order, recovery.next_action.binding |
61+
| review | final | 7 | final_review_payload | final_review_payload | missing_final_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
62+
| review | final | 8 | reviewer_decision | final_reviewer_decision | missing_reviewer_decision | finalReviewerDecisionFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
4863
| review_and_fix | feature | 1 | validation_evidence | - | missing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
4964
| review_and_fix | feature | 2 | validation_passed | - | failing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
5065
| review_and_fix | feature | 3 | review_finding_closure | review_finding_closure_ledger | missing_review_closure | reviewFindingClosureFailureMessage | completion.gates.required_order, recovery.next_action.binding |
51-
| review_and_fix | feature | 4 | reviewer_decision | feature_reviewer_decision | missing_reviewer_decision | finalReviewerDecisionFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
52-
| review_and_fix | feature | 5 | validation_scope | targeted_validation_result | missing_validation_scope | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
53-
| review_and_fix | feature | 6 | feature_review | - | failing_feature_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
54-
| review_and_fix | feature | 7 | final_review_passed | - | failing_final_review | finalReviewFailureMessage | completion.gates.required_order, recovery.next_action.binding |
66+
| review_and_fix | feature | 4 | review_scope_accounting | review_scope_ledger | missing_review_scope_accounting | reviewScopeLedgerFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
67+
| review_and_fix | feature | 5 | reviewer_decision | feature_reviewer_decision | missing_reviewer_decision | finalReviewerDecisionFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
68+
| review_and_fix | feature | 6 | validation_scope | targeted_validation_result | missing_validation_scope | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
69+
| review_and_fix | feature | 7 | feature_review | - | failing_feature_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
70+
| review_and_fix | feature | 8 | final_review_passed | - | failing_final_review | finalReviewFailureMessage | completion.gates.required_order, recovery.next_action.binding |
5571
| review_and_fix | final | 1 | validation_evidence | - | missing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
5672
| review_and_fix | final | 2 | validation_passed | - | failing_validation | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
5773
| review_and_fix | final | 3 | review_finding_closure | review_finding_closure_ledger | missing_review_closure | reviewFindingClosureFailureMessage | completion.gates.required_order, recovery.next_action.binding |
58-
| review_and_fix | final | 4 | validation_scope | broad_validation_result | missing_validation_scope | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
59-
| review_and_fix | final | 5 | feature_review | - | failing_feature_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
60-
| review_and_fix | final | 6 | final_review_passed | - | failing_final_review | finalReviewFailureMessage | completion.gates.required_order, recovery.next_action.binding |
61-
| review_and_fix | final | 7 | final_review_payload | final_review_payload | missing_final_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
62-
| review_and_fix | final | 8 | reviewer_decision | final_reviewer_decision | missing_reviewer_decision | finalReviewerDecisionFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
74+
| review_and_fix | final | 4 | review_scope_accounting | review_scope_ledger | missing_review_scope_accounting | reviewScopeLedgerFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
75+
| review_and_fix | final | 5 | validation_scope | broad_validation_result | missing_validation_scope | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
76+
| review_and_fix | final | 6 | feature_review | - | failing_feature_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
77+
| review_and_fix | final | 7 | final_review_passed | - | failing_final_review | finalReviewFailureMessage | completion.gates.required_order, recovery.next_action.binding |
78+
| review_and_fix | final | 8 | final_review_payload | final_review_payload | missing_final_review | validateNormalizedSuccessfulCompletion | completion.gates.required_order, recovery.next_action.binding |
79+
| review_and_fix | final | 9 | reviewer_decision | final_reviewer_decision | missing_reviewer_decision | finalReviewerDecisionFailureMessage | completion.gates.required_order, review.scope.payload_binding, recovery.next_action.binding |
6380
<!-- completion-gate-doc-table:end -->
6481

6582
## Semantic parity anchors

docs/releases/v2.0.11.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# v2.0.11
2+
3+
Require review-scope accounting before broad audit completion
4+
5+
Flow 2.0.11 hardens broad review and review-and-fix workflows with a runtime-owned review scope ledger. Review-shaped plans must now declare an effective scope through `reviewScope` or `fileTargets`, and final completion cannot reduce a full audit request to one closed finding unless every declared target is accounted as reviewed with no findings, finding closed, deferred, out of scope, or blocked with evidence and residual risk.
6+
7+
The release keeps artifact-derived final-review coverage separate from audit-scope closure. `reviewScopeLedger` is carried through worker results, execution history, and final reviewer approvals, while implementation-mode one-file workflows remain valid without the new ledger. Historical completed feature closures can satisfy final review-and-fix scope where appropriate, but failed historical attempts cannot be cited as completion evidence.
8+
9+
The OpenCode adapter, descriptors, prompt contracts, recovery guidance, generated completion-gate projections, architecture notes, and prompt snapshots now surface the new scope-accounting contract. Regression coverage models broad one-file fixes, multi-feature historical closures, failed-attempt evidence rejection, plan scope requirements, effective scope-id collisions, and the preserved implementation-mode path.
10+
11+
Constraint: Add audit-scope completion accounting without requiring edits to every declared target file
12+
Constraint: Keep final-review `reviewedSurfaces` artifact-derived; do not overload it into a whole-audit ledger
13+
Constraint: Keep `zod` aligned with `@opencode-ai/plugin`; no dependency-version changes in this patch
14+
Rejected: Treat broad review completion as mutation-count coverage | legitimate audits may fix one file while still reviewing or deferring the rest of the declared scope
15+
Rejected: Infer audit breadth from natural-language goals at completion time | structured `reviewScope` / `fileTargets` gives the runtime an auditable source of truth
16+
Rejected: Let failed historical attempts satisfy final reviewer `finding_closed` scope entries | rejected attempts can contain unsupported closure refs and must not become completion evidence
17+
Confidence: high
18+
Scope-risk: moderate
19+
Reversibility: clean
20+
Directive: For `review` and `review_and_fix` plans, declare scope explicitly and close it with `reviewScopeLedger`; use `deferred`, `out_of_scope`, or `blocked` for honest residual-risk accounting rather than narrowing silently
21+
Tested: `bun run lint`; `bun run typecheck`; `bun test` (543 pass, 0 fail, 1 snapshot, 17064 expect calls); Oracle review follow-ups fixed and revalidated with targeted completion, final-review, prompt, plan, schema, protocol, recovery, and snapshot suites
22+
Not-tested: Live GitHub-hosted CI/release workflow runs for tag `v2.0.11` before push
23+

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-plugin-flow",
3-
"version": "2.0.10",
3+
"version": "2.0.11",
44
"description": "Stateful planning and execution workflow plugin for OpenCode",
55
"type": "module",
66
"main": "dist/index.js",

src/adapters/opencode/tool-surface/descriptor-guidance.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const FLOW_PROMPT_GUIDANCE_BY_ID = {
4343
- Returns the canonical runtime response describing the active feature or why nothing is runnable.`,
4444
flow_run_complete_feature: `## Use when
4545
- Use only after the required validation for the current path is complete: targeted validation plus feature review for normal features, or broad validation plus the final review required by deliveryPolicy.finalReviewPolicy (detailed cross-feature by default) for the completion path.
46+
- For review/review_and_fix completion paths, include reviewScopeLedger accounting for every declared review scope target/domain.
4647
- Provide the full worker result fields directly as this tool's arguments.
4748
4849
## Avoid when
@@ -61,6 +62,7 @@ export const FLOW_PROMPT_GUIDANCE_BY_ID = {
6162
- Returns the canonical runtime response for the feature-level approval gate.`,
6263
flow_review_record_final: `## Use when
6364
- Use to persist the final reviewer decision required by deliveryPolicy.finalReviewPolicy on the final completion path.
65+
- For review/review_and_fix approvals, include reviewScopeLedger accounting for every declared review scope target/domain.
6466
- Provide the full reviewer decision fields directly as this tool's arguments.
6567
6668
## Avoid when

src/adapters/opencode/tool-surface/descriptors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ export const FLOW_SURFACE_DESCRIPTORS: readonly FlowSurfaceDescriptor[] = [
373373
],
374374
policyOwners: [
375375
"src/runtime/application/session-actions.ts",
376+
"src/runtime/domain/review-scope-accounting.ts",
376377
"src/runtime/transitions/execution-completion-validation.ts",
377378
"src/core/workflow/rejections.ts",
378379
],
@@ -457,6 +458,7 @@ export const FLOW_SURFACE_DESCRIPTORS: readonly FlowSurfaceDescriptor[] = [
457458
invariantIds: ["review.scope.payload_binding"],
458459
policyOwners: [
459460
"src/runtime/application/session-actions.ts",
461+
"src/runtime/domain/review-scope-accounting.ts",
460462
"src/runtime/transitions/execution-completion-validation.ts",
461463
"src/core/workflow/reducer.ts",
462464
],

src/adapters/opencode/tool-surface/runtime-tools/review-tools.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,16 @@ export function createReviewRuntimeTools() {
7575
(check) => check.result === "gap_recorded",
7676
).length,
7777
validationCoverageCount: input.validationCoverage?.length ?? 0,
78+
reviewScopeLedgerCount: input.reviewScopeLedger?.length ?? 0,
79+
reviewScopeLedgerStatusCounts: (
80+
input.reviewScopeLedger ?? []
81+
).reduce(
82+
(acc, entry) => {
83+
acc[entry.status] = (acc[entry.status] ?? 0) + 1;
84+
return acc;
85+
},
86+
{} as Record<string, number>,
87+
),
7888
},
7989
});
8090
return executeGuardedSessionMutation(

0 commit comments

Comments
 (0)