aw-spec-cli 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/assets/.appweaver/ORCH-DIALECT.md +8 -3
  2. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +22 -1
  3. package/assets/.appweaver/orchestrator/orchestrator.py +65 -3
  4. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +6 -6
  5. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +107 -48
  6. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +17 -9
  7. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +265 -20
  8. package/assets/.appweaver/orchestrator/workflows/aw-spec-constitution.orch.yaml +4 -4
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +89 -15
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-foundation.orch.yaml +140 -36
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +354 -52
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +3 -3
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +47 -23
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +1 -1
  15. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +651 -76
  16. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +600 -59
  17. package/assets/.appweaver/scripts/python/aw-crstate.py +194 -0
  18. package/assets/.appweaver/scripts/python/aw-foundation.py +151 -0
  19. package/assets/.appweaver/scripts/python/aw-payload-parity.py +3 -0
  20. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +423 -0
  21. package/assets/.appweaver/scripts/python/aw-stack.py +251 -28
  22. package/assets/.appweaver/scripts/python/aw-testlog.py +148 -0
  23. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +1 -0
  24. package/assets/.appweaver/stack-profiles/java-spring.json +1 -0
  25. package/assets/.appweaver/stack-profiles/node-express.json +1 -0
  26. package/assets/.appweaver/stack-profiles/python-fastapi.json +1 -0
  27. package/assets/.appweaver/templates/constitution-domain-template.md +17 -2
  28. package/assets/guardrails/projects/dotnet-default-project/domain-rule-bindings.md +10 -6
  29. package/assets/guardrails/projects/dotnet-default-project/tech-rule-bindings.md +4 -0
  30. package/assets/guardrails/technology/dotnet-webapi/auditing.md +3 -1
  31. package/assets/guardrails/technology/dotnet-webapi/coding-standards.md +5 -3
  32. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +5 -1
  33. package/assets/guardrails/technology/dotnet-webapi/domain-driven-design.md +13 -5
  34. package/assets/guardrails/technology/dotnet-webapi/examples/cqrs.md +2 -0
  35. package/assets/guardrails/technology/dotnet-webapi/examples/domain-driven-design.md +6 -2
  36. package/assets/guardrails/technology/dotnet-webapi/framework-api-map.md +70 -0
  37. package/assets/guardrails/technology/dotnet-webapi/technology-constitution.md +3 -0
  38. package/assets/guardrails/technology/dotnet-webapi/testing.md +10 -0
  39. package/assets/guardrails/technology/nextjs/ui-constitution.md +8 -1
  40. package/package.json +1 -1
  41. package/assets/.appweaver/orchestrator/.state/smoke.state.json +0 -32
@@ -47,9 +47,10 @@
47
47
  # about the order the operator ran things in: the lane gates test that the plan FILE EXISTS
48
48
  # and nothing more, so `plan` → edit spec.md → `implement` generated at full length from a
49
49
  # plan nobody had held against the spec it now contradicts. analyze-plan leaves a receipt of
50
- # the bytes it proved and plan-gate-check reads it here, on the bootstrap path, before any
51
- # lane generates. A feature with no receipt has simply never been gated since the receipt
52
- # existed — reported, never refused. One that HAS a receipt and no longer matches it stops.
50
+ # the bytes it checked and its verdict, and plan-gate-check reads it here, on the bootstrap
51
+ # path, before any lane generates. A feature with no receipt has simply never been gated
52
+ # since the receipt existed — reported, never refused. One whose receipt no longer matches,
53
+ # or whose verdict left plan gaps, stops; one whose verdict left only spec/model gaps warns.
53
54
  # 3.2.0 — AND NOT delta-spec.md EITHER. The change path used to inline it "only so the
54
55
  # paired tests can cite traceability ids", which made the rule true on the bootstrap path
55
56
  # and false on the one where changes actually ship: a document in the payload is a document
@@ -86,11 +87,22 @@
86
87
  # • FRONTEND — `## /aw-spec-implement-ui`, both paths. There was never a delta heading
87
88
  # for this lane because the delta half of it was never built; it is built here.
88
89
  #
89
- # ONE SUBTREE PER LANE. Each lane owns the source path stacks.json declares for it and
90
- # writes nothing outside it. A file written elsewhere belongs to the other lane or to no
91
- # lane at all, and the project gate is deliberately stricter for the frontend: a manifest
92
- # found only elsewhere in the repository is the service lane's, and accepting it is how a
93
- # frontend that was never scaffolded is generated into anyway and reports green.
90
+ # TWO PATHS PER LANE. Each lane owns the source path stacks.json declares for it and the
91
+ # test path lane-verify resolves beside it — `tests/` next to a .NET `src/`, the source
92
+ # path itself for a stack that keeps its tests inside — and writes nothing outside them.
93
+ # The one exception is registering a test project the lane had to create, in whatever
94
+ # file the lane's test command discovers tests through (a .NET solution file). A file
95
+ # written elsewhere belongs to the other lane or to no lane at all, and the project gate
96
+ # is deliberately stricter for the frontend: a manifest found only elsewhere in the
97
+ # repository is the service lane's, and accepting it is how a frontend that was never
98
+ # scaffolded is generated into anyway and reports green.
99
+ #
100
+ # A FILE LIST IS NOT A FINISHED LANE. Each generating lane is handed its plan's element
101
+ # list and reports every element back as done or blocked; `plan-coverage` then checks the
102
+ # code under the lane's source path for every identifier the plan introduces. Whatever
103
+ # either check leaves open gets one completion pass, and anything still open after it
104
+ # stops the run once both lanes have generated — so a pass that stopped at five use-cases
105
+ # of seven is reported, not shipped.
94
106
  #
95
107
  # A LANE'S GAP DISABLES THAT LANE, NOT THE RUN. Three things a lane needs are checked per
96
108
  # lane and reported per lane: its source path is on disk, its plan is there, and a project
@@ -116,7 +128,7 @@
116
128
 
