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,8 +2,9 @@
2
2
  # aw-spec-adopt (orchestrator) — the JUST-IN-TIME per-feature step of Brownfield DEV
3
3
  #
4
4
  # Develop an EXISTING feature by first reverse-engineering ONE aggregate / vertical
5
- # slice from its code into a living spec.md, the bounded context's domain-model.md and
6
- # aligned lane plans (+ a source-map.md sidecar), THEN reconciling this feature's evidence into the project governance that
5
+ # slice from its code into a living spec.md, the bounded context's domain-model.md, the
6
+ # API the slice publishes (api-plan.md) and aligned lane plans (+ a source-map.md sidecar),
7
+ # THEN reconciling this feature's evidence into the project governance that
7
8
  # /appweaver onboard established. Adopt is the inverse of implement: implement reads the
8
9
  # plans + the model and generates code; adopt reads code and generates the spec, the model
9
10
  # and the plans — equally a reasoning step, stamped `origin: reverse-engineered` with
@@ -28,9 +29,12 @@
28
29
  # project default). So adopt = reverse an EXISTING feature; the forward pipeline = a
29
30
  # new one. Both then evolve through the CR delta loop.
30
31
  #
31
- # The reverse output is a NORMAL feature: spec.md, domain-model.md and backend-plan.md /
32
- # frontend-plan.md are the very documents the CR delta loop + forward pipeline consume
33
- # unchanged — under those names, so nothing downstream needs a brownfield special case.
32
+ # The reverse output is a NORMAL feature: spec.md, domain-model.md, api-plan.md and
33
+ # backend-plan.md / frontend-plan.md are the very documents the CR delta loop + forward
34
+ # pipeline consume unchanged — under those names, so nothing downstream needs a brownfield
35
+ # special case. The API plan is written from the kit's api-plan-template, which onboard
36
+ # does not override: its endpoint block form is the one the contract scripts read in every
37
+ # project, so only the plans around it follow the code's own structure.
34
38
  # plan-scaffold's backend/frontend lanes also MIGRATE a legacy design.md / design.ui.md left
35
39
  # by an adopt that ran before the rename, so an already-adopted repo is carried forward. All brownfield provenance
36
40
  # lives in `origin:` frontmatter + the source-map sidecar. Adopt inits
@@ -71,7 +75,24 @@
71
75
  # =============================================================================
72
76
 
73
77
  name: aw-spec-adopt
74
- description: The JIT per-feature step of brownfield development — reverse-engineer ONE existing feature into a living spec.md, an aggregate section in the bounded context's shared domain-model.md, and backend-plan.md (+ frontend-plan.md where a frontend lane is declared and this slice owns part of its surface) + source-map.md (origin reverse-engineered, per-item confidence, code-derived plan template), then COMPLETE its shallow OKF concept (deepen it to the reversed reality + link the deep artifacts) and record its cross-slice edges. The spec cites BR-nnn by id and the model defines them, exactly as on the greenfield path, so a reversed slice is checkable by the same gates as any other. Fidelity gate is spec + model + plans vs code, and an invented entity, boundary or invariant is a fabrication exactly as an invented FR- is; the review is reported for out-of-band closure. Every reverse step is scoped to the feature's own operations from the confirmed boundary map, so two features over one aggregate never claim the same operation, and the spec continues the context's BR-nnn numbering from its domain model. Where a frontend lane is declared and this slice owns none of its surface, the feature records lanes=backend so later phases neither author nor demand a frontend plan for it. The governance reconcile — carrying this feature's real evidence into the provisional rule files + constitution onboard wrote — is HELD (reconcile_governance=false), so an ordinary run does not touch the governance tier. A NEW feature is authored via the forward pipeline instead. Output is a normal feature for the CR + forward loop.
78
+ description: The JIT per-feature step of brownfield development — reverse-engineer ONE existing feature into a living spec.md, an aggregate section in the bounded context's shared domain-model.md, and its lane plans — api-plan.md (the endpoints the slice publishes, with the operation ids the code gives them) and backend-plan.md where a backend lane is declared and the slice has service code, frontend-plan.md where a frontend lane is declared and this slice owns part of its surface — + source-map.md (origin reverse-engineered, per-item confidence, code-derived plan template), then COMPLETE its shallow OKF concept (deepen it to the reversed reality + link the deep artifacts), record its cross-slice edges and draw its API plan into api-draft.json, the API the frontend lane binds to. The spec cites BR-nnn by id and the model defines them, exactly as on the greenfield path, so a reversed slice is checkable by the same gates as any other. Fidelity gate is spec + model + plans vs code, and an invented entity, boundary or invariant is a fabrication exactly as an invented FR- is; the review is reported for out-of-band closure. Every reverse step is scoped to the feature's own operations from the confirmed boundary map, so two features over one aggregate never claim the same operation, and the spec continues the context's BR-nnn numbering from its domain model. Where a frontend lane is declared and this slice owns none of its surface, the feature records lanes=backend so later phases neither author nor demand a frontend plan for it. The governance reconcile — carrying this feature's real evidence into the provisional rule files + constitution onboard wrote — is HELD (reconcile_governance=false), so an ordinary run does not touch the governance tier. A NEW feature is authored via the forward pipeline instead. Output is a normal feature for the CR + forward loop.
79
+ # 2.5.0 — the endpoints the slice publishes are reversed into the feature's own api-plan.md,
80
+ # before the backend plan, which realises them and points to it; the frontend plan
81
+ # takes its operation ids from the API plan and never reads the backend plan; the
82
+ # source-map and the fidelity check cover the API plan too; once fidelity passes,
83
+ # the API plan is drawn into api-draft.json, so the slice is in the frontend's
84
+ # combined document from the start.
85
+ # 2.3.0 — the SERVICE half is reversed only where the slice has one (svc_lane: a backend lane
86
+ # is declared and the slice has service files), the twin of ui_lane. A frontend-only
87
+ # project gets no backend plan and no cross-slice edges, and its model is read from
88
+ # the UI files; a UI-only slice in a repo with both lanes records lanes=[frontend].
89
+ # A failed fidelity check names the two ways to resolve it — re-adopt, or correct
90
+ # the documents by hand — and what each leaves behind.
91
+ # 2.2.0 — the reversed plans take part in the API ledger between the lanes: the backend plan
92
+ # writes each endpoint the slice publishes as an endpoint block (`#### `VERB /route``
93
+ # + **Operation id**, the id the code publishes — `—` and a DA-n where it publishes
94
+ # none, never an invented one) with a "Requests from the frontend lane" table, and the
95
+ # frontend plan's Contract Binding rows cite those ids.
75
96
  # 2.1.0 — every reverse step is scoped to the FEATURE, not its aggregate: the boundary map's
