aw-spec-cli 0.1.32 → 0.1.34

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 (75) hide show
  1. package/README.md +76 -81
  2. package/assets/.appweaver/DESIGN-FORMAT.md +56 -17
  3. package/assets/.appweaver/STACK-PROFILES.md +95 -60
  4. package/assets/.appweaver/orchestrator/LAUNCHER.md +1 -1
  5. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +173 -32
  6. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +29 -1
  7. package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +7 -4
  8. package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +49 -60
  9. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +58 -0
  10. package/assets/.appweaver/orchestrator/orchestrator.py +26 -8
  11. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +20 -16
  12. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +30 -6
  13. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +9 -7
  14. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -5
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +341 -36
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +254 -43
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +248 -68
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +251 -20
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +316 -10
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +6 -47
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +62 -16
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +132 -9
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-fix-test-coverage.orch.yaml +965 -0
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +222 -169
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +28 -12
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +453 -116
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +7 -4
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +83 -6
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +45 -2
  30. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +324 -74
  31. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +275 -44
  32. package/assets/.appweaver/scripts/python/aw-foundation.py +16 -13
  33. package/assets/.appweaver/scripts/python/aw-gaps.py +11 -3
  34. package/assets/.appweaver/scripts/python/aw-model-guard.py +295 -0
  35. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +412 -3
  36. package/assets/.appweaver/scripts/python/aw-plangate.py +23 -16
  37. package/assets/.appweaver/scripts/python/aw-stack.py +1794 -265
  38. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +0 -2
  39. package/assets/.appweaver/stack-profiles/java-spring.json +0 -2
  40. package/assets/.appweaver/templates/api-plan-template.md +107 -0
  41. package/assets/.appweaver/templates/delta-api-plan-template.md +87 -0
  42. package/assets/.appweaver/templates/delta-design-template.md +25 -23
  43. package/assets/.appweaver/templates/delta-spec-template.md +3 -3
  44. package/assets/.appweaver/templates/design-template.md +20 -42
  45. package/assets/.appweaver/templates/domain-model-template.md +3 -0
  46. package/assets/.appweaver/templates/ui-delta-design-template.md +11 -8
  47. package/assets/.appweaver/templates/ui-design-template.md +4 -4
  48. package/assets/foundations/foundation.spec.template.txt +25 -18
  49. package/assets/guardrails/README.md +61 -25
  50. package/assets/guardrails/methodology/ux/accessibility-standards.md +38 -0
  51. package/assets/guardrails/methodology/ux/content-design.md +34 -0
  52. package/assets/guardrails/methodology/ux/interaction-states.md +45 -0
  53. package/assets/guardrails/methodology/ux/journey-mapping.md +41 -0
  54. package/assets/guardrails/project-specific/domain/domain-requirements.md +25 -38
  55. package/assets/guardrails/project-specific/domain-rule-bindings.md +33 -69
  56. package/assets/guardrails/project-specific/frontend/ui-requirements.md +2 -2
  57. package/assets/guardrails/project-specific/service/technical-requirements.md +2 -2
  58. package/assets/guardrails/project-specific/tech-rule-bindings.md +16 -13
  59. package/assets/guardrails/sample-healthcare/domain/domain-requirements.md +58 -0
  60. package/assets/guardrails/sample-healthcare/domain-rule-bindings.md +138 -0
  61. package/assets/guardrails/sample-healthcare/frontend/ui-requirements.md +43 -0
  62. package/assets/guardrails/sample-healthcare/service/technical-requirements.md +40 -0
  63. package/assets/guardrails/sample-healthcare/tech-rule-bindings.md +303 -0
  64. package/dist/commands/init.d.ts.map +1 -1
  65. package/dist/commands/init.js +216 -7
  66. package/dist/commands/init.js.map +1 -1
  67. package/dist/utils/config.js +1 -1
  68. package/dist/utils/config.js.map +1 -1
  69. package/dist/utils/foundation.d.ts +36 -0
  70. package/dist/utils/foundation.d.ts.map +1 -0
  71. package/dist/utils/foundation.js +109 -0
  72. package/dist/utils/foundation.js.map +1 -0
  73. package/dist/utils/paths.js +1 -1
  74. package/dist/utils/paths.js.map +1 -1
  75. package/package.json +1 -1
@@ -2,7 +2,8 @@
2
2
  # aw-spec-plan (orchestrator) — the spec-anchored Plan phase, BOTH LANES
3
3
  #
4
4
  # Authors the feature's LANE PLANS — the tech-agnostic HLDs that MEDIATE between the
5
- # spec and the code:
5
+ # spec and the code — and the API contract between them:
6
+ # • specs/<Context>/<Feature>/api-plan.md when a BACKEND lane is declared — its endpoints
6
7
  # • specs/<Context>/<Feature>/backend-plan.md when a BACKEND lane is declared
7
8
  # • specs/<Context>/<Feature>/frontend-plan.md when a FRONTEND lane is declared
8
9
  # `implement` GENERATES each lane's code from ITS PLAN ALONE (model-driven, not a
@@ -33,16 +34,28 @@
33
34
  # backend-only change gets no frontend delta plan, even from a developer on `both`. A
34
35
  # change with no such line touches every lane.
35
36
  #
36
- # THE CONTRACT HAND-OFF BETWEEN THE TWO PLANS. Each lane's developer writes only their own
37
- # plan, so what the frontend needs from the service travels as a REQUEST LEDGER:
37
+ # THE API PLAN COMES FIRST. The backend lane authors api-plan.md — every endpoint the
38
+ # service publishes, the API contract between the lanes — before backend-plan.md, which then
39
+ # realises each endpoint as a use-case. A change does the same in its own directory:
40
+ # delta-api-plan.md first, then delta-backend-plan.md. After the backend half,
41
+ # contract-draft writes api-draft.json beside the plans from the API plan, by script, so a
42
+ # frontend can generate its client against endpoints the service has planned and not built
43
+ # yet. The frontend plan reads the API plan and nothing else of the service's.
44
+ #
45
+ # A FEATURE PLANNED BEFORE API PLANS EXISTED keeps its endpoints in its backend plan, and
46
+ # every reader falls back to that while it has no api-plan.md. Every re-derivation (`main`)
47
+ # offers to move them out while they are there, by script (api-split); finalize-delta moves
48
+ # them when it first merges a change's API into the feature. A change planned before then
49
+ # keeps its API in its delta backend plan, which is re-planned with the API in it.
50
+ #
51
+ # THE CONTRACT HAND-OFF BETWEEN THE TWO LANES. Each lane's developer writes only their own
52
+ # plans, so what the frontend needs from the service travels as a REQUEST LEDGER:
38
53
  # frontend-plan.md §5 "Contract deltas needed" holds one RQ-n per operation it needs and the
39
- # surface lacks — every binding, when there is no backend plan yet — and backend-plan.md §3
40
- # answers each in "Requests from the frontend lane", published under an operation id or
41
- # declined with the reason. contract-requests reads both before each lane is authored: the
42
- # backend plan is told which requests are open, the frontend plan which were answered (to
43
- # re-bind) or declined (to plan around). After the backend half, contract-draft writes
44
- # api-draft.json beside the plan from its API section, by script, so a frontend can
45
- # generate its client against endpoints the service has planned and not built yet.
54
+ # surface lacks — every binding, when there is no API plan yet — and api-plan.md answers
55
+ # each in "Requests from the frontend lane", published under an operation id or declined
56
+ # with the reason. contract-requests reads both before each lane is authored: the API plan
57
+ # is told which requests are open, the frontend plan which were answered (to re-bind) or
58
+ # declined (to plan around).
46
59
  #
47
60
  # ONE COMMAND, TWO LANES, TWO RULE SETS. The lanes take their rules from different
48
61
  # headings of tech-rule-bindings (`## /aw-spec-plan-backend` for the service,
@@ -85,9 +98,9 @@
85
98
  # CONFIRMED first — the run stops on a decline, leaving them untouched.
86
99
  # Next: /appweaver analyze-plan (spec↔plan gate) → implement.
87
100
  # • CR-… → author THAT change's delta plans ONLY (delta-plan module). The masters are
88
- # left UNTOUCHED, and finalize-delta does not merge them either: it merges the
89
- # change's spec and model halves, and a master plan is RE-DERIVED from those — which
90
- # is what the main row above does. The change stays OPEN.
101
+ # left UNTOUCHED, and finalize-delta does not merge the lane plans either: it merges
102
+ # the change's spec, model and API halves, and a master plan is RE-DERIVED from those —
103
+ # which is what the main row above does. The change stays OPEN.
91
104
  # Next: analyze-plan-delta → implement (which asks which change) → build →