117
129
  name: aw-spec-implement
118
130
  description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open it generates from the master plans; with one or more open it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, leaving the masters untouched. Every lane is gated on its own source path, its own plan and its own scaffolded project, and a lane missing any of them is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
119
- version: 3.4.0
131
+ version: 3.7.0
120
132
  agent_agnostic: true
121
133
  dialect: aw-spec.orchestrator/v1
122
134
 
@@ -162,19 +174,23 @@ vars:
162
174
  delta_frontend_plan: ""
163
175
  delta_frontend_plan_exists: "false"
164
176
  wave: ""
165
- # THE GATE RECEIPT — was this feature's plan pair ever PROVEN against this spec, and has
166
- # anything moved since. analyze-plan writes it on a green master run; this phase is the
167
- # reader. Bootstrap path only: a delta generates from delta plans, which analyze-plan-delta
168
- # gates, and the master receipt says nothing about those.
177
+ # THE GATE RECEIPT — was this feature's plan pair checked against this spec, with what
178
+ # verdict, and has anything moved since. analyze-plan writes it on every master run; this
179
+ # phase is the reader. Bootstrap path only: a delta generates from delta plans, which
180
+ # analyze-plan-delta gates, and the master receipt says nothing about those.
169
181
  gate_tracked: "false"
170
182
  gate_fresh: "false"
171
183
  gate_reason: ""
172
184
  gate_at: ""
185
+ gate_verdict: "" # conformant | routed-gaps | gaps — empty with no receipt
186
+ gate_routed_csv: "" # the gaps only /appweaver specify can close
187
+ gate_may_generate: "true"
188
+ gate_no_routed: "true" # lowered when the receipt's verdict is routed-gaps — a warning, not a stop
173
189
  # DEFAULTED PERMISSIVE, DELIBERATELY, and it is the one flag here that is. Every tree that
174
190
  # predates the receipt has none, and "never gated" is an unknown, not a stale plan —
175
191
  # refusing on a missing file would block every existing feature on its first run. The flag
176
- # is lowered only by a receipt that EXISTS and no longer matches: that tree has been through
177
- # the gate once, so the bytes moving since is a fact, not an absence of one.
192
+ # is lowered only by a receipt that EXISTS and refuses: the bytes moved since it was
193
+ # written, or its verdict left gaps a plan rewrite closes.
178
194
  may_generate: "true"
179
195
  # PER-LANE STATE. Each lane resolves its own plan, its own project and its own answer;
180
196
  # a lane that is not declared never resolves any of them and its steps never run.
@@ -189,6 +205,15 @@ vars:
189
205
  be_go: "false"
190
206
  be_mode: "skip" # skip | bootstrap | delta
191
207
  be_files: []
208
+ # COMPLETENESS, per lane — see step 8b and the checks after each lane generates.
209
+ be_element_names: ""
210
+ be_elements: []
211
+ be_more_files: []
212
+ be_elements_more: []
213
+ be_complete: "true"
214
+ be_completion_ran: "false"
215
+ be_gap_names: ""
216
+ be_gaps_text: "none"
192
217
  fe_plan: ""
193
218
  fe_plan_ok: "false"
194
219
  fe_project_ok: "false"
@@ -198,6 +223,15 @@ vars:
198
223
  fe_go: "false"
199
224
  fe_mode: "skip" # skip | bootstrap | delta
200
225
  fe_files: []
226
+ fe_element_names: ""
227
+ fe_elements: []
228
+ fe_more_files: []
229
+ fe_elements_more: []
230
+ fe_complete: "true"
231
+ fe_completion_ran: "false"
232
+ fe_gap_names: ""
233
+ fe_gaps_text: "none"
234
+ lanes_complete: "true"
201
235
  generate_any: "false"
202
236
  record_edges: "false"
203
237
  overview_ok: "false"
@@ -245,6 +279,7 @@ workflow:
245
279
  - { name: be_declared, from: "$.backend_declared" }
246
280
  - { name: be_lane_id, from: "$.backend_lane_id" }
247
281
  - { name: be_source_path, from: "$.backend_source_path" }
282
+ - { name: be_test_path, from: "$.backend_test_path" }
248
283
  - { name: be_root_exists, from: "$.backend_root_exists" }
249
284
  - { name: be_profile, from: "$.backend_profile" }
250
285
  - { name: be_title, from: "$.backend_title" }
@@ -255,6 +290,7 @@ workflow:
255
290
  - { name: fe_declared, from: "$.frontend_declared" }
256
291
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
257
292
  - { name: fe_source_path, from: "$.frontend_source_path" }
293
+ - { name: fe_test_path, from: "$.frontend_test_path" }
258
294
  - { name: fe_root_exists, from: "$.frontend_root_exists" }
259
295
  - { name: fe_profile, from: "$.frontend_profile" }
260
296
  - { name: fe_title, from: "$.frontend_title" }
@@ -425,11 +461,15 @@ workflow:
425
461
  # spec.md, `implement` therefore generated from a plan that was never held against the
426
462
  # spec it now contradicts, silently and at full length.
427
463
  #
428
- # plan-gate-check compares today's bytes against the receipt analyze-plan left when it
429
- # passed — spec.md, the context's domain-model.md and each gated lane plan. The plans'
430
- # own `**Version**:` headers cannot answer this and must not be used: close-delta stamps
431
- # them and spec.md to the same number in lockstep whether or not a plan was re-derived,
432
- # so the comparison reads equal always.
464
+ # plan-gate-check compares today's bytes against the receipt analyze-plan left — spec.md,
465
+ # the context's domain-model.md and each gated lane plan — and reads the verdict that
466
+ # run reached. The plans' own `**Version**:` headers cannot answer this and must not be
467
+ # used: close-delta stamps them and spec.md to the same number in lockstep whether or not
468
+ # a plan was re-derived, so the comparison reads equal always.
469
+ #
470
+ # THREE OUTCOMES FOR A RECEIPT. Moved bytes refuse. Verdict `gaps` refuses: that run left
471
+ # gaps a plan rewrite closes. Verdict `routed-gaps` generates and warns: what is left
472
+ # can only be closed in the spec or the model, and it is listed for /appweaver specify.
433
473
  #