76
97
  # operation list (and a sibling feature's operations, which are not this one's)
77
98
  # reaches the spec, model, plan, UI plan, source-map and fidelity steps. The spec step
@@ -87,7 +108,7 @@ description: The JIT per-feature step of brownfield development — reverse-engi
87
108
  # 1.3.0 — reverse design scaffolds from the DEFAULT `design-template` (onboard's code-derived
88
109
  # override) instead of the never-shipped `brownfield-design-template`, unifying the
89
110
  # whole brownfield pipeline on one code-derived template.
90
- version: 2.1.0
111
+ version: 2.5.0
91
112
  agent_agnostic: true
92
113
  dialect: aw-spec.orchestrator/v1
93
114
 
@@ -128,8 +149,12 @@ vars:
128
149
  be_declared: "false"
129
150
  fe_no_surface: "false" # a frontend lane is declared, this slice owns none of its surface, and a backend lane carries it
130
151
  feature_lanes: "" # what record_lanes wrote — empty when the feature follows every declared lane
152
+ xref_edge_count: "0" # xref_record runs only where the slice has a service half
131
153
  fe_root: ""
132
154
  ui_lane: "false" # THIS slice reverses a UI half: a frontend lane is declared AND the slice owns UI files
155
+ svc_lane: "false" # THIS slice reverses a SERVICE half: a backend lane is declared AND the slice has service files
156
+ be_no_surface: "false" # a backend lane is declared, this slice has no service code, and a frontend lane carries it
157
+ model_files: "" # what the model is reversed from: the service files, or the UI files on a slice with none
133
158
  ui_count: "0" # how many UI files the concept attributes to this aggregate (aw-inventory ui_source_files)
134
159
  ui_source_files: ""
135
160
  frontend_plan: ""
@@ -149,6 +174,21 @@ vars:
149
174
  # left by an earlier adopt, so a repo that was adopted before the rename is carried
150
175
  # forward rather than left with two spellings of one master.
151
176
  backend_plan: ""
177
+ # THE API THE SLICE PUBLISHES — its own document, reversed before the backend plan, which
178
+ # realises each endpoint. `api_blocked` is the scaffold refusing because a backend plan
179
+ # already in the folder carries the endpoints: that plan keeps them for this run, as
180
+ # before API plans existed, and the frontend plan reads it in their place (`api_read`).
181
+ api_plan: ""
182
+ api_blocked: "false"
183
+ api_reverse: "false"
184
+ api_read: ""
185
+ draft_path: "" # api-draft.json, drawn after fidelity passes (contract_draft)
186
+ draft_plan: ""
187
+ draft_ops: "0"
188
+ draft_unread: "0"
189
+ draft_unread_text: ""
190
+ draft_clean: "true"
191
+ be_api_rule: "THE ENDPOINTS ARE IN api-plan.md (in context), reversed just before this document from the same code: the API contract the frontend lane binds to. Do NOT restate them here — the §2 axis that publishes them carries the one line pointing to api-plan.md, and where the template has an endpoint block or a requests table there (a template generated before API plans existed), that line goes in their place. REALISE every endpoint instead: each names under **Maps to** the code unit that serves it, and that unit is this document's element, under the same name, with request and response shapes that match the endpoint's. An endpoint with no element here is unrealised. The traceability spine cites an endpoint as `VERB /route`."
152
192
  # THE BOUNDED CONTEXT'S DOMAIN MODEL — shared by every feature in the context, and the
153
193
  # authority on the aggregate root, its entities and the TEXT of every BR-nnn. Reversed
154
194
  # here in the same run as the spec, exactly as greenfield `specify` authors the two
@@ -157,6 +197,10 @@ vars:
157
197
  context_dir: ""
158
198
  domain_model: ""
159
199
  domain_model_exists: "false"
200
+ model_guard_ok: "false" # model-guard-check's verdict on the reverse_model rewrite. DEFAULTS
201
+ # TO REFUSE: only the check's own `ok` lets the run past the gate
202
+ model_guard_losses: "" # what the rejected rewrite lost, one clause per item
203
+ model_guard_rejected: "" # where the rejected rewrite was kept
160
204
  spec_exists: "false"
161
205
  fidelity_ok: "false"
162
206
  rulemap_exists: "false" # governance-present signal (onboard ran) — bound from resolve-domain
@@ -424,14 +468,6 @@ workflow:
424
468
  outputs:
425
469
  - { name: domain_model, from: "$.domain_model" }
426
470
 
427
- - id: scaffold_plan
428
- action: execute_script
429
- script: { ref: actions }
430
- subcommand: plan-scaffold
431
- args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"] # `design-template` = onboard's code-derived override (greenfield base if onboard has not run)
432
- outputs:
433
- - { name: backend_plan, from: "$.plan_doc" }
434
-
435
471
  - id: version_init
436
472
  action: execute_script
437
473
  script: { ref: actions }
@@ -502,6 +538,100 @@ workflow:
502
538
  outputs:
503
539
  - { name: feature_lanes, from: "$.lanes" }
504
540
 
