aw-spec-cli 0.1.33 → 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 +57 -25
  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 -20
  12. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -48
  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 +239 -40
  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 +21 -22
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +453 -120
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +2 -2
  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,13 @@
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 its lane plans — 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) 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.
75
85
  # 2.3.0 — the SERVICE half is reversed only where the slice has one (svc_lane: a backend lane
76
86
  # is declared and the slice has service files), the twin of ui_lane. A frontend-only
77
87
  # project gets no backend plan and no cross-slice edges, and its model is read from
@@ -98,7 +108,7 @@ description: The JIT per-feature step of brownfield development — reverse-engi
98
108
  # 1.3.0 — reverse design scaffolds from the DEFAULT `design-template` (onboard's code-derived
99
109
  # override) instead of the never-shipped `brownfield-design-template`, unifying the
100
110
  # whole brownfield pipeline on one code-derived template.
101
- version: 2.3.0
111
+ version: 2.5.0
102
112
  agent_agnostic: true
103
113
  dialect: aw-spec.orchestrator/v1
104
114
 
@@ -164,6 +174,21 @@ vars:
164
174
  # left by an earlier adopt, so a repo that was adopted before the rename is carried
165
175
  # forward rather than left with two spellings of one master.
166
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`."
167
192
  # THE BOUNDED CONTEXT'S DOMAIN MODEL — shared by every feature in the context, and the
168
193
  # authority on the aggregate root, its entities and the TEXT of every BR-nnn. Reversed
169
194
  # here in the same run as the spec, exactly as greenfield `specify` authors the two
@@ -172,6 +197,10 @@ vars:
172
197
  context_dir: ""
173
198
  domain_model: ""
174
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
175
204
  spec_exists: "false"
176
205
  fidelity_ok: "false"
177
206
  rulemap_exists: "false" # governance-present signal (onboard ran) — bound from resolve-domain
@@ -563,6 +592,21 @@ workflow:
563
592
  action: set
564
593
  vars: { model_files: "{{ui_source_files}}" }
565
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
+
566
610
  - id: scaffold_plan
567
611
  condition: "{{svc_lane}} == true"
568
612
  action: execute_script
@@ -572,6 +616,22 @@ workflow:
572
616
  outputs:
573
617
  - { name: backend_plan, from: "$.plan_doc" }
574
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
+
575
635
  # Scaffold the LANE's plan from ITS template pair (--lane frontend resolves
576
636
  # ui-design-template -> frontend-plan.md), the twin of scaffold_plan above. onboard
577
637
  # GENERATED that override from this repo's own UI source, so the reversed frontend plan
@@ -741,6 +801,18 @@ workflow:
741
801
  - { path: "{{spec_file}}" } # the reversed spec — the BR-nnn ids this model must define
742
802
  - { path: "{{concept_path}}" } # source_files + seam
743
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
+
744
816
  - id: reverse_model
745
817
  action: agent_author
746
818
  include_standards: false # the technical/arch sticky set is for the plans, never the model
@@ -784,6 +856,12 @@ workflow:
784
856
  already there — other specs cite their ids, and a renumbered BR breaks every
785
857
  citation silently. Add `{{feature_name}}` to the `Serving specs` line of any
786
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.
787
865
  - MODEL WHAT THE CODE EXHIBITS, NOT WHAT GOOD DESIGN WOULD WANT. Classify entities
788
866
  vs value objects by whether the code gives the thing an IDENTITY; draw the
789
867
  boundary where the code actually draws it (what it loads and saves together).
@@ -807,6 +885,95 @@ workflow:
807
885
  Source files to read (the slice's code — its UI files where it has no service code,
808
886
  the one place a frontend-only app keeps its types, states and rules): {{model_files}}
809
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
+
975
+ Source files to read (the slice's code): {{source_files}}
976
+
810
977
  # ═══════════ REVERSE BACKEND PLAN — from the SPEC + MODEL + CODE, AS-BUILT ═══
811
978
  # Anchored to the reversed spec's ids (§4 Traceability → forward-compatible),
812
979
  # grounded in the code's ACTUAL structure, tech-agnostic. NO domain rule files (rule
@@ -820,6 +987,7 @@ workflow:
820
987
  context:
821
988
  - { path: "{{backend_plan}}" } # the scaffolded code-derived template = structure + embedded guidance
822
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
823
991
  - { path: "{{domain_model}}" } # the reversed model = what the domain IS — READ, never written here
824
992
  - { path: "{{concept_path}}" } # source_files + seam (structure hints)
825
993
  - { path: .appweaver/DESIGN-FORMAT.md } # the HLD contract (see its Brownfield variant note)
@@ -871,21 +1039,7 @@ workflow:
871
1039
  the EXTERNAL INTEGRATIONS table (heading contains "Integration") realising the
872
1040
  spec's IC-. Realise CN- (structural → §1.2 invariants/events; retention/audit →
873
1041
  §2 persistence / §3 cross-cutting), DC- (§3 + projection surface).
874
- - EVERY ENDPOINT THIS FEATURE PUBLISHES IS AN ENDPOINT BLOCK, in the §2 axis that
875
- publishes it: the heading `#### `VERB /route` — <purpose> · Serves <US/FR ids>`, then
876
- **Operation id**, Auth, Request, Success, the Errors table and Maps to, exactly as
877
- the template's endpoint block — never a row in a table of endpoints. The contract
878
- scripts read this form: the API draft is written from the headings, and the request
879
- ledger between the lanes reads the operation ids. The **Operation id** is the id the
880
- code publishes the endpoint under, spelled exactly as the code spells it; the
881
- boundary map records each operation's published id where the code declares one
882
- ("published as …" above). Where the code publishes none, write `—` and record a §5
883
- DA-n saying the endpoint has no published operation id — never invent one: the
884
- service's exported API document would not carry it, so nothing could bind to it.
885
- - After the endpoint blocks, a `#### Requests from the frontend lane` heading with
886
- `None` under it: a frontend reversed from the same code binds only to what the
887
- service already serves, so no request is open yet. Later plans answer requests
888
- there.
1042
+ - {{be_api_rule}}
889
1043
  - AS-BUILT DEBT, not architecture drift. §6 records ONLY debt the CODE ITSELF