434
474
  # BOOTSTRAP ONLY. A delta run generates from delta plans that analyze-plan-delta gates;
435
475
  # the master receipt is silent about them, and reading it there would refuse a change
@@ -441,25 +481,41 @@ workflow:
441
481
  subcommand: plan-gate-check
442
482
  args: ["--feature-dir", "{{feature_dir}}"]
443
483
  outputs:
444
- - { name: gate_tracked, from: "$.tracked" }
445
- - { name: gate_fresh, from: "$.gate_fresh" }
446
- - { name: gate_reason, from: "$.reason" }
447
- - { name: gate_at, from: "$.gated" }
484
+ - { name: gate_tracked, from: "$.tracked" }
485
+ - { name: gate_fresh, from: "$.gate_fresh" }
486
+ - { name: gate_verdict, from: "$.verdict" }
487
+ - { name: gate_may_generate, from: "$.may_generate" }
488
+ - { name: gate_routed_csv, from: "$.routed_csv" }
489
+ - { name: gate_reason, from: "$.reason" }
490
+ - { name: gate_at, from: "$.gated" }
448
491
 
449
492
  # The ordered assignment is the AND: only a TRACKED feature can lower the flag, and
450
- # what lowers it is the gate no longer being fresh. An untracked one never reaches
493
+ # what lowers it is plan-gate-check's may_generate. An untracked one never reaches
451
494
  # this step's assignment and generates, which is the whole accommodation for trees
452
495
  # that have not been gated since the receipt existed.
453
496
  - id: flag_gate_block
454
497
  condition: "{{gate_tracked}} == true"
455
498
  action: set
456
- vars: { may_generate: "{{gate_fresh}}" }
499
+ vars: { may_generate: "{{gate_may_generate}}" }
457
500
 
458
501
  - id: require_gate_fresh
459
502
  action: verify_context
460
503
  require_true: may_generate
461
504
  on_false: stop
462
- on_false_message: "implement REFUSED — the master plans are no longer the ones analyze-plan passed: {{gate_reason}}. This phase generates from the plans and does NOT read the spec, on the strength of that gate having conformed them to it, so generating now would realise a plan against requirements nobody checked it against. Re-run /appweaver analyze-plan — it re-checks all four checks, repairs the drifting plan on approval, and re-issues the receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path: the re-derived plans have not been gated yet. Nothing was generated."
505
+ on_false_message: "implement REFUSED — analyze-plan's receipt does not clear these plans: {{gate_reason}}. This phase generates from the plans and does NOT read the spec, on the strength of that gate having checked them against it, so generating now would realise a plan against requirements nobody held it to. Re-run /appweaver analyze-plan — it re-checks all four checks, repairs the drifting plan on approval, and re-issues the receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path: the re-derived plans have not been gated yet. Nothing was generated."
506
+
507
+ # THE WARNING for a receipt whose only open gaps belong to specify. Generation goes on:
508
+ # no plan rewrite could close them, and the plan is what this phase builds from.
509
+ - id: flag_routed_gaps
510
+ condition: "{{gate_verdict}} == routed-gaps"
511
+ action: set
512
+ vars: { gate_no_routed: "false" }
513
+
514
+ - id: warn_routed_gaps
515
+ action: verify_context
516
+ require_true: gate_no_routed
517
+ on_false: continue
518
+ on_false_message: "WARNING — analyze-plan left gaps only the spec or the model can close, so the code generated now will not cover them: {{gate_routed_csv}}. Lift each into spec.md / domain-model.md by opening a change with /appweaver specify."
463
519
 
464
520
  # 4. THE PROJECT each declared lane generates INTO. Deterministic structure gate:
465
521
  # detect-project searches the LANE'S OWN subtree for an ecosystem manifest (.sln/
@@ -575,7 +631,7 @@ workflow:
575
631
  - id: ask_backend
576
632
  condition: "{{be_runnable}} == true"
577
633
  action: ask
578
- prompt: "Implement the BACKEND lane now? Lane `{{be_lane_id}}` — {{be_title}}, {{be_language}} ({{be_ecosystem}}) — generating from {{be_plan}} into {{be_source_path}}."
634
+ prompt: "Implement the BACKEND lane now? Lane `{{be_lane_id}}` — {{be_title}}, {{be_language}} ({{be_ecosystem}}) — generating from {{be_plan}} into {{be_source_path}}, tests into {{be_test_path}}."
579
635
  options: ["yes", "no"]
580
636
  answer_var: be_answer
581
637
  - id: be_go_set
@@ -586,7 +642,7 @@ workflow:
586
642
  - id: ask_frontend
587
643
  condition: "{{fe_runnable}} == true"
588
644
  action: ask
589
- prompt: "Implement the FRONTEND lane now? Lane `{{fe_lane_id}}` — {{fe_title}}, {{fe_language}} ({{fe_ecosystem}}) — generating from {{fe_plan}} into {{fe_source_path}}."
645
+ prompt: "Implement the FRONTEND lane now? Lane `{{fe_lane_id}}` — {{fe_title}}, {{fe_language}} ({{fe_ecosystem}}) — generating from {{fe_plan}} into {{fe_source_path}}, tests into {{fe_test_path}}."
590
646
  options: ["yes", "no"]
591
647
  answer_var: fe_answer
592
648
  - id: fe_go_set
@@ -616,6 +672,26 @@ workflow:
616
672
  action: set
617
673
  vars: { fe_mode: "{{mode}}" }
618
674
 