92
105
  # analyze-slice-delta → finalize-delta → analyze-slice → close-delta.
93
106
  # • new → open a plan-only change first, then plan it: a refactor or architectural
@@ -116,8 +129,8 @@
116
129
  # =============================================================================
117
130
 
118
131
  name: aw-spec-plan
119
- description: "Author the feature's lane plans from the spec and the bounded context's domain model — backend-plan.md when a backend lane is declared, frontend-plan.md when a frontend lane is, both when both are. Lanes come from .appweaver/stacks.json, so one command covers backend-only, frontend-only and full-stack projects; the former separate design-ui command is folded in. It ASKS what it is planning, the way implement asks what it is building: the master (`main`), one of the OPEN CHANGES, or a new plan-only change for a refactor with no spec delta — the argument doubles as the answer, and the menu appears only once a master exists, so a first run bootstraps with no prompt. `main` on a feature that already has masters RE-DERIVES them from the merged spec.md + domain-model.md — the operation finalize-delta's contract assumes when it merges a change's spec and model halves and no lane plan — and is confirmed before it rewrites anything. A change instead gets delta-backend-plan.md / delta-frontend-plan.md inside changes/<CR-id>/ while the masters stay untouched, written against the effective model (the master model, the change's own model delta, and the pending model deltas of LOWER waves, which are its predecessors) and against the feature's real source files from its OKF concept. Master authoring is template-driven: the kit's templates by default, or the code-derived as-built structure when onboard installed an override. Authoring only — a master gates at analyze-plan; a change ships through the CR chain."
120
- version: 5.5.0
132
+ description: "Author the feature's lane plans from the spec and the bounded context's domain model — backend-plan.md when a backend lane is declared, with api-plan.md (the endpoints the service publishes, the API contract the frontend binds to) authored before it; frontend-plan.md when a frontend lane is; all of them when both are. Lanes come from .appweaver/stacks.json, so one command covers backend-only, frontend-only and full-stack projects; the former separate design-ui command is folded in. It ASKS what it is planning, the way implement asks what it is building: the master (`main`), one of the OPEN CHANGES, or a new plan-only change for a refactor with no spec delta — the argument doubles as the answer, and the menu appears only once a master exists, so a first run bootstraps with no prompt. `main` on a feature that already has masters RE-DERIVES them from the merged spec.md + domain-model.md — the operation finalize-delta's contract assumes when it merges a change's spec and model halves and no lane plan — and is confirmed before it rewrites anything. A change instead gets delta-backend-plan.md / delta-frontend-plan.md inside changes/<CR-id>/ while the masters stay untouched, written against the effective model (the master model, the change's own model delta, and the pending model deltas of LOWER waves, which are its predecessors) and against the feature's real source files from its OKF concept. Master authoring is template-driven: the kit's templates by default, or the code-derived as-built structure when onboard installed an override. Authoring only — a master gates at analyze-plan; a change ships through the CR chain."
133
+ version: 5.8.1
121
134
  agent_agnostic: true
122
135
  dialect: aw-spec.orchestrator/v1
123
136
 
@@ -161,10 +174,34 @@ vars:
161
174
  fe_working: "false"
162
175
  any_working: "false"
163
176
  working_lane: ""
164
- # The backend plans AS READ CONTEXT for the frontend lane — bound only when the backend
165
- # lane is declared, so a plan left behind by a removed lane never reaches the payload.
166
- be_plan_read: ""
167
- delta_backend_plan_read: ""
177
+ # THE SERVICE'S API AS READ CONTEXT for the frontend lane — the API plan, or the backend
178
+ # plan of a feature planned before API plans existed; and the same for the chosen change.
179
+ # Bound only when the backend lane is declared, so a plan left behind by a removed lane
180
+ # never reaches the payload.
181
+ api_read: ""
182
+ delta_api_read: ""
183
+ # THE API PLAN (api-plan.md) — the endpoints the backend lane publishes, authored before
184
+ # the backend plan on the main path. api_mode: skip | author (fill the template just
185
+ # scaffolded) | update (revise the API plan that is there, or was just split out).
186
+ api_plan: ""
187
+ api_plan_exists: "false"
188
+ api_go: "false" # the main path, on a worked backend lane
189
+ api_mode: "skip"
190
+ api_created: "false"
191
+ api_blocked: "false" # the backend plan still carries the API — the split is offered
192
+ api_split_answer: ""
193
+ api_split_done: "false"
194
+ api_split_endpoints: "0"
195
+ api_split_dropped: "none"
196
+ api_split_kept: "true"
197
+ api_split_ok: "true" # false when a yes to the split did not move the endpoints
198
+ api_in_backend: "false" # the backend plan keeps the API this run: the scaffold refused and no split moved it
199
+ # What the backend plan's authoring step is told about where the endpoints are — the API
200
+ # plan, or this document itself when no split moved them out of it.
201
+ be_api_rule: "THE ENDPOINTS ARE IN api-plan.md (in context), authored just before this document: the API contract the frontend lane binds to. Do NOT restate them here — the API section of this document is the one line pointing to api-plan.md, and where the template has an endpoint block or a requests table (a template generated before API plans existed), that line goes in their place. REALISE every endpoint instead: each names under **Maps to** the use-case that serves it, and that use-case is this document's, with request and response DTOs that match the endpoint's shapes. An endpoint with no use-case here is unrealised. The traceability spine cites an endpoint as `VERB /route`."
202
+ cr_delta_backend_plan: "" # cr-paths, on the delta path
203
+ cr_delta_api_plan: ""
204
+ cr_delta_api_exists: "false"
168
205
  rederive_be_txt: ""
169
206
  rederive_fe_txt: ""
170
207
  active_change: ""
@@ -236,11 +273,25 @@ vars:
236
273
  source_files: [] # the feature's real source files, from its OKF concept
237
274
  source_count: "0"
238
275
  # THE REQUEST LEDGER and the draft (contract-requests / contract-draft).
239
- req_backend_text: "none" # the frontend's requests still open — what the backend plan answers
276
+ req_backend_text: "none" # the frontend's requests still open — what the API plan answers (the backend plan, for a feature with none yet)
240
277
  req_frontend_text: "none" # answered requests to re-bind and declined ones to plan around
241
278
  req_summary: ""
242
279
  draft_path: ""
280
+ draft_plan: "" # the document it was drawn from: api-plan.md, or the backend plan
243
281
  draft_ops: "0"
282
+ draft_unread: "0" # endpoint shapes the drafting script could not type
283
+ draft_unread_text: "none"
284
+ draft_clean: "true"
285
+ draft_dropped: "0" # operations the previous draft had and this one does not
286
+ draft_dropped_text: "none"
287
+ draft_kept: "true"
288
+ # The change's own API draft (delta path).
289
+ draft_change_go: "false"
290
+ change_draft_path: ""
291
+ change_draft_plan: ""
292
+ change_draft_unread: "0"
293
+ change_draft_unread_text: "none"
294
+ change_draft_clean: "true"
244
295
 
245
296
  workflow:
246
297
 
@@ -312,6 +363,8 @@ workflow:
312
363
  - { name: backend_plan_exists, from: "$.backend_plan_exists" }
313
364
  - { name: frontend_plan, from: "$.frontend_plan" }
314
365
  - { name: frontend_plan_exists, from: "$.frontend_plan_exists" }
366
+ - { name: api_plan, from: "$.api_plan" }
367
+ - { name: api_plan_exists, from: "$.api_plan_exists" }
315
368
 
316
369
  # THIS FEATURE'S LANES. lane_resolve above read the REPO's declaration; one feature may
317
370
  # own no part of a lane's surface — adopt records `lanes: [backend]` in
@@ -345,12 +398,6 @@ workflow:
345
398
  action: set
346
399
  vars: { any_working: "true" }
347
400
 
348
- # The backend plan as READ context for the frontend lane, on a declared backend only.
349
- - id: be_plan_read_set
350
- condition: "{{be_declared}} == true"
351
- action: set
352
- vars: { be_plan_read: "{{backend_plan}}" }
353
-
354
401
  # The BOUNDED CONTEXT's domain model — where the aggregate root, its entities and its
355
402
  # invariants (BR-n) live. The spec cites them by id; this phase records where each is
356
403
  # ENFORCED, so the model has to be in context or the instruction to "follow it exactly"
@@ -615,6 +662,28 @@ workflow:
615
662
  # returns the existing path when the plan is there, and RENAMES a legacy design.md /