890
1044
  evidences — stubs/no-ops, dead or duplicate code, torn seams (shared table/
891
1045
  transaction), TODOs, hard-coded values. The code being organised differently
@@ -904,8 +1058,9 @@ workflow:
904
1058
  # ═══════ REVERSE UI DESIGN — the LANE's half, from the UI code, AS-BUILT ═══
905
1059
  # Runs only where this slice HAS a surface (ui_lane). The service design above is
906
1060
  # NOT its parent: backend-plan.md realises the spec's FR/BR/CN-, frontend-plan.md
907
- # realises the spec's UX- and the user stories' interaction, and it CONSUMES the backend
908
- # 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.
909
1064
  # THE FRONTEND'S RULES REPLACE THE BACKEND'S before the lane's plan is authored — the
910
1065
  # swap the forward `plan` makes at the same boundary. Sticky standards only accumulate,
911
1066
  # so without it the UI plan was authored under the service rule set and none of the
@@ -923,7 +1078,7 @@ workflow:
923
1078
  context:
924
1079
  - { path: "{{frontend_plan}}" } # the scaffolded code-derived lane template = structure + embedded guidance
925
1080
  - { path: "{{spec_file}}" } # the reversed spec — UX- + the user stories this surface serves
926
- - { path: "{{backend_plan}}", optional: true } # the backend plan — the contracts the UI consumes, never re-specified here; absent with no service lane
1081
+ - { path: "{{api_read}}", optional: true } # the API plan — the contracts the UI consumes, never re-specified here; absent with no service lane
927
1082
  - { path: "{{domain_model}}", optional: true } # the ubiquitous language labels and states must use — READ, never written
928
1083
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's UX-* base, reversed by onboard from this same UI
929
1084
  - { path: "{{bundle_dir}}/architecture/frontend.md", optional: true } # the as-built UI architecture + the SHARED surface no slice owns