541
+ # 2d. DOES THIS SLICE HAVE A SERVICE HALF? The twin of 2b: a backend lane must be DECLARED
542
+ # and this aggregate must have service files (the concept's source_files). A
543
+ # frontend-only project declares no backend lane, and its concepts list every file as
544
+ # ui_source_files, so a backend plan there would describe code that does not exist and
545
+ # the fidelity gate would score it as fabricated. Three ordered sets, as in 2b.
546
+ - id: svc_lane_off
547
+ action: set
548
+ vars: { svc_lane: "false" }
549
+ - id: svc_lane_on
550
+ action: set
551
+ condition: "{{be_declared}}"
552
+ vars: { svc_lane: "true" }
553
+ - id: svc_lane_no_code
554
+ action: set
555
+ condition: "{{source_count}} == 0"
556
+ vars: { svc_lane: "false" }
557
+
558
+ # 2e. RECORD A UI-ONLY SLICE in a repo that declares both lanes — the twin of 2c, so plan
559
+ # and the analyze gates do not demand a backend plan for a slice with no service code.
560
+ - id: be_no_surface_off
561
+ action: set
562
+ vars: { be_no_surface: "false" }
563
+ - id: be_no_surface_declared
564
+ action: set
565
+ condition: "{{be_declared}}"
566
+ vars: { be_no_surface: "true" }
567
+ - id: be_no_surface_has_code
568
+ action: set
569
+ condition: "{{source_count}} != 0"
570
+ vars: { be_no_surface: "false" }
571
+ - id: be_no_surface_no_frontend
572
+ action: set
573
+ condition: "{{ui_lane}} != true"
574
+ vars: { be_no_surface: "false" }
575
+ - id: record_lanes_frontend
576
+ condition: "{{be_no_surface}} == true"
577
+ action: execute_script
578
+ script: { ref: actions }
579
+ subcommand: feature-lanes
580
+ args: ["--feature-dir", "{{feature_dir}}", "--set", "frontend"]
581
+ outputs:
582
+ - { name: feature_lanes, from: "$.lanes" }
583
+
584
+ # What the domain model is reversed FROM. The service files where the slice has them; a
585
+ # slice with none keeps its domain in the UI code — the types, states and rules a
586
+ # frontend-only app holds — so the model is read from those.
587
+ - id: model_files_service
588
+ action: set
589
+ vars: { model_files: "{{source_files}}" }
590
+ - id: model_files_ui
591
+ condition: "{{svc_lane}} != true"
592
+ action: set
593
+ vars: { model_files: "{{ui_source_files}}" }
594
+
595
+ # THE API PLAN FIRST, where the slice has a service half — scaffolded from the kit's
596
+ # api-plan-template before the backend plan, as `plan` does it: the scaffold refuses
597
+ # (`blocked`) while a backend plan in the folder carries the API, and a backend plan
598
+ # scaffolded from a template generated before API plans existed carries an empty requests
599
+ # table, so it must not be there yet.
600
+ - id: scaffold_api_plan
601
+ condition: "{{svc_lane}} == true"
602
+ action: execute_script
603
+ script: { ref: actions }
604
+ subcommand: plan-scaffold
605
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "api"]
606
+ outputs:
607
+ - { name: api_plan, from: "$.plan_doc" }
608
+ - { name: api_blocked, from: "$.blocked" }
609
+
610
+ - id: scaffold_plan
611
+ condition: "{{svc_lane}} == true"
612
+ action: execute_script
613
+ script: { ref: actions }
614
+ subcommand: plan-scaffold
615
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"] # `design-template` = onboard's code-derived override (greenfield base if onboard has not run)
616
+ outputs:
617
+ - { name: backend_plan, from: "$.plan_doc" }
618
+
619
+ # WHERE THE API IS REVERSED, and what the frontend plan reads of the service: the API plan
620
+ # just scaffolded — or, where the scaffold refused, the backend plan, which then keeps its
621
+ # endpoint blocks for this run. Nothing where the slice has no service half.
622
+ - id: api_reverse_arm
623
+ condition: "{{svc_lane}} == true"
624
+ action: set
625
+ vars: { api_reverse: "true", api_read: "{{api_plan}}" }
626
+ - id: api_reverse_blocked
627
+ condition: "{{api_blocked}} == true"
628
+ action: set
629
+ vars:
630
+ api_plan: ""
631
+ api_reverse: "false"
632
+ api_read: "{{backend_plan}}"
633
+ be_api_rule: "EVERY ENDPOINT THIS FEATURE PUBLISHES IS AN ENDPOINT BLOCK IN THIS DOCUMENT — a backend plan already in the folder carries the API, so it stays here for this run — in the §2 axis that publishes it: the heading `#### `VERB /route` — <purpose> · Serves <US/FR ids>`, then **Operation id**, Auth, Request, Success, the Errors table and Maps to, exactly as the template's endpoint block — never a row in a table of endpoints. The contract scripts read this form: the API draft is written from the headings, and the request ledger between the lanes reads the operation ids. The **Operation id** is the id the code publishes the endpoint under, spelled exactly as the code spells it; the boundary map records each operation's published id where the code declares one ('published as …' above). Where the code publishes none, write `—` and record a §5 DA-n saying the endpoint has no published operation id — never invent one: the service's as-built API document would not carry it, so the frontend would bind to an id the service does not answer, and analyze-slice would report it. After the endpoint blocks, a `#### Requests from the frontend lane` heading with `None` under it: a frontend reversed from the same code binds only to what the service already serves, so no request is open yet."
634
+
505
635
  # Scaffold the LANE's plan from ITS template pair (--lane frontend resolves
506
636
  # ui-design-template -> frontend-plan.md), the twin of scaffold_plan above. onboard
507
637
  # GENERATED that override from this repo's own UI source, so the reversed frontend plan
@@ -671,6 +801,18 @@ workflow:
671
801
  - { path: "{{spec_file}}" } # the reversed spec — the BR-nnn ids this model must define
672
802
  - { path: "{{concept_path}}" } # source_files + seam
673
803
  - { path: .appweaver/memory/constitution.domain.md, optional: true }
804
+
805
+ # THE REWRITE IS CHECKED. The engine writes the agent's reply over the WHOLE model, which
806
+ # on a context's second slice holds the sections, BR-/XBR- rules and ledger rows the
807
+ # features adopted or specified before it rely on. The snapshot copies it aside; the check
808
+ # after the step compares the two (python/aw-model-guard.py) and, on a loss, puts the
809
+ # model back and keeps the rewrite as domain-model.rejected.md.
810
+ - id: reverse_model_snapshot
811
+ action: execute_script
812
+ script: { ref: actions }
813
+ subcommand: model-guard-snapshot
814
+ args: ["--model", "{{domain_model}}"]
815
+
674
816
  - id: reverse_model
675
817
  action: agent_author
676
818
  include_standards: false # the technical/arch sticky set is for the plans, never the model
@@ -714,6 +856,12 @@ workflow:
714
856
  already there — other specs cite their ids, and a renumbered BR breaks every
715
857
  citation silently. Add `{{feature_name}}` to the `Serving specs` line of any
716
858
  existing aggregate this slice also acts on.
859
+ - A SCRIPT COMPARES YOUR FILE WITH THE MODEL AS IT WAS, and puts the old one back if
860
+ any of these is gone: an `## Aggregate:` section under its name; a BR-n or XBR-n,
861
+ in the same section, with the same rule text, defined once; a name on an
862
+ aggregate's `Serving specs` line; a Ubiquitous Language term; a context the
863
+ Context Map names; a Change History row; the Context version. Add to any of them
864
+ freely; never remove, rename, renumber, move or reword one.
717
865
  - MODEL WHAT THE CODE EXHIBITS, NOT WHAT GOOD DESIGN WOULD WANT. Classify entities