616
663
  # design.ui.md into the plan name). It sits after the question on purpose: a template
617
664
  # copied in before a `no` stays on disk, and the next run reads it as a master.
665
+ #
666
+ # THE API PLAN FIRST, on the main path of a worked backend lane. The scaffold refuses
667
+ # (`blocked`) while a backend plan still carries the API, since an empty API plan
668
+ # beside it would be read in its place — and a backend plan scaffolded from a template
669
+ # generated before API plans existed carries an empty requests table, so on a first
670
+ # run the backend plan must not be there yet. The delta path plans the change's API in
671
+ # the delta-plan module instead and leaves the master API plan alone.
672
+ - id: api_go_arm
673
+ condition: "{{plan_target}} == main"
674
+ action: set
675
+ vars: { api_go: "{{be_working}}" }
676
+ - id: api_scaffold
677
+ condition: "{{api_go}} == true"
678
+ action: execute_script
679
+ script: { ref: actions }
680
+ subcommand: plan-scaffold
681
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "api"]
682
+ outputs:
683
+ - { name: api_plan, from: "$.plan_doc" }
684
+ - { name: api_created, from: "$.created" }
685
+ - { name: api_blocked, from: "$.blocked" }
686
+
618
687
  - id: be_scaffold
619
688
  condition: "{{be_working}} == true"
620
689
  action: execute_script
@@ -639,11 +708,100 @@ workflow:
639
708
  - { name: fe_created, from: "$.created" }
640
709
  - { name: fe_migrated, from: "$.migrated" }
641
710
 
642
- # The scaffold may have renamed a legacy design.md, so the READ path is taken again.
643
- - id: be_plan_read_scaffolded
644
- condition: "{{be_working}} == true"
711
+ # A FEATURE PLANNED BEFORE API PLANS EXISTED keeps its endpoints in its backend plan, and
712
+ # the API scaffold above refused to put an empty API plan beside them. Offered on every
713
+ # re-derivation while they are there: api-split moves every endpoint block and the
714
+ # requests table into a new api-plan.md by script, leaves a line in the backend plan where
715
+ # they were, and redraws api-draft.json. A no keeps the old layout for this run — every
716
+ # reader falls back to the backend plan — and finalize-delta moves them when it first
717
+ # merges a change's API. Yes is first, so an unattended run moves them: the move only
718
+ # relocates blocks, and what it drew is checked against the draft before it below. A yes
719
+ # the split does not complete keeps the old layout too, and is reported.
720
+ - id: ask_api_split
721
+ condition: "{{api_blocked}} == true"
722
+ action: ask
723
+ prompt: "This feature's endpoints are still in {{backend_plan}}. Move them into their own api-plan.md now? A script does it (api-split): every endpoint block and the requests table go over as they are, the backend plan keeps one line saying where they went, and api-draft.json is redrawn from the new file. The frontend lane's next analyze-plan re-gates once, because the surface it binds to is now a different file. Answer no to keep them where they are for this run."
724
+ options: ["yes", "no"]
725
+ answer_var: api_split_answer
726
+ - id: api_split
727
+ condition: "{{api_split_answer}} == yes"
728
+ action: execute_script
729
+ on_error: continue
730
+ script: { ref: actions }
731
+ subcommand: api-split
732
+ args: ["--feature-dir", "{{feature_dir}}"]
733
+ outputs:
734
+ - { name: api_split_done, from: "$.split" }
735
+ - { name: api_split_endpoints, from: "$.endpoints" }
736
+ - { name: api_split_dropped, from: "$.dropped_text" }
737
+ - id: api_split_lost
738
+ condition: "{{api_split_dropped}} != none"
739
+ action: set
740
+ vars: { api_split_kept: "false" }
741
+ - id: warn_api_split_lost
742
+ condition: "{{api_split_done}} == true"
743
+ action: verify_context
744
+ require_true: api_split_kept
745
+ on_false: continue
746
+ on_false_message: "API SPLIT — the {{api_split_endpoints}} endpoint(s) moved into {{api_plan}} draw fewer operations than api-draft.json had before: {{api_split_dropped}}. Compare {{api_plan}} with the backend plan's history, restore what is missing, then re-run plan or analyze-plan."
747
+
748
+ # A YES THE SPLIT DID NOT COMPLETE — the script failed, or answered without moving the
749
+ # endpoints. They are still in the backend plan and no api-plan.md was written, so the run
750
+ # goes on as a declined split does (the backend plan keeps the API, below) and says so.
751
+ # The engine binds no reason for a script that fails without answering, so the message
752
+ # names the command that shows it.
753
+ - id: api_split_ok_flag
754
+ condition: "{{api_split_answer}} == yes"
645
755
  action: set
646
- vars: { be_plan_read: "{{backend_plan}}" }
756
+ vars: { api_split_ok: "{{api_split_done}}" }
757
+ - id: warn_api_split_failed
758
+ action: verify_context
759
+ require_true: api_split_ok
760
+ on_false: continue
761
+ on_false_message: "API SPLIT DID NOT COMPLETE — you answered yes, but api-split did not move this feature's endpoints out of {{backend_plan}}, and no api-plan.md was written. This run went on as if the move had been declined: the endpoints stay in the backend plan, and its re-derivation keeps them there. To see why and move them, run `.appweaver/scripts/bash/aw-spec-actions.sh api-split --feature-dir {{feature_dir}}` (PowerShell: `.appweaver/scripts/powershell/aw-spec-actions.ps1 api-split --feature-dir {{feature_dir}}`); once it reports split=true, re-run /appweaver plan main to revise the new api-plan.md."
762
+
763
+ # THE API PLAN'S AUTHORING MODE, as ordered assignments: `update` revises an API plan that
764
+ # was already there; `author` fills a template the scaffold just copied in; a refused
765
+ # scaffold leaves `skip` and the API in the backend plan (`api_in_backend`) for this run;
766
+ # a split that happened makes it `update` again, over the blocks it moved, and the API is
767
+ # no longer in the backend plan.
768
+ - id: api_mode_existing
769
+ condition: "{{api_go}} == true"
770
+ action: set
771
+ vars: { api_mode: "update" }
772
+ - id: api_mode_author
773
+ condition: "{{api_created}} == true"
774
+ action: set
775
+ vars: { api_mode: "author" }
776
+ - id: api_mode_blocked
777
+ condition: "{{api_blocked}} == true"
778
+ action: set
779
+ vars: { api_mode: "skip", api_in_backend: "true" }
780
+ - id: api_mode_split
781
+ condition: "{{api_split_done}} == true"
782
+ action: set
783
+ vars: { api_mode: "update", api_in_backend: "false" }
784
+
785
+ # WHAT THE FRONTEND LANE READS OF THE SERVICE: the API plan when there is one — on disk
786
+ # already, or authored in this run — and otherwise the backend plan (the scaffold may
787
+ # have renamed a legacy design.md, so its path is the one taken after the scaffold).
788
+ # Nothing on an undeclared backend lane.
789
+ - id: api_read_be
790
+ condition: "{{be_declared}} == true"
791
+ action: set
792
+ vars: { api_read: "{{backend_plan}}" }
793
+ - id: api_read_on_disk
794
+ condition: "{{api_plan_exists}} == true"
795
+ action: set
796
+ vars: { api_read: "{{api_plan}}" }
797
+ - id: api_read_authored
798
+ condition: "{{api_mode}} != skip"
799
+ action: set
800
+ vars: { api_read: "{{api_plan}}" }
801
+ - id: api_read_undeclared
802
+ condition: "{{be_declared}} == false"
803
+ action: set
804
+ vars: { api_read: "" }
647
805
 
648
806
  # 5e. Resolve each lane's authoring MODE. Four ordered assignments per lane: the
649
807
  # greenfield default for a new lane, the code-derived override replacing it, a
@@ -710,6 +868,16 @@ workflow:
710
868
  - { name: req_backend_text, from: "$.backend_text" }
711
869
  - { name: req_summary, from: "$.summary" }
712
870
 