@@ -949,16 +1104,17 @@ workflow:
949
1104
  operation — typically that feature's own UI folder — is that feature's. Where this
950
1105
  feature's screen hosts it, cite it as the sibling's; never plan it here.
951
1106
  - STAY IN THE LANE. Realise the spec's Experience Standards (UX-) and the
952
- interaction its User Stories imply. The backend plan (in context) supplies
953
- the CONTRACTS this surface consumes — cite them; do NOT restate, re-derive or
954
- contradict them, and introduce no server-side behaviour here. With no backend plan
955
- in context the service is outside this repo: bind against the routes the UI code
956
- calls and the spec's IC-, as an external contract. 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
957
1113
  model is in context, use its UBIQUITOUS LANGUAGE exactly in labels, states and
958
1114
  messages, and cite BR-nnn rather than restating a rule.
959
1115
  - CONTRACT BINDING CITES OPERATION IDS. Each row of the Contract Binding table names,
960
- in its Operation id column, the **Operation id** backend-plan.md gives the endpoint
961
- the UI code calls — `—` where the backend plan records none. The id is how
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
962
1118
  implement finds the endpoint, and how a later change that alters or removes it is
963
1119
  reported to this lane. The "Contract deltas needed" table reads `None`: the UI calls
964
1120
  only what the service already serves.
@@ -987,6 +1143,7 @@ workflow:
987
1143
  - { path: .appweaver/templates/source-map-template.md } # the sidecar structure
988
1144
  - { path: "{{spec_file}}" }
989
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
990
1147
  - { path: "{{frontend_plan}}", optional: true } # the lane's design, where this slice has one
991
1148
  - { path: "{{concept_path}}" } # both file lists to map
992
1149
  - id: source_map
@@ -1000,7 +1157,8 @@ workflow:
1000
1157
  spine — it keeps spec.md / backend-plan.md clean while recording provenance here.
1001
1158
  - One row per legacy artefact in the bundle concept's `source_files` (read
1002
1159
  them under {{module_dir}}): map each to the spec ids (FR/BR/US/CN/DC/IC)
1003
- 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
1004
1162
  (certain | inferred). EVERY source file MUST appear — the Fidelity gate
1005
1163
  checks full coverage both ways.
1006
1164
  - THE CONCEPT IS THE AGGREGATE'S ('{{aggregate_primary}}') and lists every feature's
@@ -1042,6 +1200,7 @@ workflow:
1042
1200
  - "{{spec_file}}"
1043
1201
  - "{{domain_model}}"
1044
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
1045
1204
  - { path: "{{frontend_plan}}", optional: true } # the lane's plan, where this slice has one
1046
1205
  - "{{feature_dir}}/source-map.md"
1047
1206
  - "{{concept_path}}"
@@ -1056,7 +1215,10 @@ workflow:
1056
1215
  {{module_dir}}). Where frontend-plan.md is in context, it is judged the same way
1057
1216
  against the slice's UI files ({{ui_source_files}}) — a screen, state store or
1058
1217
  route it describes that the UI code does not have is a fabricated-plan, and a
1059
- 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:
1060
1222
  - BLOCKING (structural — set ok=false if any):
1061
1223
  (1) unmapped-code: a source file with NO home in the source-map — one recorded
1062
1224
  there as `sibling: <Feature>` has a home;
@@ -1106,6 +1268,7 @@ workflow:
1106
1268
  - "{{spec_file}}"
1107
1269
  - "{{domain_model}}"
1108
1270
  - { path: "{{backend_plan}}", optional: true }
1271
+ - { path: "{{api_plan}}", optional: true }
1109
1272
  - { path: "{{frontend_plan}}", optional: true }
1110
1273
  - "{{feature_dir}}/source-map.md"
1111
1274
  output_target: { type: file, path: "{{feature_dir}}/adopt-review-report.md" }
@@ -1137,7 +1300,7 @@ workflow:
1137
1300
  action: verify_context