718
866
  vs value objects by whether the code gives the thing an IDENTITY; draw the
719
867
  boundary where the code actually draws it (what it loads and saves together).
@@ -734,6 +882,96 @@ workflow:
734
882
  - Add `origin: reverse-engineered` near the identity block. Do NOT write the Context
735
883
  version or the Change History version column — scripts stamp those.
736
884
 
885
+ Source files to read (the slice's code — its UI files where it has no service code,
886
+ the one place a frontend-only app keeps its types, states and rules): {{model_files}}
887
+
888
+ - id: reverse_model_check
889
+ action: execute_script
890
+ script: { ref: actions }
891
+ subcommand: model-guard-check
892
+ args: ["--model", "{{domain_model}}"]
893
+ outputs:
894
+ - { name: model_guard_ok, from: "$.ok" }
895
+ - { name: model_guard_losses, from: "$.losses_text" }
896
+ - { name: model_guard_rejected, from: "$.rejected" }
897
+
898
+ # On a stop the model is already restored; this slice's spec.md is written, so the
899
+ # already-adopted guard would refuse a plain re-run — the feature folder goes first.
900
+ - id: reverse_model_guard
901
+ action: verify_context
902
+ require_true: model_guard_ok
903
+ on_false: stop
904
+ on_false_message: "The domain model lost content other features rely on, so {{domain_model}} was put back as it was before this run. Lost: {{model_guard_losses}}. The rejected rewrite is kept at {{model_guard_rejected}} for inspection. This slice's spec.md was already written, so remove {{feature_dir}} and run /appweaver adopt {{feature_name}} again."
905
+
906
+ # ═══════════ REVERSE API PLAN — the endpoints the slice publishes, AS-BUILT ═══
907
+ # The contract between the lanes, reversed before the backend plan exactly as `plan`
908
+ # authors it first: the backend plan then realises each endpoint, and the frontend plan
909
+ # binds to the operation ids written here. One template in every project — the kit's
910
+ # api-plan-template, whose endpoint block form is what the contract scripts read — so the
911
+ # code decides which endpoints there are and what they carry, never the document's shape.
912
+ - id: ctx_reverse_api
913
+ condition: "{{api_reverse}} == true"
914
+ action: build_context
915
+ context:
916
+ - { path: "{{api_plan}}" } # the scaffolded template = the fixed endpoint block form
917
+ - { path: "{{spec_file}}" } # the reversed spec = the US/FR ids each endpoint serves
918
+ - { path: "{{domain_model}}" } # the ubiquitous language, and the BR-nnn an error answers to — READ, never written
919
+ - { path: "{{concept_path}}" } # source_files + seam
920
+ - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract, and The API plan in it
921
+ - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — the protocol the service publishes
922
+ - id: reverse_api
923
+ condition: "{{api_reverse}} == true"
924
+ action: agent_author
925
+ include_standards: true # the technical sticky set (reference only — the CODE wins)
926
+ tools: Read # grounded in the CODE, which arrives as {{source_files}} paths
927
+ output_target: { type: file, path: "{{api_plan}}" }
928
+ task: |
929
+ Reverse-engineer the API PLAN of the feature '{{feature_name}}' (the target
930
+ api-plan.md in context — the kit's API plan template) FROM ITS CODE: every endpoint
931
+ the slice publishes for this feature. It is the API contract between the lanes — the
932
+ frontend plan binds to its operation ids, a script drafts it into api-draft.json,
933
+ analyze-slice checks the built service against it — and it is written BEFORE the
934
+ backend plan, which then realises each endpoint. READ the source files listed below;
935
+ the code is the SOURCE OF TRUTH.
936
+ Rules:
937
+ - DESCRIBE THE API AS-BUILT, quirks included: each endpoint's route, verb,
938
+ parameters, request and response fields, status codes and error bodies exactly as
939
+ the code serves them, with the field names as they appear on the wire. Never tidy
940
+ a route, rename a field or add a status the code does not return.
941
+ - THE FEATURE'S SCOPE. {{operations_note}} An endpoint that serves only a sibling
942
+ feature's operation is that feature's and is left out, even though its file is
943
+ listed; a read both features call is this feature's too.
944
+ - KEEP THE TEMPLATE'S FORM EXACTLY: one block per endpoint under "## Endpoints" — the
945
+ heading `### `VERB /route` — purpose · Serves …` citing the reversed spec's US/FR
946
+ ids, then **Operation id**, **Auth**, **Request**, **Success**, the **Errors** table
947
+ and **Maps to**. The contract scripts read exactly this form; an endpoint written
948
+ as a table row or in prose reaches neither api-draft.json nor the frontend.
949
+ - EVERY SHAPE IS `{ field: type }` in conceptual types (string, int, decimal, bool,
950
+ enum(A|B), list<T>, id<T>, money, date, datetime), never the code's own types. A
951
+ shape in prose reaches the frontend's client untyped.
952
+ - THE OPERATION ID is the id the code publishes the endpoint under, spelled exactly as
953
+ the code spells it; the boundary map records each operation's published id where
954
+ the code declares one (the scope line above). Where the code publishes none, write
955
+ `—` and say so in the block — never invent one: the service's as-built API document
956
+ would not carry it, so the frontend would bind to an id the service does not
957
+ answer, and analyze-slice would report it.
958
+ - **Maps to** names the code's own unit that serves the endpoint — its handler,
959
+ use-case or service operation — by the name the code gives it; the backend plan,
960
+ reversed next from the same code, realises it under that name.
961
+ - THE ERROR CONTRACT is what the code returns: one row per failure it actually
962
+ produces, with its status, when it is returned and its body shape. An error the
963
+ code never produces is not written.
964
+ - Under "## Requests from the frontend lane" write `None`: a frontend reversed from
965
+ the same code binds only to what the service already serves, so no request is
966
+ open yet.
967
+ - A SERVICE THAT PUBLISHES ANOTHER PROTOCOL (messages, remote procedures) states each
968
+ operation in the same block form, headed by the code's own name for it.
969
+ - Write `None` under "## Endpoints" when the slice publishes no endpoint.
970
+ - TECHNOLOGY-AGNOSTIC: protocol level only — no controller code, framework
971
+ attributes, serializer settings or language types.
972
+ - Add `origin: reverse-engineered` near the Version field. This is v1.0: write the
973
+ first Change History row.
974
+
737
975
  Source files to read (the slice's code): {{source_files}}
738
976
 
739
977
  # ═══════════ REVERSE BACKEND PLAN — from the SPEC + MODEL + CODE, AS-BUILT ═══
@@ -744,16 +982,19 @@ workflow:
744
982
  # backend-plan.md IS onboard's code-derived `design-template` override — its §2
745
983
  # component sections follow the code's own folders/modules, not a fixed layering.
746
984
  - id: ctx_reverse_plan
985
+ condition: "{{svc_lane}} == true"
747
986
  action: build_context
748
987
  context:
749
988
  - { path: "{{backend_plan}}" } # the scaffolded code-derived template = structure + embedded guidance
750
989
  - { path: "{{spec_file}}" } # the reversed spec = WHAT the slice must do
990
+ - { path: "{{api_plan}}", optional: true } # the reversed API = the endpoints this plan realises — absent where the scaffold refused
751
991
  - { path: "{{domain_model}}" } # the reversed model = what the domain IS — READ, never written here
752
992
  - { path: "{{concept_path}}" } # source_files + seam (structure hints)
753
993
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract (see its Brownfield variant note)
754
994
  - { path: "{{bundle_dir}}/architecture/overview.md", optional: true } # the as-built architecture (inventory) — cite, don't re-derive
755
995
  - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
756
996
  - id: reverse_plan
997
+ condition: "{{svc_lane}} == true"
757
998
  action: agent_author
758
999
  include_standards: true # the technical/architecture sticky set (reference only — the CODE wins)
759
1000
  tools: Read # grounded in the CODE, which arrives as {{source_files}} paths
@@ -798,6 +1039,7 @@ workflow:
798
1039
  the EXTERNAL INTEGRATIONS table (heading contains "Integration") realising the
799
1040
  spec's IC-. Realise CN- (structural → §1.2 invariants/events; retention/audit →
800
1041
  §2 persistence / §3 cross-cutting), DC- (§3 + projection surface).
1042
+ - {{be_api_rule}}
801
1043
  - AS-BUILT DEBT, not architecture drift. §6 records ONLY debt the CODE ITSELF
802
1044
  evidences — stubs/no-ops, dead or duplicate code, torn seams (shared table/
803
1045
  transaction), TODOs, hard-coded values. The code being organised differently
@@ -816,8 +1058,9 @@ workflow:
816
1058
  # ═══════ REVERSE UI DESIGN — the LANE's half, from the UI code, AS-BUILT ═══
817
1059
  # Runs only where this slice HAS a surface (ui_lane). The service design above is
818
1060
  # NOT its parent: backend-plan.md realises the spec's FR/BR/CN-, frontend-plan.md
819
- # realises the spec's UX- and the user stories' interaction, and it CONSUMES the backend
820
- # plan's contracts rather than restating them. Both are in context for exactly that reason.
1061
+ # realises the spec's UX- and the user stories' interaction, and it CONSUMES the API
1062
+ # plan's contracts rather than restating them. The spec and the API plan are in context
1063
+ # for exactly that reason; the backend plan, the service's internals, is not.
821
1064
  # THE FRONTEND'S RULES REPLACE THE BACKEND'S before the lane's plan is authored — the
822
1065
  # swap the forward `plan` makes at the same boundary. Sticky standards only accumulate,
823
1066
  # so without it the UI plan was authored under the service rule set and none of the
@@ -835,7 +1078,7 @@ workflow:
835
1078
  context:
836
1079
  - { path: "{{frontend_plan}}" } # the scaffolded code-derived lane template = structure + embedded guidance
837
1080
  - { path: "{{spec_file}}" } # the reversed spec — UX- + the user stories this surface serves
838
- - { path: "{{backend_plan}}" } # the backend plan — the contracts the UI consumes, never re-specified here
1081
+ - { path: "{{api_read}}", optional: true } # the API plan — the contracts the UI consumes, never re-specified here; absent with no service lane
839
1082
  - { path: "{{domain_model}}", optional: true } # the ubiquitous language labels and states must use — READ, never written
840
1083
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's UX-* base, reversed by onboard from this same UI
841
1084
  - { path: "{{bundle_dir}}/architecture/frontend.md", optional: true } # the as-built UI architecture + the SHARED surface no slice owns
@@ -861,11 +1104,20 @@ workflow:
861
1104
  operation — typically that feature's own UI folder — is that feature's. Where this
862
1105
  feature's screen hosts it, cite it as the sibling's; never plan it here.
863
1106
  - STAY IN THE LANE. Realise the spec's Experience Standards (UX-) and the
864
- interaction its User Stories imply. The backend plan (in context) supplies
865
- the CONTRACTS this surface consumes — cite them; do NOT restate, re-derive or
866
- contradict them, and introduce no server-side behaviour here. Where the domain
1107
+ interaction its User Stories imply. The API plan (in context — api-plan.md, or the
1108
+ backend plan where this feature's endpoints stayed in it) supplies the CONTRACTS
1109
+ this surface consumes — cite them; do NOT restate, re-derive or contradict them,
1110
+ and introduce no server-side behaviour here. With no API plan in context the
1111
+ service is outside this repo: bind against the routes the UI code calls and the
1112
+ spec's IC-, as an external contract. Where the domain
867
1113
  model is in context, use its UBIQUITOUS LANGUAGE exactly in labels, states and
868
1114
  messages, and cite BR-nnn rather than restating a rule.
1115
+ - CONTRACT BINDING CITES OPERATION IDS. Each row of the Contract Binding table names,
1116
+ in its Operation id column, the **Operation id** the API plan gives the endpoint
1117
+ the UI code calls — `—` where the API plan records none. The id is how
1118
+ implement finds the endpoint, and how a later change that alters or removes it is
1119
+ reported to this lane. The "Contract deltas needed" table reads `None`: the UI calls
1120
+ only what the service already serves.
869
1121
  - STRUCTURE FOLLOWS THE UI CODE. Fill each section from the lane's real shape —
870
1122
  its actual routes and screens, its own composition axes (however it groups
871
1123
  components), where client state and server data-fetching really live, its
@@ -890,7 +1142,8 @@ workflow:
890
1142
  context:
891
1143
  - { path: .appweaver/templates/source-map-template.md } # the sidecar structure
892
1144
  - { path: "{{spec_file}}" }
893
- - { path: "{{backend_plan}}" }
1145
+ - { path: "{{backend_plan}}", optional: true } # the service lane's plan, where this slice has one
1146
+ - { path: "{{api_plan}}", optional: true } # the endpoints the slice publishes, where it has a service half
894
1147
  - { path: "{{frontend_plan}}", optional: true } # the lane's design, where this slice has one
895
1148
  - { path: "{{concept_path}}" } # both file lists to map
896
1149
  - id: source_map
@@ -904,7 +1157,8 @@ workflow:
904
1157
  spine — it keeps spec.md / backend-plan.md clean while recording provenance here.
905
1158
  - One row per legacy artefact in the bundle concept's `source_files` (read
906
1159
  them under {{module_dir}}): map each to the spec ids (FR/BR/US/CN/DC/IC)
907
- and the backend-plan element (§/name) that now describe it, with CONFIDENCE
1160
+ and the backend-plan element (§/name) that now describe it — and, for a file
1161
+ that publishes an endpoint, its `VERB /route` block in api-plan.md — with CONFIDENCE
908
1162
  (certain | inferred). EVERY source file MUST appear — the Fidelity gate
909
1163
  checks full coverage both ways.
910
1164
  - THE CONCEPT IS THE AGGREGATE'S ('{{aggregate_primary}}') and lists every feature's
@@ -945,7 +1199,8 @@ workflow:
945
1199
  context:
946
1200
  - "{{spec_file}}"
947
1201
  - "{{domain_model}}"
948
- - "{{backend_plan}}"
1202
+ - { path: "{{backend_plan}}", optional: true } # the service lane's plan, where this slice has one
1203
+ - { path: "{{api_plan}}", optional: true } # the endpoints it publishes, where this slice has a service half
949
1204
  - { path: "{{frontend_plan}}", optional: true } # the lane's plan, where this slice has one
950
1205
  - "{{feature_dir}}/source-map.md"
951
1206
  - "{{concept_path}}"
@@ -960,7 +1215,10 @@ workflow:
960
1215
  {{module_dir}}). Where frontend-plan.md is in context, it is judged the same way