871
+ # THE API STAYS IN THE BACKEND PLAN whenever no split moved it — declined, or a yes the
872
+ # split did not complete — so its re-derivation is told to keep the endpoint blocks and
873
+ # answer the requests itself, as it did before API plans existed. Told the endpoints were
874
+ # in api-plan.md, it would replace them with a pointer to a document that does not exist.
875
+ - id: be_api_rule_in_plan
876
+ condition: "{{api_in_backend}} == true"
877
+ action: set
878
+ vars:
879
+ be_api_rule: "THE ENDPOINTS STAY IN THIS DOCUMENT for this run — they have not been moved into api-plan.md. Keep the endpoint blocks of its API section in their form: the `VERB /route` heading, then **Operation id**, Request, Success and the Errors table, every shape `{ field: type }`. OPERATION IDS ARE STABLE: every endpoint keeps the operation id it already has, and a new one gets an id unique across the service. ANSWER THE FRONTEND LANE'S CONTRACT REQUESTS still open in the frontend plan's §5 'Contract deltas needed': {{req_backend_text}}. Answer each one in the 'Requests from the frontend lane' table of the API section (| Request | Outcome | Operation id | Why |): PUBLISH it as an endpoint, under the operation id it proposes unless that clashes with one already in use, or DECLINE it with the reason. Publish only what the spec supports — a request the spec gives no basis for is declined as a spec gap for /appweaver specify. Keep every row the table already has. `none` means no request is open."
880
+
713
881
  # 6. Pre-execution hooks.
714
882
  - id: resolve_pre_hooks
715
883
  action: execute_script
@@ -722,6 +890,129 @@ workflow:
722
890
  action: dispatch_hooks
723
891
  hooks_from_variable: before_hooks
724
892
 
893
+ # ═══════════ THE API PLAN — the endpoints, before the backend plan ══════════
894
+ # The contract first: the endpoints a client needs, their shapes and their errors, and
895
+ # the answers to the frontend lane's requests. The backend plan is authored next and
896
+ # realises each endpoint as a use-case. One template in every project, greenfield or
897
+ # brownfield — the endpoint block form is what the contract scripts read — so there is
898
+ # no template split here, only author (a template just scaffolded) or update.
899
+ - id: ctx_api
900
+ condition: "{{api_mode}} == author"
901
+ action: build_context
902
+ context:
903
+ - { path: "{{api_plan}}" } # the copied template = the fixed endpoint block form
904
+ - { path: "{{spec_file}}" } # BEHAVIOUR: what a client must be able to do
905
+ - { path: "{{domain_model}}", optional: true } # the ubiquitous language; the BR-nnn an error answers to
906
+ - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract, and The API plan in it
907
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — the protocol it publishes
908
+ - id: gen_api
909
+ condition: "{{api_mode}} == author"
910
+ action: agent_author
911
+ tools: Read # the feature's EXISTING source arrives as {{source_files}} paths, not inlined
912
+ output_target: { type: file, path: "{{api_plan}}" }
913
+ task: |
914
+ Author the API PLAN (the target api-plan.md in context) for this feature by filling
915
+ out the template: every endpoint the service lane publishes for it, and its answers
916
+ to the frontend lane's requests. It is the API contract between the lanes — the
917
+ frontend plan binds to it, implement serves it, a script drafts it into
918
+ api-draft.json — and it is written BEFORE the backend plan, which then realises each
919
+ endpoint as a use-case. Guidance: {{USER_INPUT}}
920
+ Rules:
921
+ - THE SPEC IS THE BEHAVIOUR AUTHORITY. Publish the endpoints a client needs to do
922
+ what the spec's requirements and user stories say, and no more. Each block's
923
+ heading cites the spec ids it serves; an endpoint no spec id calls for is
924
+ over-reach.
925
+ - KEEP THE TEMPLATE'S FORM EXACTLY: one block per endpoint under "## Endpoints" — the
926
+ heading `### `VERB /route` — purpose · Serves …`, then **Operation id**, **Auth**,
927
+ **Request**, **Success**, the **Errors** table and **Maps to**. Scripts read exactly
928
+ this form; an endpoint written as a table row or in prose reaches neither
929
+ api-draft.json nor the frontend.
930
+ - EVERY SHAPE IS `{ field: type }` in conceptual types — path and query parameters,
931
+ request body, success body, each error body. A shape in prose reaches the frontend's
932
+ client untyped. **Maps to** names the use-case that serves the endpoint; the backend
933
+ plan, authored next, realises it under that name.
934
+ - NAME EVERY ENDPOINT. Its **Operation id** is its name in the service's published API
935
+ document (e.g. `CreatePatient`), unique across the service and never reused. The
936
+ frontend plan cites it and implement gives the endpoint exactly this id.
937
+ - THE ERROR CONTRACT: every failure the spec's requirements and the model's BR-nnn
938
+ imply gets a row — its status, when it is returned, its body shape.
939
+ - DATA CLASSIFICATION (DC-): a response shape carries no field the spec's DC- items
940
+ keep from the client; cite the DC- id in the block whose shape it governs.
941
+ - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY. {{source_count}} source file(s)
942
+ already realise this feature: {{source_files}}. Where the code already serves an
943
+ endpoint, record its route, verb and operation id exactly as the code has them; one
944
+ it serves under no id keeps `—` and says so in its block — never an invented id,
945
+ which the built service would not carry and analyze-slice would report. With no
946
+ source files there is nothing to conform to.
947
+ - ANSWER THE FRONTEND LANE'S CONTRACT REQUESTS. Still open in the frontend plan's §5
948
+ "Contract deltas needed": {{req_backend_text}}. Answer each one in the "Requests
949
+ from the frontend lane" table (| Request | Outcome | Operation id | Why |): PUBLISH
950
+ it as an endpoint, under the operation id it proposes unless that clashes with one
951
+ this service already uses, or DECLINE it with the reason. Publish only what the spec
952
+ supports — a request the spec gives no basis for is declined as a spec gap for
953
+ /appweaver specify, never published on a guess. `none` means no request is open:
954
+ write `None` under that heading when the table would be empty.
955
+ - TECHNOLOGY-AGNOSTIC: protocol level only — no controller code, framework
956
+ attributes, serializer settings or language types. The stack binds at implement.
957
+ - Write `None` under "## Endpoints" when the feature publishes no endpoint.
958
+ - Set **Version** to the feature's version as the spec's header gives it, and write
959
+ the first Change History row.
960
+
961
+ # The API plan RE-DERIVED: revised against the spec as it stands now, before the backend
962
+ # plan is. The document in context is the current API plan — what is being revised, and
963
+ # its form — and the backend plan as it stands is read for the use-cases its endpoints
964
+ # map to.
965
+ - id: ctx_api_update
966
+ condition: "{{api_mode}} == update"
967
+ action: build_context
968
+ context:
969
+ - { path: "{{api_plan}}" } # the CURRENT API plan = what is being revised
970
+ - { path: "{{spec_file}}" } # BEHAVIOUR, carrying every change finalize-delta has merged
971
+ - { path: "{{domain_model}}", optional: true } # the ubiquitous language; the BR-nnn an error answers to
972
+ - { path: "{{backend_plan}}", optional: true } # READ ONLY — the use-cases the endpoints map to today
973
+ - { path: .appweaver/DESIGN-FORMAT.md }
974
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY
975
+ - id: gen_api_update
976
+ condition: "{{api_mode}} == update"
977
+ action: agent_author
978
+ tools: Read # the feature's EXISTING source arrives as {{source_files}} paths, not inlined
979
+ output_target: { type: file, path: "{{api_plan}}" }
980
+ task: |
981
+ RE-DERIVE the API PLAN in context from the current spec. The document you are given
982
+ is the feature's API as it stands today; produce the API this feature's spec calls
983
+ for NOW. It is revised before the backend plan, which then realises each endpoint.
984
+ Do not edit the backend plan. Guidance: {{USER_INPUT}}
985
+ Rules:
986
+ - THIS IS A REVISION, NOT A RE-AUTHORING. Keep every endpoint block the spec still
987
+ supports, byte-for-byte where nothing requires a change: the frontend plan binds to
988
+ it, and the built service is checked against it.
989
+ - OPERATION IDS ARE STABLE. Every endpoint keeps the operation id it already has. A
990
+ new endpoint gets one unique across the service; an endpoint without one gets one
991
+ now, unless the code serves it under none, which keeps `—`.
992
+ - WHAT THE SPEC SAYS NOW WINS. An endpoint no spec id still calls for is deleted with
993
+ its block; a requirement or user story a client needs and no endpoint serves is a
994
+ gap — add the endpoint. The draft drawn after this run reports every operation the
995
+ previous api-draft.json had and this document no longer has.
996
+ - KEEP THE FORM: one block per endpoint under "## Endpoints" — the `### `VERB /route``
997
+ heading, then **Operation id**, **Auth**, **Request**, **Success**, the **Errors**
998
+ table and **Maps to** — every shape `{ field: type }`. A block left in another form
999
+ by an earlier hand reaches neither api-draft.json nor the frontend: bring it into
1000
+ this form.
1001
+ - ANSWER THE FRONTEND LANE'S CONTRACT REQUESTS. Still open in the frontend plan's §5
1002
+ "Contract deltas needed": {{req_backend_text}}. Answer each one in the "Requests
1003
+ from the frontend lane" table (| Request | Outcome | Operation id | Why |): PUBLISH
1004
+ it as an endpoint, under the operation id it proposes unless that clashes with one
1005
+ already in use, or DECLINE it with the reason. Publish only what the spec supports.
1006
+ Keep every row the table already has. `none` means no request is open.
1007
+ - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY. {{source_count}} source file(s)
1008
+ realise this feature: {{source_files}}. Where the plan and the code disagree about
1009
+ a route, a verb or an operation id the code serves, the code is what is there —
1010
+ record it as served and let the spec decide whether it changes.
1011
+ - TECHNOLOGY-AGNOSTIC: protocol level only, conceptual types, no code.
1012
+ - ADD ONE Change History row for this re-derivation, describing what moved and why
1013
+ (the spec ids that drove it). Do NOT invent a version number — the version spine
1014
+ belongs to close-delta.
1015
+
725
1016
  # ═══════════ BACKEND LANE — bootstrap, greenfield four-layer template ═══════