675
+ # 8b. EACH GENERATING LANE'S ELEMENT LIST, read off its plan by plan-coverage. The
676
+ # generation prompt carries it, and the model reports every name on it — so an
677
+ # element it did not get to is declared rather than silently absent.
678
+ - id: be_plan_elements
679
+ condition: "{{be_go}} == true"
680
+ action: execute_script
681
+ script: { ref: actions }
682
+ subcommand: plan-coverage
683
+ args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--list-only"]
684
+ outputs:
685
+ - { name: be_element_names, from: "$.names_text" }
686
+ - id: fe_plan_elements
687
+ condition: "{{fe_go}} == true"
688
+ action: execute_script
689
+ script: { ref: actions }
690
+ subcommand: plan-coverage
691
+ args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--list-only"]
692
+ outputs:
693
+ - { name: fe_element_names, from: "$.names_text" }
694
+
619
695
  # 9. Each generating lane's code-generation rule files → sticky standards (configuration,
620
696
  # not hardcoded). Loaded only for a lane that is actually going to generate, and the
621
697
  # backend's heading is keyed by mode (see the header). No blocking gate: an
@@ -698,15 +774,23 @@ workflow:
698
774
  loaded standards describe a DIFFERENT stack than the one above, the lane wins on
699
775
  language and layout — the standards' stack-specific mechanics do not apply, so
700
776
  follow their INTENT and STOP to surface the mismatch in your summary.
701
- - WRITE ONLY UNDER `{{be_source_path}}` — the source path the `{{be_lane_id}}` lane
702
- declares in .appweaver/stacks.json. That subtree is this lane's, and a file outside
703
- it belongs to another lane or to no lane at all. NEVER write into the frontend
704
- lane's subtree, not even to consume a contract. Do not create a parallel source
705
- tree because the layout inside it is unfamiliar; if the plan needs a place this
706
- subtree has no home for, STOP and surface it.
777
+ - WRITE ONLY INTO THE LANE'S TWO PATHS, both resolved for the `{{be_lane_id}}` lane
778
+ from .appweaver/stacks.json: production code under `{{be_source_path}}`, paired
779
+ tests under `{{be_test_path}}` (the same directory when the stack keeps its tests
780
+ inside its source). Those subtrees are this lane's, and a file outside them belongs
781
+ to another lane or to no lane at all. NEVER write into the frontend lane's subtree,
782
+ not even to consume a contract. Do not create a parallel source tree because the
783
+ layout inside them is unfamiliar; if the plan needs a place they have no home for,
784
+ STOP and surface it.
785
+ - A MISSING TEST PROJECT IS CREATED, NEVER A REASON TO SKIP THE TESTS. When
786
+ `{{be_test_path}}` holds no test project yet, create one there as the loaded
787
+ testing standard describes, referencing the production project it tests, and
788
+ register it wherever `{{be_test}}` discovers tests (for .NET, the solution file).
789
+ That registration is the ONLY edit allowed outside the lane's two paths; name the
790
+ file you edited in your summary.
707
791
  - Follow the project's folder/layer convention + the loaded coding-standards,
708
792
  dependency-policy and testing standards for HOW to structure, name and place files
709
- WITHIN that subtree — the plan says WHAT to build; these standards (identical for
793
+ WITHIN those subtrees — the plan says WHAT to build; these standards (identical for
710
794
  every feature) say HOW.
711
795
  - COMPONENTS: when the plan's External Integrations table depends on a framework
712
796
  component (e.g. `IEmailService`, `IJobScheduler`), code against that component's
@@ -727,6 +811,12 @@ workflow:
727
811
  CN / DC / IC / US) from its Traceability section. Include tests for the constraints
728
812
  — e.g. an audit record is emitted, a DC-restricted field is NOT exposed on the
729
813
  public surface, an IC- projection round-trips.
814
+ - WIRE WHAT YOU BUILD, in this same pass. Every port, adapter, handler and endpoint
815
+ the plan introduces is registered in the lane's composition root as the plan's §4.3
816
+ Wiring states. A dependency that is injected and never bound COMPILES GREEN — a
817
+ container graph is not compile-checked — and fails on the first request that needs
818
+ it. Where the project registers by assembly scan or convention, confirm the new
819
+ type is actually covered by it rather than assuming it is.
730
820
  - Do NOT add behaviour the plan does not state, and do NOT omit any it does. If the
731
821
  plan looks wrong or incomplete, STOP and surface it (it is fixed in plan /
732
822
  analyze-plan) — never compensate from the spec or from memory.
@@ -740,8 +830,16 @@ workflow:
740
830
  one changed, STOP and surface it.
741
831
  - Where generated code depends on a plan Assumption (DA-n) or Workaround (WA-n), mark
742
832
  it with a comment citing that id so it is easy to revisit later.
833
+ - REPORT EVERY PLAN ELEMENT. The plan's elements, by name: {{be_element_names}}.
834
+ Add an `elements` list to your closing JSON block with one entry per name, spelled
835
+ exactly as listed: {"element": "<name>", "status": "done" | "blocked", "reason":
836
+ "<what stops it, when blocked>"}. `done` means it is realised in code now,
837
+ including one that already existed; `blocked` means you could not realise it. An
838
+ element missing from the list counts as not done, and the code is checked for
839
+ each name after this step.
743
840
  outputs:
744
841
  - { name: be_files, from: "$.files" }
842
+ - { name: be_elements, from: "$.elements" }
745
843
 
746
844
  # ═══════════ BACKEND LANE — delta, generate ONLY what the change adds ════════
747
845
  # The delta plan is the authoritative source of WHAT changed — analyze-plan-delta already
@@ -816,12 +914,18 @@ workflow:
816
914
  loaded standards describe a DIFFERENT stack than the one above, the lane wins on
817
915
  language and layout — follow the standards' INTENT and STOP to surface the
818
916
  mismatch in your summary.