961
1216
  against the slice's UI files ({{ui_source_files}}) — a screen, state store or
962
1217
  route it describes that the UI code does not have is a fabricated-plan, and a
963
- UI file with no home in the source-map is unmapped-code. Classify findings:
1218
+ UI file with no home in the source-map is unmapped-code. Where api-plan.md is in
1219
+ context, it is judged against the service code the same way: an endpoint the code
1220
+ does not serve, a field or status it does not have, or an operation id the code does
1221
+ not publish is a fabricated-plan. Classify findings:
964
1222
  - BLOCKING (structural — set ok=false if any):
965
1223
  (1) unmapped-code: a source file with NO home in the source-map — one recorded
966
1224
  there as `sibling: <Feature>` has a home;
@@ -1006,7 +1264,13 @@ workflow:
1006
1264
  condition: "{{report_needed}} == true"
1007
1265
  action: agent_author
1008
1266
  include_standards: false
1009
- context: ["{{spec_file}}", "{{domain_model}}", "{{backend_plan}}", "{{feature_dir}}/source-map.md"]
1267
+ context:
1268
+ - "{{spec_file}}"
1269
+ - "{{domain_model}}"
1270
+ - { path: "{{backend_plan}}", optional: true }
1271
+ - { path: "{{api_plan}}", optional: true }
1272
+ - { path: "{{frontend_plan}}", optional: true }
1273
+ - "{{feature_dir}}/source-map.md"
1010
1274
  output_target: { type: file, path: "{{feature_dir}}/adopt-review-report.md" }