726
1017
  # Author the whole HLD in one pass: all four layers as sections, tech-agnostic, traced
727
1018
  # to the spec. build_context REPLACES the per-request envelope; sticky standards stay
@@ -733,6 +1024,7 @@ workflow:
733
1024
  - { path: "{{backend_plan}}" } # the copied template = section structure
734
1025
  - { path: "{{spec_file}}" } # BEHAVIOUR: FR/CN/DC/IC/US/SC + cited BR ids
735
1026
  - { path: "{{domain_model}}", optional: true } # the CONTEXT's canonical model — entities, invariants, boundary
1027
+ - { path: "{{api_plan}}", optional: true } # the API plan authored just before — the endpoints §2 realises
736
1028
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract
737
1029
  - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain (domain enters via the spec + model)
738
1030
  - id: gen_backend
@@ -764,32 +1056,18 @@ workflow:
764
1056
  seams. The spec stays the only authority on BEHAVIOUR; the code is the authority
765
1057
  on what the structure currently IS. With no source files this is a greenfield
766
1058
  feature and there is nothing to conform to.
767
- - Cover the four layers as sections: Domain (§1), Application (§2), API (§3),
768
- Infrastructure (§4). Keep the template's section order and headings.
1059
+ - Cover the four layers as sections: Domain (§1), Application (§2), API (§3 — the
1060
+ one line pointing to api-plan.md), Infrastructure (§4). Keep the template's section
1061
+ order and headings.
1062
+ - {{be_api_rule}}
769
1063
  - REALISE THE LIFTED CONSTRAINTS (each cites its spec id):
770
1064
  • Constraints & NFRs (CN-) → structural ones (immutability, append-only audit,
771
1065
  no-hard-delete) as §1 invariant enforcement/events; retention/performance/audit
772
1066
  as §5 Cross-Cutting rows or §4 persistence.
773
- • Data Classification (DC-) → §5 data-protection + the §2/§3 projection surface
774
- (what MUST NOT be exposed in API/logs/traces).
1067
+ • Data Classification (DC-) → §5 data-protection + the §2 projections (what MUST
1068
+ NOT be exposed in responses, logs or traces).
775
1069
  • Integration Contracts (IC-) → §4.2 External integrations (one port/adapter row
776
1070
  per IC-), realising the obligation the spec named.
777
- - PUBLISH THE SURFACE THE FRONTEND LANE BINDS TO. §3 states each endpoint's contract
778
- as field shapes. That section is the ONLY part of this document the frontend plan
779
- may read; keep it complete and self-contained, because a contract the UI needs and
780
- this document never states becomes a client-side workaround.
781
- - NAME EVERY ENDPOINT. Each §3 endpoint carries an **Operation id** — its name in the
782
- service's published API document (e.g. `CreatePatient`), unique across the
783
- service and never reused. The frontend plan cites it, and implement gives the
784
- endpoint exactly this id, so the two lanes meet on it.
785
- - ANSWER THE FRONTEND LANE'S CONTRACT REQUESTS. Still open in the frontend plan's §5
786
- "Contract deltas needed": {{req_backend_text}}. Answer each one in §3's "Requests
787
- from the frontend lane" table (| Request | Outcome | Operation id | Why |): PUBLISH
788
- it as a §3 endpoint, under the operation id it proposes unless that clashes with
789
- one this service already uses, or DECLINE it with the reason. Publish only what
790
- the spec supports — a request the spec gives no basis for is declined as a spec gap
791
- for /appweaver specify, never published on a guess. `none` means no request is
792
- open: write `None` under that heading when the table would be empty.
793
1071
  - CONSTITUTION-MANDATED CROSS-CUTTING (from the loaded technical constitution, NOT
794
1072
  the spec): a NON-NEGOTIABLE technical principle you must apply that has NO
795
1073
  per-feature spec id. APPLY it (it is non-negotiable), and because it is not
@@ -825,6 +1103,7 @@ workflow:
825
1103
  - { path: "{{backend_plan}}" } # the scaffolded BROWNFIELD template = code-derived section structure
826
1104
  - { path: "{{spec_file}}" } # BEHAVIOUR: FR/CN/DC/IC/US/SC + cited BR ids
827
1105
  - { path: "{{domain_model}}", optional: true } # the CONTEXT's canonical model — entities, invariants, boundary
1106
+ - { path: "{{api_plan}}", optional: true } # the API plan authored just before — the endpoints §2 realises
828
1107
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract (see its Brownfield variant note)
829
1108
  - { path: knowledge/architecture/overview.md, optional: true } # the project's AS-BUILT architecture (onboard) — align to it
830
1109
  - { path: knowledge/architecture/platform.md, optional: true } # the as-built technology base / cross-cutting reality
@@ -873,22 +1152,9 @@ workflow:
873
1152
  (immutability, append-only audit, no-hard-delete) as §1.2 invariant enforcement /
874
1153
  events; retention/performance/audit as §2 persistence or §3 Cross-Cutting rows.
875
1154
  DC- → §3 data-protection + the projection surface (what MUST NOT be exposed in
876
- API/logs/traces).
877
- - PUBLISH THE SURFACE THE FRONTEND LANE BINDS TO: the section carrying this feature's
878
- endpoints states each contract as field shapes, completely, because it is the only
879
- part of this document the frontend plan may read.
880
- - NAME EVERY ENDPOINT with its route, verb and operation id — its name in the
881
- service's published API document, unique across the service. Where the code
882
- already gives the endpoint one, record THAT id exactly as the code spells it; the
883
- frontend plan cites it and implement finds the endpoint by it.
884
- - ANSWER THE FRONTEND LANE'S CONTRACT REQUESTS. Still open in the frontend plan's
885
- "Contract deltas needed" table: {{req_backend_text}}. Answer each one in a
886
- "Requests from the frontend lane" table beside this feature's endpoints (| Request
887
- | Outcome | Operation id | Why |): PUBLISH it as an endpoint, under the operation id
888
- it proposes unless that clashes with one the service already uses, or DECLINE it
889
- with the reason. Publish only what the spec supports — a request the spec gives no
890
- basis for is declined as a spec gap for /appweaver specify. `none` means no request
891
- is open: write `None` under that heading when the table would be empty.
1155
+ responses, logs or traces).
1156
+ - {{be_api_rule}} The §2 axis this project publishes its endpoints from ends with
1157
+ that line.
892
1158
  - CONSTITUTION-MANDATED CROSS-CUTTING (from the loaded technical constitution, NOT
893
1159
  the spec): a NON-NEGOTIABLE technical principle with no per-feature spec id —
894
1160
  APPLY it, and LOG it as a §5 DA-n whose basis cites the mandating principle
@@ -927,6 +1193,7 @@ workflow:
927
1193
  - { path: "{{backend_plan}}" } # the CURRENT master = what is being revised, and its section structure
928
1194
  - { path: "{{spec_file}}" } # BEHAVIOUR, carrying every change finalize-delta has merged
929
1195
  - { path: "{{domain_model}}", optional: true } # the CONTEXT's canonical model — entities, invariants, boundary
1196
+ - { path: "{{api_plan}}", optional: true } # the API plan revised just before — absent when no split moved the endpoints
930
1197
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract
931
1198
  - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY
932
1199
  - id: gen_backend_update
@@ -958,17 +1225,7 @@ workflow:
958
1225
  workarounds are DECISIONS THIS TEAM MADE, not derivations from the spec — keep