819
- - WRITE ONLY UNDER `{{be_source_path}}` — the source path the `{{be_lane_id}}` lane
820
- declares. That subtree is this lane's, and a file outside it belongs to another
821
- lane or to no lane at all. NEVER write into the frontend lane's subtree.
917
+ - WRITE ONLY INTO THE LANE'S TWO PATHS: production code under `{{be_source_path}}`,
918
+ paired tests under `{{be_test_path}}`. Those subtrees are this lane's, and a file
919
+ outside them belongs to another lane or to no lane at all. NEVER write into the
920
+ frontend lane's subtree.
921
+ - A MISSING TEST PROJECT IS CREATED, NEVER A REASON TO SKIP THE TESTS. When
922
+ `{{be_test_path}}` holds no test project yet, create one there as the loaded
923
+ testing standard describes and register it wherever `{{be_test}}` discovers tests
924
+ (for .NET, the solution file) — the ONLY edit allowed outside the lane's two paths;
925
+ name the file you edited in your summary.
822
926
  - Follow the project's EXISTING folder/layer convention + the loaded
823
927
  coding-standards, dependency-policy and testing standards for HOW to structure,
824
- name and place files WITHIN that subtree. Match the surrounding code already there.
928
+ name and place files WITHIN those subtrees. Match the surrounding code already there.
825
929
  - Realise EVERY ADDED/MODIFIED delta element as code: one file per Domain type /
826
930
  Application use-case / API endpoint / Infrastructure adapter, placed by layer.
827
931
  Preserve the ubiquitous-language names verbatim. Enforce each new/changed Domain
@@ -838,13 +942,24 @@ workflow:
838
942
  The spec is not in context: take the id from the plan and cite it verbatim, never
839
943
  guess one from the element's shape. A delta-plan element that names no id is a
840
944
  planning gap — surface it rather than inventing the traceability.
945
+ - WIRE WHAT YOU BUILD, in this same pass: every port, adapter, handler and endpoint
946
+ this change introduces is registered in the lane's composition root, as the delta
947
+ plan states. A dependency injected and never bound compiles green and fails on the
948
+ first request; where registration is by assembly scan, confirm the new type is
949
+ covered by it.
841
950
  - REGRESSION: for each element in the delta plan's "Regression targets", verify (add
842
951
  or adjust a test where needed) that the existing behaviour still holds after a
843
952
  shared element changed — but do NOT rewrite that existing behaviour.
844
953
  - Where generated code depends on a delta Assumption (DA-n) or Workaround (WA-n),
845
954
  mark it with a comment citing that id.
955
+ - REPORT EVERY DELTA ELEMENT. The delta plan's ADDED and MODIFIED elements, by name:
956
+ {{be_element_names}}. Add an `elements` list to your closing JSON block with one
957
+ entry per name, spelled exactly as listed: {"element": "<name>", "status": "done"
958
+ | "blocked", "reason": "<what stops it, when blocked>"}. An element missing from
959
+ the list counts as not done, and the code is checked for each name after this step.
846
960
  outputs:
847
961
  - { name: be_files, from: "$.files" }
962
+ - { name: be_elements, from: "$.elements" }
848
963
 
849
964
  - id: verify_be_files
850
965
  condition: "{{be_go}} == true"
@@ -853,6 +968,66 @@ workflow:
853
968
  on_empty: stop
854
969
  on_empty_message: "The backend lane generated no files. Ensure {{be_plan}} is a FILLED plan (not the empty template) and that it passed its gate — /appweaver analyze-plan for a master, /appweaver analyze-plan-delta for a change — then re-run implement."
855
970
 
971
+ # PARTIAL GENERATION. A non-empty file list is not a finished lane: a pass that stopped
972
+ # part-way returns the files it did write. plan-coverage checks every plan element two
973
+ # ways — the lane's code must name each identifier the plan introduces, and the model's
974
+ # own report must say each element is done — and whatever fails either check gets ONE
975
+ # completion pass in the same envelope. What is still missing after it stops the run at
976
+ # step 10b, once both lanes have had their turn.
977
+ - id: be_coverage
978
+ condition: "{{be_go}} == true"
979
+ action: execute_script
980
+ script: { ref: actions }
981
+ subcommand: plan-coverage
982
+ args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--source-path", "{{be_source_path}}", "--test-path", "{{be_test_path}}", "--reported", "{{be_elements}}"]
983
+ outputs:
984
+ - { name: be_complete, from: "$.ok" }
985
+ - { name: be_gap_names, from: "$.gap_names_csv" }
986
+ - { name: be_gaps_text, from: "$.gaps_text" }
987
+ - id: be_completion_flag
988
+ condition: "{{be_complete}} == false"
989
+ action: set
990
+ vars: { be_completion_ran: "true" }
991
+ - id: gen_backend_complete
992
+ condition: "{{be_completion_ran}} == true"
993
+ action: agent_generate
994
+ tools: "Read,Write,Edit"
995
+ permission_mode: acceptEdits
996
+ task: |
997
+ COMPLETE the backend lane. The generation pass before this one left these plan
998
+ elements unrealised: {{be_gap_names}}.
999
+ Why each was flagged: {{be_gaps_text}}.
1000
+ The same plan, domain model and constitution are in context, and every rule of that
1001
+ pass still holds: realise each element STRICTLY as the plan states it; production
1002
+ code only under `{{be_source_path}}` and paired tests only under `{{be_test_path}}`;
1003
+ the `{{be_lane_id}}` lane's stack ({{be_title}}, {{be_language}}) and no other;
1004
+ shared aggregate files EXTENDED, never regenerated; nothing the plan does not state.
1005
+ Rules:
1006
+ - Realise each listed element now, with its paired test citing the spec id the plan
1007
+ gives it. Leave what the earlier pass wrote as it is, except to extend a shared file.
1008
+ - Use the plan's name for each element EXACTLY. An element that already exists
1009
+ under a different name is renamed to the plan's name, with its references — the
1010
+ plan's names are the ubiquitous language.
1011
+ - An element the plan names by intent (a port, an adapter, an endpoint route) is done
1012
+ when the code realises it, whatever the framework calls it; report it `done`.
1013
+ - If an element cannot be realised, do not work around it: report it `blocked` and
1014
+ say what stops you. A plan that is wrong is fixed in plan / analyze-plan.
1015
+ - Report every listed element in an `elements` list in your closing JSON block:
1016
+ {"element": "<name as listed>", "status": "done" | "blocked", "reason": "…"}.
1017
+ outputs:
1018
+ - { name: be_more_files, from: "$.files" }
1019
+ - { name: be_elements_more, from: "$.elements" }
1020
+ - id: be_coverage_recheck
1021
+ condition: "{{be_completion_ran}} == true"
1022
+ action: execute_script
1023
+ script: { ref: actions }
1024
+ subcommand: plan-coverage
1025
+ args: ["--plan", "{{be_plan}}", "--kind", "backend", "--mode", "{{be_mode}}", "--source-path", "{{be_source_path}}", "--test-path", "{{be_test_path}}", "--reported", "{{be_elements}}", "--reported", "{{be_elements_more}}"]
1026
+ outputs:
1027
+ - { name: be_complete, from: "$.ok" }
1028
+ - { name: be_gap_names, from: "$.gap_names_csv" }
1029
+ - { name: be_gaps_text, from: "$.gaps_text" }
1030
+
856
1031
  # OKF knowledge — the feature has ONE concept across both lanes and both modes, so every