1011
1275
  task: |
1012
1276
  Write an Adopt Review report (the target) for the feature '{{feature_name}}' (acting on {{aggregates_csv}}) —
@@ -1014,8 +1278,10 @@ workflow:
1014
1278
  - "# Adopt Review — {{feature_name}}" + today's date + "origin: reverse-engineered".
1015
1279
  - "verdict:" line — "BLOCKING ✗ · <n>" if blocking gaps exist, else "REVIEW ○ · <m> item(s)".
1016
1280
  - "## Blocking gaps" (if any): table | # | kind | artefact | detail | — MUST be
1017
- resolved before the slice is trustworthy (fix by re-reading the code / refining
1018
- spec+design, then re-run adopt-review or re-adopt).
1281
+ resolved before the slice is trustworthy, one of two ways: re-adopt — remove
1282
+ {{feature_dir}} and run /appweaver adopt {{feature_name}} again, which reverses
1283
+ the documents afresh from the code, so edits made to them by hand are not kept —
1284
+ or correct the documents by hand and keep them.
1019
1285
  Blocking gaps: {{blocking_gaps}}
1020
1286
  - "## Review items" (close out of band): table | # | kind | where | confirm |.
1021
1287
  These are inferred-vs-certain calls, as-built quirks, and missing-invariant
@@ -1034,7 +1300,7 @@ workflow:
1034
1300
  action: verify_context
1035
1301
  require_true: fidelity_ok
1036
1302
  on_false: stop
1037
- on_false_message: "Fidelity FAILED for {{feature_name}} — the reversed documents do not match the code (blocking gaps: {{blocking_gaps}}). See {{feature_dir}}/adopt-review-report.md. Fix the unmapped code / remove fabricated items (re-read the source, refine spec.md, {{domain_model}}, backend-plan.md and source-map.md), then re-run /appweaver adopt (after removing the dir) or resolve out of band. A `fabricated-model` gap means the reverse gave this slice structure its code does not have — an entity, a boundary, a lifecycle state or a BR-nnn nothing enforces; the fix is to record the domain as thin as the code really is, never to leave it flattering. A `foreign-operation` gap means a document claims an operation the boundary map gives only to another feature; remove it here — it belongs to that feature's adopt."
1303
+ on_false_message: "Fidelity FAILED for {{feature_name}} — the reversed documents do not match the code (blocking gaps: {{blocking_gaps}}). See {{feature_dir}}/adopt-review-report.md. Resolve it one of two ways. RE-ADOPT: remove {{feature_dir}} and run /appweaver adopt {{feature_name}} again; the documents are reversed afresh from the code, so edits made to them first are not kept. Removing the folder leaves what this run added to {{domain_model}} (git diff shows it), and a re-adopt extends it, so remove that first. CORRECT BY HAND: re-read the source and refine spec.md, {{domain_model}}, the API plan and lane plans this slice has (API: {{api_plan}}; backend: {{backend_plan}}; frontend: {{frontend_plan}}) and source-map.md — map the unmapped code, remove what is fabricated. This run stopped before it completed the OKF concept {{aggregate_primary}}, recorded cross-slice edges and drew the API draft, so on this path the concept and the edges stay as onboard left them and the slice has no api-draft.json until /appweaver analyze-plan draws it. A `fabricated-model` gap means the reverse gave this slice structure its code does not have — an entity, a boundary, a lifecycle state or a BR-nnn nothing enforces; the fix is to record the domain as thin as the code really is, never to leave it flattering. A `foreign-operation` gap means a document claims an operation the boundary map gives only to another feature; remove it here — it belongs to that feature's adopt."
1038
1304
 