1138
1301
  require_true: fidelity_ok
1139
1302
  on_false: stop
1140
- 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 lane plans this slice has (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}} and recorded cross-slice edges, so on this path both stay as onboard left them. 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."
1141
1304
 
1142
1305
  # ═══════════ OKF CONCEPT — COMPLETE the shallow scan concept (deep reverse) ═══
1143
1306
  # onboard wrote this aggregate's OKF concept SHALLOW (a discovery stub — enough to pick the
@@ -1155,6 +1318,7 @@ workflow:
1155
1318
  - { path: "{{spec_file}}" } # the deep reversed spec (entities, US, BR/CN/DC/IC)
1156
1319
  - { path: "{{domain_model}}" } # the deep reversed model (entities, boundary, invariants)
1157
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
1158
1322
  - { path: "{{frontend_plan}}", optional: true } # the deep reversed UI plan, where the slice has one
1159
1323
  - { path: "{{feature_dir}}/source-map.md" } # the source anchor (files, seam, quirks)
1160
1324
  - { path: .appweaver/templates/okf-concept-template.md } # OKF v0.1 format authority
@@ -1167,8 +1331,8 @@ workflow:
1167
1331
  COMPLETE the OKF concept of the aggregate '{{aggregate_primary}}' at {{concept_path}} —
1168
1332
  the aggregate this run adopted the feature '{{feature_name}}' over. onboard wrote it
1169
1333
  SHALLOW (a discovery stub from the whole-repo scan). The deep reverse has now
1170
- produced a TRUSTWORTHY spec.md + domain-model.md + backend-plan.md + source-map.md (in
1171
- 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
1172
1336
  concept to match and LINK those artifacts. Follow the OKF concept template in context.
1173
1337
  THE CONCEPT IS THE AGGREGATE'S, NOT THE FEATURE'S. Where its `features:` names more
1174
1338
  than one feature, another feature's adopt may already have deepened it, and nothing
@@ -1180,8 +1344,9 @@ workflow:
1180
1344
  the authority on them; correct any drift the shallow scan got wrong (the deep docs
1181
1345
  win). A sibling capability's commands and endpoints stay where they are.
1182
1346
  - LINK the deep artifacts: add bundle links to {{feature_dir}}/spec.md,
1183
- backend-plan.md, source-map.md and the context's domain-model.md so the concept
1184
- 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.
1185
1350
  - UNION `source_files` with the source-map's mapped files (grouped by the code's OWN
1186
1351
  folders, per OKF) — never drop a file already listed, which may be a sibling
1187
1352
  capability's. The boundary map was drawn at the baseline commit, whose time is
@@ -1222,6 +1387,37 @@ workflow:
1222
1387
  - { name: xref_wrote, from: "$.wrote" }
1223
1388
  - { name: xref_edge_count, from: "$.edge_count" }
1224
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
+
1225
1421
  # ═══════════ GOVERNANCE RECONCILE — upgrade onboard's PROVISIONAL scaffold ═══
1226
1422
  # ⏸ HELD 2026-07-29: every step in this block is gated on `reconcile_governance`
1227
1423
  # (default "false"), so adopt currently does NOT touch the constitution halves
@@ -1432,6 +1628,9 @@ workflow:
1432
1628
  outputs:
1433
1629
  feature_dir: "{{feature_dir}}"
1434
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}}"
1435
1634
  backend_plan: "{{backend_plan}}"
1436
1635
  frontend_plan: "{{frontend_plan}}" # empty where this slice has no UI half
1437
1636
  domain_model: "{{domain_model}}" # the CONTEXT's, shared — this run ADDED an aggregate section to it
@@ -1458,4 +1657,4 @@ outputs:
1458
1657
  composed_file: "{{composed_file}}"
1459
1658
  technical_reconcile_ok: "{{technical_ok}}"
1460
1659
  domain_reconcile_ok: "{{domain_ok}}"
1461
- next: "Adopted {{feature_name}} (spec.md, source-map.md and the lane plans this slice has — 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). 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."
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."