857
1032
  # lane's files JOIN it rather than replace it (--merge unions them with what is already
858
1033
  # recorded). Without the union a backend run would erase the frontend lane's source_files
@@ -868,6 +1043,15 @@ workflow:
868
1043
  args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{be_files}}", "--merge", "--origin", "greenfield"]
869
1044
  outputs:
870
1045
  - { name: okf_concept, from: "$.concept" }
1046
+ - id: xref_concept_backend_more
1047
+ condition: "{{be_completion_ran}} == true"
1048
+ action: execute_script
1049
+ on_error: continue
1050
+ script: { ref: xref }
1051
+ subcommand: concept-upsert
1052
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{be_more_files}}", "--merge", "--origin", "greenfield"]
1053
+ outputs:
1054
+ - { name: okf_concept, from: "$.concept" }
871
1055
 
872
1056
  # ══════════════ THE LANE SWAP — the frontend's rules replace the backend's ═══
873
1057
  # build_context already scoped the two lanes' PAYLOADS apart; this is the same boundary
@@ -932,16 +1116,22 @@ workflow:
932
1116
  re-decide anything already fixed there. If the loaded standards describe a
933
1117
  DIFFERENT stack than the one above, the lane wins on framework and layout — follow
934
1118
  the standards' INTENT and STOP to surface the mismatch in your summary.
935
- - WRITE ONLY UNDER `{{fe_source_path}}` — the source path the `{{fe_lane_id}}` lane
936
- declares. That subtree is this lane's; a file outside it belongs to the service
937
- lane or to no lane. NEVER touch the service lane's code, not even to add a field
938
- the UI wants: a gap in the service surface is a contract delta raised back to that
939
- lane. Do not create a parallel source tree because the layout inside this one is
940
- unfamiliar; if the plan needs a place this subtree has no home for, STOP and
941
- surface it.
1119
+ - WRITE ONLY INTO THE LANE'S TWO PATHS, both resolved for the `{{fe_lane_id}}` lane:
1120
+ production code under `{{fe_source_path}}`, paired tests under `{{fe_test_path}}`
1121
+ (the same directory when the stack keeps its tests inside its source). Those
1122
+ subtrees are this lane's; a file outside them belongs to the service lane or to no
1123
+ lane. NEVER touch the service lane's code, not even to add a field the UI wants: a
1124
+ gap in the service surface is a contract delta raised back to that lane. Do not
1125
+ create a parallel source tree because the layout inside this one is unfamiliar; if
1126
+ the plan needs a place this subtree has no home for, STOP and surface it.
1127
+ - A MISSING TEST SETUP IS CREATED, NEVER A REASON TO SKIP THE TESTS. When the lane
1128
+ has no test project or test runner yet, set one up inside the lane's paths as the
1129
+ loaded testing standard describes, so that `{{fe_test}}` runs the tests you write.
1130
+ Anything outside the lane's two paths that must change for that is the ONLY such
1131
+ edit allowed; name the file you edited in your summary.
942
1132
  - Follow the UI constitution's folder structure, naming conventions and forbidden
943
1133
  patterns, plus the loaded standards, for HOW to structure, name and place files
944
- WITHIN that subtree — the plan says WHAT to build; these say HOW.
1134
+ WITHIN those subtrees — the plan says WHAT to build; these say HOW.
945
1135
  - REALISE EVERY PLAN ELEMENT, changing nothing about WHAT it specifies:
946
1136
  • §1 — one route/screen per row, with the navigation shape and the access
947
1137
  conditions the row states.
@@ -974,8 +1164,16 @@ workflow:
974
1164
  feature. If one of them has to change, STOP and surface it.
975
1165
  - Where generated code depends on a plan Assumption (DA-n) or Workaround (WA-n), mark
976
1166
  it with a comment citing that id so it is easy to revisit later.
1167
+ - REPORT EVERY PLAN ELEMENT. The plan's elements, by name: {{fe_element_names}}.
1168
+ Add an `elements` list to your closing JSON block with one entry per name, spelled
1169
+ exactly as listed: {"element": "<name>", "status": "done" | "blocked", "reason":
1170
+ "<what stops it, when blocked>"}. `done` means it is realised in code now,
1171
+ including one that already existed; `blocked` means you could not realise it. An
1172
+ element missing from the list counts as not done, and the code is checked for
1173
+ each name after this step.
977
1174
  outputs:
978
1175
  - { name: fe_files, from: "$.files" }
1176
+ - { name: fe_elements, from: "$.elements" }
979
1177
 
980
1178
  # ═══════════ FRONTEND LANE — delta, generate ONLY what the change adds ═══════
981
1179
  # The UI half of the CR loop, which never existed as its own command: the ⌛