1039
1305
  # ═══════════ OKF CONCEPT — COMPLETE the shallow scan concept (deep reverse) ═══
1040
1306
  # onboard wrote this aggregate's OKF concept SHALLOW (a discovery stub — enough to pick the
@@ -1051,7 +1317,9 @@ workflow:
1051
1317
  - { path: "{{concept_path}}" } # the shallow onboard concept to complete
1052
1318
  - { path: "{{spec_file}}" } # the deep reversed spec (entities, US, BR/CN/DC/IC)
1053
1319
  - { path: "{{domain_model}}" } # the deep reversed model (entities, boundary, invariants)
1054
- - { path: "{{backend_plan}}" } # the deep reversed plan (components, ports, integrations)
1320
+ - { path: "{{backend_plan}}", optional: true } # the deep reversed service plan (components, ports, integrations), where the slice has one
1321
+ - { path: "{{api_plan}}", optional: true } # the deep reversed API (the endpoints it publishes), where the slice has a service half
1322
+ - { path: "{{frontend_plan}}", optional: true } # the deep reversed UI plan, where the slice has one
1055
1323
  - { path: "{{feature_dir}}/source-map.md" } # the source anchor (files, seam, quirks)
1056
1324
  - { path: .appweaver/templates/okf-concept-template.md } # OKF v0.1 format authority
1057
1325
  - id: complete_concept
@@ -1063,8 +1331,8 @@ workflow:
1063
1331
  COMPLETE the OKF concept of the aggregate '{{aggregate_primary}}' at {{concept_path}} —
1064
1332
  the aggregate this run adopted the feature '{{feature_name}}' over. onboard wrote it
1065
1333
  SHALLOW (a discovery stub from the whole-repo scan). The deep reverse has now
1066
- produced a TRUSTWORTHY spec.md + domain-model.md + backend-plan.md + source-map.md (in
1067
- context); deepen the
1334
+ produced a TRUSTWORTHY spec.md + domain-model.md + api-plan.md + backend-plan.md +
1335
+ source-map.md (in context, each the slice has); deepen the
1068
1336
  concept to match and LINK those artifacts. Follow the OKF concept template in context.
1069
1337
  THE CONCEPT IS THE AGGREGATE'S, NOT THE FEATURE'S. Where its `features:` names more
1070
1338
  than one feature, another feature's adopt may already have deepened it, and nothing
@@ -1076,8 +1344,9 @@ workflow:
1076
1344
  the authority on them; correct any drift the shallow scan got wrong (the deep docs
1077
1345
  win). A sibling capability's commands and endpoints stay where they are.
1078
1346
  - LINK the deep artifacts: add bundle links to {{feature_dir}}/spec.md,
1079
- backend-plan.md, source-map.md and the context's domain-model.md so the concept
1080
- points at the deep truth. Keep every link another feature's adopt added.
1347
+ backend-plan.md, api-plan.md, source-map.md and the context's domain-model.md (each
1348
+ the slice has) so the concept points at the deep truth. Keep every link another
1349
+ feature's adopt added.
1081
1350
  - UNION `source_files` with the source-map's mapped files (grouped by the code's OWN
1082
1351
  folders, per OKF) — never drop a file already listed, which may be a sibling
1083
1352
  capability's. The boundary map was drawn at the baseline commit, whose time is
@@ -1109,6 +1378,7 @@ workflow:
1109
1378
  # reconcile-on-write helper as forward implement, stamped origin reverse-engineered,
1110
1379
  # additive to the inventory bundle. Runs only after fidelity passes.
1111
1380
  - id: xref_record
1381
+ condition: "{{svc_lane}} == true" # edges are read off the service plan, as forward implement records them
1112
1382
  action: execute_script
1113
1383
  script: { ref: xref }
1114
1384
  subcommand: record
@@ -1117,6 +1387,37 @@ workflow:
1117
1387
  - { name: xref_wrote, from: "$.wrote" }
1118
1388
  - { name: xref_edge_count, from: "$.edge_count" }
1119
1389
 
1390
+ # 3c. THE API DRAFT — api-draft.json beside the plans, drawn by script from the API plan just
1391
+ # reversed (the backend plan, where its endpoints stayed in it): the API contract the
1392
+ # frontend lane's combined document reads and analyze-slice checks the built service
1393
+ # against. Drawn here, after fidelity passed, so the adopted slice is in the combined
1394
+ # document from the start rather than only once plan or analyze-plan next runs for it.
1395
+ # Advisory — a plan the script cannot read leaves the run going; a shape it cannot type
1396
+ # is reported, because the frontend's client gets that data untyped.
1397
+ - id: contract_draft
1398
+ condition: "{{svc_lane}} == true"
1399
+ action: execute_script
1400
+ on_error: continue
1401
+ script: { ref: actions }
1402
+ subcommand: contract-draft
1403
+ args: ["--feature-dir", "{{feature_dir}}"]
1404
+ outputs:
1405
+ - { name: draft_path, from: "$.path" }
1406
+ - { name: draft_plan, from: "$.plan" }
1407
+ - { name: draft_ops, from: "$.operations" }
1408
+ - { name: draft_unread, from: "$.unread_count" }
1409
+ - { name: draft_unread_text, from: "$.unread_text" }
1410
+ - id: draft_unread_flag
1411
+ condition: "{{draft_unread}} != 0"
1412
+ action: set
1413
+ vars: { draft_clean: "false" }
1414
+ - id: warn_draft_unread
1415
+ condition: "{{svc_lane}} == true"
1416
+ action: verify_context
1417
+ require_true: draft_clean
1418
+ on_false: continue
1419
+ 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 API plan template's endpoint block does, with the fields the code puts on the wire, then run /appweaver analyze-plan, which redraws the draft."
1420
+
1120
1421
  # ═══════════ GOVERNANCE RECONCILE — upgrade onboard's PROVISIONAL scaffold ═══
1121
1422
  # ⏸ HELD 2026-07-29: every step in this block is gated on `reconcile_governance`
1122
1423
  # (default "false"), so adopt currently does NOT touch the constitution halves