959
1226
  every one, with its id and its basis, unless the thing it decided about is gone.
960
1227
  Never renumber a surviving entry: its id is cited elsewhere.
961
- - OPERATION IDS ARE STABLE. Every endpoint keeps the operation id it already has —
962
- the frontend plan and the built code both use it. A new endpoint gets one, unique
963
- across the service; an endpoint without one gets one now.
964
- - ANSWER THE FRONTEND LANE'S CONTRACT REQUESTS. Still open in the frontend plan's §5
965
- "Contract deltas needed": {{req_backend_text}}. Answer each one in the "Requests
966
- from the frontend lane" table of the API section (| Request | Outcome | Operation id
967
- | Why |) — add the table if the document has none: PUBLISH it as an endpoint, under
968
- the operation id it proposes unless that clashes with one already in use, or
969
- DECLINE it with the reason. Publish only what the spec supports — a request the
970
- spec gives no basis for is declined as a spec gap for /appweaver specify. Keep
971
- every row the table already has. `none` means no request is open.
1228
+ - {{be_api_rule}}
972
1229
  - THE CODE THAT EXISTS IS THE WHAT-IS AUTHORITY. {{source_count}} source file(s)
973
1230
  realise this feature: {{source_files}}. READ them, and where the plan and the code
974
1231
  disagree about STRUCTURE, the code is what is actually there — conform to it and
@@ -981,11 +1238,15 @@ workflow:
981
1238
  (the spec ids and BR-nnn that drove it). Do NOT invent a version number — the
982
1239
  version spine belongs to close-delta.
983
1240
 
984
- # ══════════════ THE DRAFT — the backend plan's API section as a document ══════
985
- # api-draft.json beside the plan, drawn by script from the API section as it now stands,
986
- # so the frontend lane can generate its client against what the service has planned
987
- # before the service is built. Rewritten only when it differs; removed when the plan
988
- # names no endpoint. Advisory — a plan the script cannot read leaves the run going.
1241
+ # ══════════════ THE DRAFT — the API plan as a document ═══════════════════════
1242
+ # api-draft.json beside the plans, drawn by script from the API plan as it now stands (the
1243
+ # backend plan's API section, for a feature planned before API plans existed): the API
1244
+ # contract between the lanes. The frontend lane generates its client from it, and
1245
+ # analyze-slice checks that the built service serves it. Rewritten only when it differs;
1246
+ # removed when the plan names no endpoint. Advisory — a plan the script cannot read
1247
+ # leaves the run going. Two things are reported: a shape it cannot type, because the
1248
+ # frontend's client gets that data untyped; and an operation the previous draft had and
1249
+ # this one does not, because the frontend may still bind to it.
989
1250
  - id: contract_draft
990
1251
  condition: "{{be_working}} == true"
991
1252
  action: execute_script
@@ -994,8 +1255,33 @@ workflow:
994
1255
  subcommand: contract-draft
995
1256
  args: ["--feature-dir", "{{feature_dir}}"]
996
1257
  outputs:
997
- - { name: draft_path, from: "$.path" }
998
- - { name: draft_ops, from: "$.operations" }
1258
+ - { name: draft_path, from: "$.path" }
1259
+ - { name: draft_plan, from: "$.plan" }
1260
+ - { name: draft_ops, from: "$.operations" }
1261
+ - { name: draft_unread, from: "$.unread_count" }
1262
+ - { name: draft_unread_text, from: "$.unread_text" }
1263
+ - { name: draft_dropped, from: "$.dropped_count" }
1264
+ - { name: draft_dropped_text, from: "$.dropped_text" }
1265
+ - id: draft_unread_flag
1266
+ condition: "{{draft_unread}} != 0"
1267
+ action: set
1268
+ vars: { draft_clean: "false" }
1269
+ - id: warn_draft_unread
1270
+ condition: "{{be_working}} == true"
1271
+ action: verify_context
1272
+ require_true: draft_clean
1273
+ on_false: continue
1274
+ on_false_message: "API DRAFT — {{draft_unread}} endpoint shape(s) in {{draft_plan}} are not written as `{ field: type }`, so the frontend's client gets that data untyped: {{draft_unread_text}}. Write each request and response the way the template's endpoint block does, then re-run plan or analyze-plan."
1275
+ - id: draft_dropped_flag
1276
+ condition: "{{draft_dropped}} != 0"
1277
+ action: set
1278
+ vars: { draft_kept: "false" }
1279
+ - id: warn_draft_dropped
1280
+ condition: "{{be_working}} == true"
1281
+ action: verify_context
1282
+ require_true: draft_kept
1283
+ on_false: continue
1284
+ on_false_message: "API DRAFT — {{draft_dropped}} operation(s) the previous api-draft.json had are no longer in {{draft_plan}}: {{draft_dropped_text}}. The frontend lane may still bind to them. If this run removed them on purpose, the frontend plan has to stop binding to them; if not, restore their blocks in {{draft_plan}} and re-run plan or analyze-plan."
999
1285
 
1000
1286
  # THE LEDGER AGAIN, for the frontend half: the backend plan may have just answered.
1001
1287
  - id: contract_requests_frontend
@@ -1040,7 +1326,7 @@ workflow:
1040
1326
  - { path: "{{domain_model}}", optional: true } # the ubiquitous language this lane's labels and messages use
1041
1327
  - { path: .appweaver/DESIGN-FORMAT.md } # the design-doc contract + its frontend lane variant
1042
1328
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base — optional: a lane may be declared before it is ratified
1043
- - { path: "{{be_plan_read}}", optional: true } # §5 ONLY — the service's published surface, never its internals
1329
+ - { path: "{{api_read}}", optional: true } # §5 ONLY — the service's API: api-plan.md, or the backend plan of a feature planned before it
1044
1330
  - id: gen_frontend
1045
1331
  condition: "{{fe_mode}} == bootstrap_gf"
1046
1332
  action: agent_author
@@ -1084,22 +1370,23 @@ workflow:
1084
1370
  gap: log it as a §8 DA-n and say it routes to `specify`; never invent the
1085
1371
  requirement here.
1086
1372
  - §5 IS THE LANE BOUNDARY. Bind against the service's PUBLISHED surface only —
1087
- the spec's IC- ids, and where the backend plan is in context, the API contracts
1088
- it states. CITE those contracts; do NOT restate, re-derive or contradict them,
1089
- and introduce NO server-side behaviour, persistence assumption or handler detail.
1373
+ the spec's IC- ids, and where the service's API is in context (api-plan.md, or the
1374
+ API section of a backend plan written before API plans existed), the endpoints it
1375
+ states. CITE those contracts; do NOT restate, re-derive or contradict them, and
1376
+ introduce NO server-side behaviour, persistence assumption or handler detail.
1090
1377
  - WHAT THE SURFACE DOES NOT OFFER IS A CONTRACT REQUEST, never a workaround in the
1091
1378
  client: one row in §5's "Contract deltas needed" table per operation, with a new
1092
1379
  RQ-n (never renumber or reuse one), the operation id you propose, what it sends
1093
1380
  and receives as field shapes, and the spec ids it serves. The binding row that
1094
1381
  needs it cites that proposed id and names the RQ-n in its Serves cell. With NO
1095
- backend plan in context, EVERY binding is a request. The backend developer
1096
- answers each in the backend plan; this plan never edits that one.
1382
+ API in context, EVERY binding is a request. The backend developer answers each in
1383
+ the API plan; this plan never edits that one.
1097
1384
  - THE BACKEND'S ANSWERS SO FAR: {{req_frontend_text}}. Re-bind every binding row of
1098
1385
  an answered request to the operation id the backend gave it, and plan around a
1099
1386
  declined one — or log a §8 DA-n routing the need to /appweaver specify. `none`
1100
1387
  means no answer is waiting on this plan.
1101
1388
  - EVERY §5 ROW CITES ITS OPERATION ID, copied exactly from the endpoint's
1102
- **Operation id** in the backend plan's §3, or the id its request proposes —
1389
+ **Operation id** in the API in context, or the id its request proposes —
1103
1390
  implement looks the endpoint up by it in the API document.
1104
1391
  - CONSTITUTION-MANDATED CROSS-CUTTING (from the loaded UI constitution, NOT the
1105
1392
  spec): a NON-NEGOTIABLE UX principle you must apply that has no per-feature spec
@@ -1142,7 +1429,7 @@ workflow:
1142
1429
  - { path: .appweaver/DESIGN-FORMAT.md } # the design-doc contract + its frontend lane variant