@@ -1031,9 +1229,13 @@ workflow:
1031
1229
  `{{fe_test}}`. Generate in THAT framework and no other. Take the router, state
1032
1230
  approach, component library, design system and styling from the UI constitution +
1033
1231
  the loaded standards, and MATCH the code already in the tree.
1034
- - WRITE ONLY UNDER `{{fe_source_path}}`. NEVER touch the service lane's code, not
1232
+ - WRITE ONLY INTO THE LANE'S TWO PATHS: production code under `{{fe_source_path}}`,
1233
+ paired tests under `{{fe_test_path}}`. NEVER touch the service lane's code, not
1035
1234
  even to add a field the UI wants — a gap in the service surface is a contract delta
1036
1235
  raised back to that lane, never worked around in the client.
1236
+ - A MISSING TEST SETUP IS CREATED, NEVER A REASON TO SKIP THE TESTS: set one up
1237
+ inside the lane's paths so that `{{fe_test}}` runs the tests you write, and name
1238
+ any file outside them you had to edit for that in your summary.
1037
1239
  - STATES ARE PART OF THE CHANGE. Every interaction this delta adds or alters ships
1038
1240
  its FULL state set — loading, empty, error, permission-denied, in-flight — plus the
1039
1241
  validation timing, focus placement and keyboard reachability the delta plan states.
@@ -1055,8 +1257,14 @@ workflow:
1055
1257
  feature. If one of them has to change, STOP and surface it.
1056
1258
  - Where generated code depends on a delta Assumption (DA-n) or Workaround (WA-n),
1057
1259
  mark it with a comment citing that id.
1260
+ - REPORT EVERY DELTA ELEMENT. The delta plan's ADDED and MODIFIED elements, by name:
1261
+ {{fe_element_names}}. Add an `elements` list to your closing JSON block with one
1262
+ entry per name, spelled exactly as listed: {"element": "<name>", "status": "done"
1263
+ | "blocked", "reason": "<what stops it, when blocked>"}. An element missing from
1264
+ the list counts as not done, and the code is checked for each name after this step.
1058
1265
  outputs:
1059
1266
  - { name: fe_files, from: "$.files" }
1267
+ - { name: fe_elements, from: "$.elements" }
1060
1268
 
1061
1269
  - id: verify_fe_files
1062
1270
  condition: "{{fe_go}} == true"
@@ -1065,6 +1273,62 @@ workflow:
1065
1273
  on_empty: stop
1066
1274
  on_empty_message: "The frontend lane generated no files. Ensure {{fe_plan}} is a FILLED plan (not the empty template) and that it passed its gate — /appweaver analyze-plan for a master, /appweaver analyze-plan-delta for a change — then re-run implement."
1067
1275
 
1276
+ # PARTIAL GENERATION — the frontend twin of the backend check above.
1277
+ - id: fe_coverage
1278
+ condition: "{{fe_go}} == true"
1279
+ action: execute_script
1280
+ script: { ref: actions }
1281
+ subcommand: plan-coverage
1282
+ args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--source-path", "{{fe_source_path}}", "--test-path", "{{fe_test_path}}", "--reported", "{{fe_elements}}"]
1283
+ outputs:
1284
+ - { name: fe_complete, from: "$.ok" }
1285
+ - { name: fe_gap_names, from: "$.gap_names_csv" }
1286
+ - { name: fe_gaps_text, from: "$.gaps_text" }
1287
+ - id: fe_completion_flag
1288
+ condition: "{{fe_complete}} == false"
1289
+ action: set
1290
+ vars: { fe_completion_ran: "true" }
1291
+ - id: gen_frontend_complete
1292
+ condition: "{{fe_completion_ran}} == true"
1293
+ action: agent_generate
1294
+ tools: "Read,Write,Edit"
1295
+ permission_mode: acceptEdits
1296
+ task: |
1297
+ COMPLETE the frontend lane. The generation pass before this one left these plan
1298
+ elements unrealised: {{fe_gap_names}}.
1299
+ Why each was flagged: {{fe_gaps_text}}.
1300
+ The same plan, domain model and UI constitution are in context, and every rule of
1301
+ that pass still holds: realise each element STRICTLY as the plan states it, with its
1302
+ FULL state set; production code only under `{{fe_source_path}}` and paired tests
1303
+ only under `{{fe_test_path}}`; the `{{fe_lane_id}}` lane's stack ({{fe_title}},
1304
+ {{fe_language}}) and no other; never the service lane's code, the app shell, the
1305
+ global layout or the design system; nothing the plan does not state.
1306
+ Rules:
1307
+ - Realise each listed element now, with its paired test citing the spec id the plan
1308
+ gives it. Leave what the earlier pass wrote as it is.
1309
+ - Use the plan's name for each element EXACTLY. An element that already exists
1310
+ under a different name is renamed to the plan's name, with its references — the
1311
+ plan's names are the ubiquitous language.
1312
+ - A screen or a binding is done when the code realises it, whatever its route or
1313
+ file is called; report it `done`.
1314
+ - If an element cannot be realised, do not work around it: report it `blocked` and
1315
+ say what stops you. A plan that is wrong is fixed in plan / analyze-plan.
1316
+ - Report every listed element in an `elements` list in your closing JSON block:
1317
+ {"element": "<name as listed>", "status": "done" | "blocked", "reason": "…"}.
1318
+ outputs:
1319
+ - { name: fe_more_files, from: "$.files" }
1320
+ - { name: fe_elements_more, from: "$.elements" }
1321
+ - id: fe_coverage_recheck
1322
+ condition: "{{fe_completion_ran}} == true"
1323
+ action: execute_script
1324
+ script: { ref: actions }
1325
+ subcommand: plan-coverage
1326
+ args: ["--plan", "{{fe_plan}}", "--kind", "frontend", "--mode", "{{fe_mode}}", "--source-path", "{{fe_source_path}}", "--test-path", "{{fe_test_path}}", "--reported", "{{fe_elements}}", "--reported", "{{fe_elements_more}}"]
1327
+ outputs:
1328
+ - { name: fe_complete, from: "$.ok" }
1329
+ - { name: fe_gap_names, from: "$.gap_names_csv" }
1330
+ - { name: fe_gaps_text, from: "$.gaps_text" }
1331
+
1068
1332
  - id: xref_concept_frontend