@@ -1327,13 +1628,17 @@ workflow:
1327
1628
  outputs:
1328
1629
  feature_dir: "{{feature_dir}}"
1329
1630
  spec_file: "{{spec_file}}"
1631
+ api_plan: "{{api_plan}}" # empty where this slice has no service half, or its backend plan keeps the endpoints
1632
+ api_draft: "{{draft_path}}" # api-draft.json drawn from it — empty where the slice has no service half or publishes no endpoint
1633
+ api_draft_operations: "{{draft_ops}}"
1330
1634
  backend_plan: "{{backend_plan}}"
1331
1635
  frontend_plan: "{{frontend_plan}}" # empty where this slice has no UI half
1332
1636
  domain_model: "{{domain_model}}" # the CONTEXT's, shared — this run ADDED an aggregate section to it
1333
1637
  bounded_context: "{{context}}"
1638
+ svc_lane: "{{svc_lane}}"
1334
1639
  ui_lane: "{{ui_lane}}"
1335
1640
  ui_count: "{{ui_count}}"
1336
- feature_lanes: "{{feature_lanes}}" # empty = every declared lane; [backend] = a service-only slice in a repo with a frontend
1641
+ feature_lanes: "{{feature_lanes}}" # empty = every declared lane; [backend] = a service-only slice in a repo with a frontend; [frontend] = a UI-only slice in a repo with a service
1337
1642
  operations: "{{operations_csv}}" # this feature's operations, from the boundary map
1338
1643
  baseline_commit: "{{baseline_commit}}"
1339
1644
  source_map: "{{feature_dir}}/source-map.md"
@@ -1352,4 +1657,4 @@ outputs:
1352
1657
  composed_file: "{{composed_file}}"
1353
1658
  technical_reconcile_ok: "{{technical_ok}}"
1354
1659
  domain_reconcile_ok: "{{domain_ok}}"
1355
- next: "Adopted {{feature_name}} (spec.md + backend-plan.md + source-map.md, v1.0) and wrote {{aggregate_sections}} — the model section(s) for the aggregate(s) it acts on, resolved from the {{aggregate_source}} — in the bounded context `{{context}}`'s shared {{domain_model}}. The spec cites BR-nnn by id, the model carries their text, so this slice is checkable by the same gates as a greenfield one and analyze-plan's check 4 now has a model to check against. A section another feature over the same aggregate wrote first was EXTENDED, never duplicated; every other section is untouched. COMPLETED the OKF concept of {{aggregate_primary}} (deepened + linked the deep artifacts; {{xref_edge_count}} cross-slice edge(s) recorded). UI HALF: ui_lane={{ui_lane}} — where it is true this slice also has frontend-plan.md, reversed from the {{ui_count}} UI file(s) the feature map attributes to it; where it is false the repo declared no frontend lane, or this aggregate owns no part of its surface, and a service-only adoption is the correct outcome rather than a gap. In the second case, where a frontend lane IS declared, the feature records lanes=[backend] in its active_version.json (feature_lanes shows it), so plan and the analyze gates treat the frontend lane as absent for THIS feature; run `aw-spec-actions feature-lanes --clear` if it later gains a UI. The SHARED UI surface (app shell, layout, design system) belongs to no slice and stays in {{bundle_dir}}/architecture/frontend.md. NOTE — governance reconcile is currently HELD (reconcile_governance=false): this run did NOT modify the constitution halves or the guardrails/{{gov_domain}}/ rule set, so they keep onboard's provisional scaffold. Close any items in {{feature_dir}}/adopt-review-report.md out of band. The slice is now a NORMAL feature: refine via /appweaver analyze-plan, then drive every change through the CR delta loop (/appweaver specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta). If a change edits untested legacy behaviour with blast radius, characterize just that surface first (optional on-touch fence). To restore governance reconcile, set reconcile_governance to 'true' in the workflow vars."
1660
+ next: "Adopted {{feature_name}} (spec.md, source-map.md, the API plan and the lane plans this slice has — API: {{api_plan}}; backend: {{backend_plan}}; frontend: {{frontend_plan}} — v1.0) and wrote {{aggregate_sections}} — the model section(s) for the aggregate(s) it acts on, resolved from the {{aggregate_source}} — in the bounded context `{{context}}`'s shared {{domain_model}}. The spec cites BR-nnn by id, the model carries their text, so this slice is checkable by the same gates as a greenfield one and analyze-plan's check 4 now has a model to check against. A section another feature over the same aggregate wrote first was EXTENDED, never duplicated; every other section is untouched. COMPLETED the OKF concept of {{aggregate_primary}} (deepened + linked the deep artifacts; {{xref_edge_count}} cross-slice edge(s) recorded). API DRAFT: {{draft_ops}} operation(s) drawn from the API plan into api-draft.json beside it, the API the frontend lane binds to and analyze-slice checks the service against (0 where the slice has no service half). UI HALF: ui_lane={{ui_lane}} — where it is true this slice also has frontend-plan.md, reversed from the {{ui_count}} UI file(s) the feature map attributes to it; where it is false the repo declared no frontend lane, or this aggregate owns no part of its surface, and a service-only adoption is the correct outcome rather than a gap. In the second case, where a frontend lane IS declared, the feature records lanes=[backend] in its active_version.json (feature_lanes shows it), so plan and the analyze gates treat the frontend lane as absent for THIS feature; run `aw-spec-actions feature-lanes --clear` if it later gains a UI. SERVICE HALF: svc_lane={{svc_lane}} — false where the repo declares no backend lane (a frontend-only project: no backend plan, no cross-slice edges, and the model was read from the UI code) or this slice has no service code; in the second case, where a frontend lane carries it, the feature records lanes=[frontend] the same way. The SHARED UI surface (app shell, layout, design system) belongs to no slice and stays in {{bundle_dir}}/architecture/frontend.md. NOTE — governance reconcile is currently HELD (reconcile_governance=false): this run did NOT modify the constitution halves or the guardrails/{{gov_domain}}/ rule set, so they keep onboard's provisional scaffold. Close any items in {{feature_dir}}/adopt-review-report.md out of band. The slice is now a NORMAL feature: refine via /appweaver analyze-plan, then drive every change through the CR delta loop (/appweaver specify → plan → analyze-plan-delta → implement → build → analyze-slice-delta → finalize-delta → analyze-slice → close-delta). If a change edits untested legacy behaviour with blast radius, characterize just that surface first (optional on-touch fence). To restore governance reconcile, set reconcile_governance to 'true' in the workflow vars."