1143
1430
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's UX-* base, reversed by onboard from this same UI
1144
1431
  - { path: knowledge/architecture/frontend.md, optional: true } # the as-built UI architecture + the SHARED surface no slice owns
1145
- - { path: "{{be_plan_read}}", optional: true } # §5 ONLY — the service's published surface, never its internals
1432
+ - { path: "{{api_read}}", optional: true } # §5 ONLY — the service's API: api-plan.md, or the backend plan of a feature planned before it
1146
1433
  - id: gen_frontend_brownfield
1147
1434
  condition: "{{fe_mode}} == bootstrap_bf"
1148
1435
  action: agent_author
@@ -1182,19 +1469,20 @@ workflow:
1182
1469
  or its logs, storage and URLs, IC- as binding rows, SC- as the performance
1183
1470
  decision.
1184
1471
  - THE LANE BOUNDARY: bind against the service's PUBLISHED surface only — the
1185
- spec's IC- ids and, where the backend plan is in context, the API contracts it
1472
+ spec's IC- ids and, where the service's API is in context (api-plan.md, or the API
1473
+ section of a backend plan written before API plans existed), the endpoints it
1186
1474
  states. Cite them; do not restate, re-derive or contradict them, and introduce no
1187
1475
  server-side behaviour.
1188
1476
  - WHAT THE SURFACE LACKS IS A CONTRACT REQUEST, never a client workaround: one row
1189
1477
  in the "Contract deltas needed" table per operation, with a new RQ-n (never
1190
1478
  renumbered or reused), the operation id you propose, what it sends and receives,
1191
1479
  and the spec ids it serves; the binding row that needs it cites that id and names
1192
- the RQ-n. With NO backend plan in context, EVERY binding is a request.
1480
+ the RQ-n. With NO API in context, EVERY binding is a request.
1193
1481
  - THE BACKEND'S ANSWERS SO FAR: {{req_frontend_text}}. Re-bind an answered
1194
1482
  request's binding rows to the id the backend gave it; plan around a declined one,
1195
1483
  or log a DA-n routing the need to /appweaver specify. `none` means nothing waits.
1196
- - EVERY BINDING ROW CITES ITS OPERATION ID, copied exactly from the backend plan, or
1197
- the id its request proposes — implement looks the endpoint up by it in the API
1484
+ - EVERY BINDING ROW CITES ITS OPERATION ID, copied exactly from the API in context,
1485
+ or the id its request proposes — implement looks the endpoint up by it in the API
1198
1486
  document.
1199
1487
  - CONSTITUTION-MANDATED CROSS-CUTTING with no per-feature spec id: apply it, and