1069
1333
  condition: "{{fe_go}} == true"
1070
1334
  action: execute_script
@@ -1074,6 +1338,34 @@ workflow:
1074
1338
  args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fe_files}}", "--merge", "--origin", "greenfield"]
1075
1339
  outputs:
1076
1340
  - { name: okf_concept, from: "$.concept" }
1341
+ - id: xref_concept_frontend_more
1342
+ condition: "{{fe_completion_ran}} == true"
1343
+ action: execute_script
1344
+ on_error: continue
1345
+ script: { ref: xref }
1346
+ subcommand: concept-upsert
1347
+ args: ["--feature-dir", "{{feature_dir}}", "--source-files", "{{fe_more_files}}", "--merge", "--origin", "greenfield"]
1348
+ outputs:
1349
+ - { name: okf_concept, from: "$.concept" }
1350
+
1351
+ # 10b. A LANE LEFT INCOMPLETE STOPS THE RUN — after both lanes have generated and every
1352
+ # file written is recorded in the OKF concept, and before the edges, the Overview
1353
+ # and the after-hooks, which all assume a finished slice. Re-running implement
1354
+ # finishes the job: shared files are extended, and a file that exists is not
1355
+ # regenerated.
1356
+ - id: lanes_incomplete_be
1357
+ condition: "{{be_complete}} == false"
1358
+ action: set
1359
+ vars: { lanes_complete: "false" }
1360
+ - id: lanes_incomplete_fe
1361
+ condition: "{{fe_complete}} == false"
1362
+ action: set
1363
+ vars: { lanes_complete: "false" }
1364
+ - id: require_complete_lanes
1365
+ action: verify_context
1366
+ require_true: lanes_complete
1367
+ on_false: stop
1368
+ on_false_message: "PARTIAL GENERATION — implement did not realise every plan element, even after one completion pass. Backend lane: {{be_gaps_text}}. Frontend lane: {{fe_gaps_text}}. Everything written so far is kept and recorded in the OKF concept. An element reported `blocked` names what stops it — a plan that is wrong is fixed with /appweaver plan (then analyze-plan or analyze-plan-delta). An element with no code under the lane's source path is still to be written, or was written under a name that is not the plan's: re-run /appweaver implement to finish, which extends existing files rather than regenerating them."
1077
1369
 
1078
1370
  # 11. CROSS-SLICE EDGES — a Cross-References row written into every SIBLING aggregate
1079
1371
  # this one uses, read off the master backend plan (§1 References-by-id + its External
@@ -1181,15 +1473,25 @@ outputs:
1181
1473
  frontend_implemented: "{{fe_go}}"
1182
1474
  frontend_plan: "{{fe_plan}}" # the document the frontend lane generated FROM
1183
1475
  frontend_files: "{{fe_files}}"
1476
+ # Every plan element realised, per lane — `true` for a lane that did not generate.
1477
+ # `*_completion_pass` says whether the first pass left gaps a second one had to close.
1478
+ backend_complete: "{{be_complete}}"
1479
+ backend_completion_pass: "{{be_completion_ran}}"
1480
+ backend_more_files: "{{be_more_files}}"
1481
+ frontend_complete: "{{fe_complete}}"
1482
+ frontend_completion_pass: "{{fe_completion_ran}}"
1483
+ frontend_more_files: "{{fe_more_files}}"
1184
1484
  domain_model: "{{domain_model}}" # the master model every generating lane read
1185
1485
  delta_model: "{{delta_model}}" # its second half on the delta path — empty on the bootstrap path
1186
1486
  okf_concept: "{{okf_concept}}"
1187
1487
  xref_edge_count: "{{xref_edge_count}}"
1188
- # Bootstrap path only. tracked=false means this feature has never passed analyze-plan
1189
- # since the gate began leaving a receipt — an unknown, not a stale plan, and it blocks
1190
- # nothing.
1488
+ # Bootstrap path only. tracked=false means this feature has not been through a master
1489
+ # analyze-plan run since the gate began leaving a receipt — an unknown, not a stale plan,
1490
+ # and it blocks nothing.
1191
1491
  plan_gate_tracked: "{{gate_tracked}}"
1192
1492
  plan_gate_fresh: "{{gate_fresh}}"
1193
1493
  plan_gate_at: "{{gate_at}}"
1494
+ plan_gate_verdict: "{{gate_verdict}}"
1495
+ plan_gate_routed_to_specify: "{{gate_routed_csv}}" # open spec/model gaps this code does not cover
1194
1496
  note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else. On the bootstrap path the plans were checked against analyze-plan's receipt before anything generated (tracked={{gate_tracked}}, fresh={{gate_fresh}}, gated {{gate_at}}) — tracked=false means this feature has never passed analyze-plan since the gate began recording what it proved, which is an unknown rather than a stale plan and blocks nothing; run /appweaver analyze-plan once to start the record."
1195
1497
  next: "This run was mode `{{mode}}`, change `{{active_change}}` (empty on the bootstrap path). BOOTSTRAP → /appweaver build → analyze-slice → run. DELTA → /appweaver build → analyze-slice-delta {{active_change}} → finalize-delta → analyze-slice → close-delta, with the change still OPEN throughout — compile first, then analyze-slice-delta verifies that every element the delta plans marked ADDED or MODIFIED is actually in the code with a paired test (the gate that catches a lane you declined at the prompt above), then finalize-delta merges it into spec.md + the context's domain-model.md while leaving it open, analyze-slice verifies the slice against those merged masters while a red result is still recoverable, and close-delta LAST archives the change and bumps the version."