1200
1488
  log it as a DA-n citing the mandating principle ("per constitution.ui.md
@@ -1225,7 +1513,7 @@ workflow:
1225
1513
  - { path: "{{domain_model}}", optional: true } # the ubiquitous language this lane's labels and messages use
1226
1514
  - { path: .appweaver/DESIGN-FORMAT.md } # the design-doc contract + its frontend lane variant
1227
1515
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's ratified UX-* base
1228
- - { path: "{{be_plan_read}}", optional: true } # §5 ONLY — the service's published surface, never its internals
1516
+ - { path: "{{api_read}}", optional: true } # §5 ONLY — the service's API: api-plan.md, or the backend plan of a feature planned before it
1229
1517
  - id: gen_frontend_update
1230
1518
  condition: "{{fe_mode}} == update"
1231
1519
  action: agent_author
@@ -1251,20 +1539,21 @@ workflow:
1251
1539
  source file(s) realise this feature: {{source_files}}. Read them and conform to
1252
1540
  the component boundaries and naming actually there.
1253
1541
  - THE LANE BOUNDARY IS UNCHANGED: bind against the service's PUBLISHED surface only
1254
- — the spec's IC- ids and, where the backend plan is in context, the API contracts
1255
- it states.
1542
+ — the spec's IC- ids and, where the service's API is in context (api-plan.md, or
1543
+ the API section of a backend plan written before API plans existed), the
1544
+ endpoints it states.
1256
1545
  - WHAT THE SURFACE LACKS IS A CONTRACT REQUEST, never a client workaround: one row
1257
1546
  in §5's "Contract deltas needed" table per operation, with a new RQ-n, the
1258
1547
  operation id you propose, what it sends and receives, and the spec ids it serves;
1259
1548
  the binding row that needs it cites that id and names the RQ-n. KEEP every request
1260
- already in the table with its id — the backend plan's answers refer to it. With NO
1261
- backend plan in context, EVERY binding is a request.
1549
+ already in the table with its id — the API plan's answers refer to it. With NO
1550
+ API in context, EVERY binding is a request.
1262
1551
  - THE BACKEND'S ANSWERS SO FAR: {{req_frontend_text}}. Re-bind an answered
1263
1552
  request's binding rows to the id the backend gave it; plan around a declined one,
1264
1553
  or log a §8 DA-n routing the need to /appweaver specify. `none` means nothing waits.
1265
- - EVERY BINDING ROW CITES ITS OPERATION ID, copied exactly from the backend plan or
1554
+ - EVERY BINDING ROW CITES ITS OPERATION ID, copied exactly from the API in context or
1266
1555
  the id its request proposes; a row that has none yet gets one now, and a row whose
1267
- id the backend plan no longer has is re-bound.
1556
+ id the API no longer has is re-bound.
1268
1557
  - TECHNOLOGY-AGNOSTIC: no framework, router, state library, component library or
1269
1558
  styling approach — responsibilities, field shapes, events and states only.
1270
1559
  - ADD ONE Change History row for this re-derivation, describing what MOVED and why.
@@ -1371,10 +1660,13 @@ workflow:
1371
1660
  - { name: predecessor_ids, from: "$.predecessor_ids" }
1372
1661
  - { name: sibling_count, from: "$.sibling_count" }
1373
1662
  - { name: sibling_ids, from: "$.sibling_ids" }
1374
- # THIS CHANGE'S SERVICE HALF, as READ context for its frontend delta plan — the
1375
- # contract table the experience binds to, which the backend developer may have
1376
- # authored in a run of their own. Bound below only on a declared backend lane.
1663
+ # THIS CHANGE'S API, as READ context for its frontend delta plan — what the experience
1664
+ # binds to, which the backend developer may have authored in a run of their own: the
1665
+ # delta API plan, or the delta backend plan of a change planned before those.
1666
+ # Bound below only on a declared backend lane.
1377
1667
  - { name: cr_delta_backend_plan, from: "$.delta_backend_plan" }
1668
+ - { name: cr_delta_api_plan, from: "$.delta_api_plan" }
1669
+ - { name: cr_delta_api_exists, from: "$.delta_api_plan_exists" }
1378
1670
  # Predecessors AND siblings in one list: the delta plan is authored against both, and
1379
1671
  # a build_context takes a single `context_from:` variable. Which entries are which is
1380
1672
  # carried by predecessor_ids / sibling_ids, because the planner's licence over the two
@@ -1386,10 +1678,18 @@ workflow:
1386
1678
  - { name: cr_touches_backend, from: "$.touches_backend" }
1387
1679
  - { name: cr_touches_frontend, from: "$.touches_frontend" }
1388
1680
 
1389
- - id: delta_backend_plan_read_set
1681
+ - id: delta_api_read_be
1390
1682
  condition: "{{be_declared}} == true"
1391
1683
  action: set
1392
- vars: { delta_backend_plan_read: "{{cr_delta_backend_plan}}" }
1684
+ vars: { delta_api_read: "{{cr_delta_backend_plan}}" }
1685
+ - id: delta_api_read_on_disk
1686
+ condition: "{{cr_delta_api_exists}} == true"
1687
+ action: set
1688
+ vars: { delta_api_read: "{{cr_delta_api_plan}}" }
1689
+ - id: delta_api_read_undeclared
1690
+ condition: "{{be_declared}} == false"
1691
+ action: set
1692
+ vars: { delta_api_read: "" }
1393
1693
 
1394
1694
  # ONLY THE LANES THE CHANGE NAMES. be_existing / fe_existing already mean "a working lane
1395
1695
  # with a master plan"; a lane the change's **Lanes** line leaves out is knocked down here,
@@ -1428,6 +1728,39 @@ workflow:
1428
1728
  action: include
1429
1729
  module: .appweaver/orchestrator/modules/delta-plan.orch.yaml
1430
1730
 
1731
+ # THE CHANGE'S API DRAFT — what its delta API plan adds, changes and removes in the
1732
+ # service's API (its delta backend plan's, for a change planned before delta API plans
1733
+ # existed), drawn by script into changes/<CR-id>/api-draft.json. While the change is
1734
+ # open the frontend lane binds to it over the feature's draft, analyze-slice-delta checks
1735
+ # the built service against it, and finalize-delta merges the change's API into the
1736
+ # feature's api-plan.md, whose redrawn draft then carries it.
1737
+ - id: draft_change_arm
1738
+ condition: "{{plan_target}} == delta"
1739
+ action: set
1740
+ vars: { draft_change_go: "{{be_existing}}" }
1741
+ - id: contract_draft_change
1742
+ condition: "{{draft_change_go}} == true"
1743
+ action: execute_script
1744
+ on_error: continue
1745
+ script: { ref: actions }
1746
+ subcommand: contract-draft
1747
+ args: ["--feature-dir", "{{feature_dir}}", "--cr", "{{cr_id}}"]
1748
+ outputs:
1749
+ - { name: change_draft_path, from: "$.path" }
1750
+ - { name: change_draft_plan, from: "$.plan" }
1751
+ - { name: change_draft_unread, from: "$.unread_count" }
1752
+ - { name: change_draft_unread_text, from: "$.unread_text" }
1753
+ - id: change_draft_unread_flag
1754
+ condition: "{{change_draft_unread}} != 0"
1755
+ action: set
1756
+ vars: { change_draft_clean: "false" }
1757
+ - id: warn_change_draft_unread
1758
+ condition: "{{draft_change_go}} == true"
1759
+ action: verify_context
1760
+ require_true: change_draft_clean
1761
+ on_false: continue
1762
+ on_false_message: "API DRAFT — {{change_draft_unread}} endpoint shape(s) in {{change_draft_plan}} are not written as `{ field: type }`, so the frontend's client gets that data untyped: {{change_draft_unread_text}}. Write each endpoint the change adds or changes as a whole block under its endpoints section, the way the API plan does, then re-run plan {{cr_id}} or analyze-plan-delta."
1763
+
1431
1764
  # 6b. CROSS-SLICE EDGES, from the document that states them. `aw-xref record` reads the
1432
1765
  # MASTER service plan — §1 References-by-id and the External integrations table — and
1433
1766
  # writes a Cross-References row into every sibling aggregate this one uses. Its input
@@ -1468,10 +1801,11 @@ workflow:
1468
1801
  # conformance gate) → implement. No change is opened either way.
1469
1802
  # • DELTA → the chosen change stays OPEN with filled delta plans. Ship it via the CR
1470
1803
  # chain: /appweaver analyze-plan-delta → implement → build → analyze-slice-delta
1471
- # → finalize-delta (merges the delta into spec.md + domain-model.md — not the lane
1472
- # plans, which are derived from those two; the change stays OPEN) → analyze-slice
1473
- # → close-delta (archives the change, bumps the minor version). Re-deriving the
1474
- # masters from the merged documents is `plan main` on a later run.
1804
+ # → finalize-delta (merges the delta into spec.md + domain-model.md, and its delta
1805
+ # API plan into api-plan.md — not the lane plans, which are derived from those; the
1806
+ # change stays OPEN) → analyze-slice → close-delta (archives the change, bumps the
1807
+ # minor version). Re-deriving the masters from the merged documents is `plan main`
1808
+ # on a later run.
1475
1809
 
1476
1810
  # 7. Post-execution hooks.
1477
1811
  - id: resolve_post_hooks
@@ -1488,6 +1822,8 @@ workflow:
1488
1822
  outputs:
1489
1823
  feature_dir: "{{feature_dir}}"
1490
1824
  bounded_context: "{{context}}"
1825
+ api_plan: "{{api_plan}}" # api-plan.md — the endpoints the service publishes, the API contract the frontend binds to
1826
+ api_mode: "{{api_mode}}" # author | update | skip (not the main path, not your lane, or no split moved the endpoints out of the backend plan)
1491
1827
  backend_plan: "{{backend_plan}}" # empty when no backend lane is declared
1492
1828
  frontend_plan: "{{frontend_plan}}" # empty when no frontend lane is declared
1493
1829
  frontend_excluded: "{{fe_excluded}}" # true = a frontend lane is declared and this feature records no surface in it (feature-lanes)
@@ -1500,7 +1836,8 @@ outputs:
1500
1836
  version: "{{version}}"
1501
1837
  change_id: "{{active_change}}"
1502
1838
  source_count: "{{source_count}}"
1503
- api_draft: "{{draft_path}}" # api-draft.json, drafted by script from the backend plan's API section ({{draft_ops}} operation(s)) — commit it with the plan
1504
- contract_requests: "{{req_summary}}" # the frontend plan's RQ-n requests and the backend plan's answers
1505
- note: "This run was pointed at `{{plan_target}}` (plan_mode {{plan_mode}}); backend lane {{be_mode}}, frontend lane {{fe_mode}}. Lanes come from .appweaver/stacks.json: backend declared = {{be_declared}}, frontend declared = {{fe_declared}}; your working lane is `{{working_lane}}` (.appweaver/lane.json — backend worked = {{be_working}}, frontend worked = {{fe_working}}). A working lane gets a plan; a declared lane you do not work on is read, never written, and an undeclared one gets nothing — neither is a failure. Contract requests: {{req_summary}} — the frontend plan raises them in §5 'Contract deltas needed' and the backend plan answers them in its API section; commit the plans so the other lane's developer sees them. A lane whose mode is `update` had its MASTER re-derived from the current spec and model, with its DA-n and WA-n ledgers carried forward — that rewrite was confirmed before it ran. The domain model at {{domain_model}} was READ, never written — it is spec-tier, and this phase records where each invariant is ENFORCED rather than what it says."
1506
- next: "MAIN (masters authored or re-derived): /appweaver analyze-plan (spec↔plan gate) → implement. DELTA (change {{active_change}} open, delta plans authored): /appweaver analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta — implement is the same command on both paths and ASKS which open change it is building, so pass the id (/appweaver implement {{active_change}}) to skip the prompt (finalize-delta merges the delta into spec.md and domain-model.md — not the lane plans, which are derived from those two — but leaves the change open; analyze-slice verifies the slice against the merged masters; close-delta archives the change and bumps the minor version). Once a change has merged, /appweaver plan main re-derives the masters from the documents it merged into."
1839
+ api_draft: "{{draft_path}}" # api-draft.json, drafted by script from {{draft_plan}} ({{draft_ops}} operation(s)) — the API contract the frontend binds to; commit it with the plans
1840
+ change_api_draft: "{{change_draft_path}}" # on the delta path: changes/<CR-id>/api-draft.json, the change's API changes — commit it with the change
1841
+ contract_requests: "{{req_summary}}" # the frontend plan's RQ-n requests and the API plan's answers
1842
+ note: "This run was pointed at `{{plan_target}}` (plan_mode {{plan_mode}}); backend lane {{be_mode}}, frontend lane {{fe_mode}}. Lanes come from .appweaver/stacks.json: backend declared = {{be_declared}}, frontend declared = {{fe_declared}}; your working lane is `{{working_lane}}` (.appweaver/lane.json — backend worked = {{be_working}}, frontend worked = {{fe_working}}). A working lane gets a plan; a declared lane you do not work on is read, never written, and an undeclared one gets nothing — neither is a failure. Contract requests: {{req_summary}} — the frontend plan raises them in §5 'Contract deltas needed' and the API plan answers them; commit the plans so the other lane's developer sees them. The API plan: api_mode {{api_mode}} — `author` or `update` means it was written before the backend plan, which realises its endpoints; `skip` means this run did not write it (not the main path, not your lane, or the endpoints are still in the backend plan). A lane whose mode is `update` had its MASTER re-derived from the current spec and model, with its DA-n and WA-n ledgers carried forward — that rewrite was confirmed before it ran. The domain model at {{domain_model}} was READ, never written — it is spec-tier, and this phase records where each invariant is ENFORCED rather than what it says."
1843
+ next: "MAIN (masters authored or re-derived): /appweaver analyze-plan (spec↔plan gate) → implement. DELTA (change {{active_change}} open, delta plans authored): /appweaver analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta — implement is the same command on both paths and ASKS which open change it is building, so pass the id (/appweaver implement {{active_change}}) to skip the prompt (finalize-delta merges the delta into spec.md and domain-model.md, and its delta API plan into api-plan.md — not the lane plans, which are derived from those — but leaves the change open; analyze-slice verifies the slice against the merged masters; close-delta archives the change and bumps the minor version). Once a change has merged, /appweaver plan main re-derives the masters from the documents it merged into."