aw-spec-cli 0.1.31 → 0.1.33

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.
@@ -82,11 +82,18 @@ same traceability spine and the two deferral ledgers — with **one difference t
82
82
  are **derived from the code's own organisation** (its folders / projects / modules)
83
83
  rather than the fixed Domain/Application/API/Infrastructure four. A component the
84
84
  code has that fits no textbook layer (a UI tier, a worker, a host project) gets its
85
- own section; a layer the code lacks does not appear. Two neutral anchors are kept
85
+ own section; a layer the code lacks does not appear. Three neutral anchors are kept
86
86
  because every system has them and the tooling keys on them: **§1 is the domain/data
87
- model** (the cross-reference tool reads it for `<Other>Id` reference edges) and one
87
+ model** (the cross-reference tool reads it for `<Other>Id` reference edges), one
88
88
  **§2 subsection is the External Integrations table** (matched by heading keyword, so
89
- `aw-xref` extracts contract edges from either layout). The `debt` ledger records
89
+ `aw-xref` extracts contract edges from either layout), and **each published endpoint
90
+ is an endpoint block** — the greenfield §3 form, `#### `VERB /route`` with its
91
+ **Operation id**, followed by the "Requests from the frontend lane" table — inside
92
+ whichever §2 axis publishes it. The contract scripts read exactly that form: the API
93
+ draft is written from the headings, and the request ledger between the two lanes reads
94
+ the operation ids and the table. An endpoint the code already serves keeps the id it
95
+ publishes; where it publishes none the plan writes `—` and records a §5 assumption,
96
+ never an invented id. The `debt` ledger records
90
97
  only debt the **code itself** evidences (stubs, dead code, torn seams) — being
91
98
  organised unlike a reference architecture is **not** debt, it is the as-built shape;
92
99
  migrating toward a target is a separate modernization decision.
@@ -113,6 +113,14 @@ project it does not exist yet — the whole point of declaring `backend/` on day
113
113
  `build`, `run`, and before `onboard` reverse-engineers a codebase out of it. That split is what lets one file serve a project with no code yet and a project that is
114
114
  nothing but code.
115
115
 
116
+ A lane can also be declared before it is built on a project that has code — a new front end
117
+ planned beside an existing service, or the reverse. `inventory` and `onboard` read code, so
118
+ they pass `--allow-missing-root` and read the lanes whose folders exist (`kinds_on_disk`): a
119
+ declared lane whose lane root folder is not there yet is read and governed as absent, and
120
+ named in a warning and in the proposal's Scanned line. Its governance is the developer's to
121
+ provide before the forward pipeline builds it. Both stop, with each missing folder named
122
+ (`missing_roots`), only when no declared lane's folder exists.
123
+
116
124
  Two failure classes, deliberately distinguished:
117
125
 
118
126
  - **MISSING** — the phase needs a lane of this kind and none is declared. Reported only for
@@ -691,7 +699,7 @@ implementation and the twins cannot drift.
691
699
  | `contract-check --plan p [--doc d]` | the operation ids a frontend plan cites (its §5 `Operation id` column, or a delta plan's `Contract Binding` rows; never the request table) against the document's `operationId`s — by script, no model. `drafted[]` names the cited ids that are only draft operations |
692
700
  | `contract-impact --delta-backend p --frontend-plan p [--delta-frontend p]` | the operations a change's service delta marks `changed` or `removed` in its "Contract deltas for the frontend lane" table that the frontend master plan binds to, and which of them the change's frontend delta plan does not name (`unaddressed[]`) — `analyze-plan-delta`'s warning. By script, no model |
693
701
  | `lane-resolve [--kind k] [--id lane] [--feature-dir p]` | read; exit 0 always. `exists` reports the file, `declared` reports the lanes — the shipped-but-unfilled seed is `exists:true, declared:false`. Returns the matched lane **and** both kinds flattened — `backend_source_path`, `frontend_root_exists`, `backend_build` … — so a workflow binds one variable instead of calling twice — and the working-lane fields of §2a |
694
- | `lane-verify [--require backend\|frontend]… [--allow-missing-root] [--working-only] [--command phase]` | **the gate**: is the declaration usable for the phase about to run. Never writes, and **always exits 0** — the calling workflow's `verify_context` owns the stop, so the reason arrives as text. `--working-only` checks on disk only the working lanes' root folders and the required kinds (§2a) |
702
+ | `lane-verify [--require backend\|frontend]… [--allow-missing-root] [--working-only] [--command phase]` | **the gate**: is the declaration usable for the phase about to run. Never writes, and **always exits 0** — the calling workflow's `verify_context` owns the stop, so the reason arrives as text. `--working-only` checks on disk only the working lanes' root folders and the required kinds (§2a). Whatever the flags, `kinds_on_disk[]` lists the kinds whose lane root folder exists and `missing_roots` the issue each declared lane without one raises when `--allow-missing-root` is not passed |
695
703
  | `lane-select [--lane backend\|frontend\|both] [--default] [--preselect text]` | read or write `.appweaver/lane.json` (§2a). No argument reads it and returns the `select_options[]` the declared stacks offer; `--lane` writes one of them; `--default` writes the default — `select-stack`'s call |
696
704
  | `cr-lanes [--cr-id id [--feature-dir p] [--stamp] [--set both\|backend\|frontend]]` | the lanes a change touches (§2a). No `--cr-id` returns the `select_options[]` the declared stacks offer, both first; with it, reads that change's `**Lanes**:` line — `lanes[]` is what it touches among the lanes declared now, every declared lane when nothing is recorded. `--set` writes an answer (a whole option line is accepted); `--stamp` alone writes every declared lane |
697
705
  | `slice-gate-stamp --cr-id id --lane backend\|frontend [--feature-dir p] [--tests-green b] [--tests-ran b] [--requirements-ok b] [--findings json]` | write one lane's `analyze-slice-delta` result to `changes/<id>/.aw-slice-gate.<lane>.json` (§2a): `verdict` `pass` when no test failed and the requirements are met, else `fail`, with `tests_ran` (false when the lane declares no test command, so nothing ran), the findings counts and the sha256 of the delta-spec and the lane's delta plan it was gated against. Findings that cannot be read are recorded as `null`, never as none |
@@ -126,15 +126,19 @@ migration is needed (yes/no); any contract change other systems couple to.]
126
126
 
127
127
  ## Contract deltas for the frontend lane
128
128
 
129
- <!-- What this change adds to or alters in the service's PUBLISHED surface — the
130
- only part of this document the frontend lane may bind against. `None` when
131
- the surface is unchanged. Omit the section entirely where the code has no
132
- frontend lane. The operation id is the endpoint's name in the published API
133
- document; an endpoint that already has one keeps it. -->
129
+ <!-- What this change adds to, alters in or removes from the service's PUBLISHED
130
+ surface — the only part of this document the frontend lane may bind against.
131
+ `None` when the surface is unchanged. Omit the section entirely where the code
132
+ has no frontend lane. The operation id is the endpoint's name in the published
133
+ API document; an endpoint that already has one keeps it. A `changed` or
134
+ `removed` row whose operation the frontend plan binds to is reported by
135
+ analyze-plan-delta (contract-impact reads the Operation id and Change
136
+ columns) until the frontend delta of this change — or a follow-up change —
137
+ carries the matching change. -->
134
138
 
135
139
  | Operation id | Contract | Change | Shape | Serves |
136
140
  |---|---|---|---|---|
137
- | `[OperationId]` | `[contract]` | [added / changed] | [request → response field shapes] | IC-[nnn] |
141
+ | `[OperationId]` | `[contract]` | [added / changed / removed] | [request → response field shapes] | IC-[nnn] |
138
142
 
139
143
  ## Other open deltas
140
144
 
@@ -30,6 +30,13 @@
30
30
  • Exactly one §2 subsection's heading contains the word "Integration".
31
31
  `aw-xref` matches it by keyword to extract contract edges, which is what
32
32
  lets a derived layout and the greenfield layout be read by one tool.
33
+ • Each published endpoint is a `####` ENDPOINT BLOCK — the heading
34
+ `#### `[VERB] [/route]`` and its **Operation id** field — with the
35
+ "Requests from the frontend lane" table after the blocks, inside whichever
36
+ §2 axis publishes them. The contract scripts read exactly this form:
37
+ `contract-draft` drafts api-draft.json from the headings, and
38
+ `contract-requests` reads the operation ids and the table. The axis keeps
39
+ the project's own name; only the block form is fixed.
33
40
  • The traceability spine (§4) and both ledgers (§5 DA-n, §6 WA-n) survive
34
41
  every layout — the analyze gates read them.
35
42
  • The §0–§6 numbering itself: `plan`'s bootstrap_bf branch names those
@@ -169,9 +176,8 @@
169
176
  the titles are the code's answer — the slots below are illustrative
170
177
  positions, not a required set. In each: the components that live there,
171
178
  their behaviour in signature form, and the contracts they expose or
172
- require. A component that PUBLISHES an endpoint names it with its route, verb
173
- and operation id — the endpoint's name in the published API document, which
174
- the frontend plan cites and implement looks the endpoint up by. -->
179
+ require. The endpoints the service PUBLISHES take the endpoint block form
180
+ shown in the axis below. -->
175
181
 
176
182
  ### 2.1 [PROJECT_STRUCTURAL_AXIS_NAME]
177
183
 
@@ -186,6 +192,50 @@
186
192
 
187
193
  <!-- … one per axis the code actually shows, then stop … -->
188
194
 
195
+ ### 2.[M] [AXIS_THAT_PUBLISHES_THE_ENDPOINTS]
196
+
197
+ <!-- PINNED FORM, in whichever axis this project publishes its endpoints from. The
198
+ axis keeps the project's own name and its component table; what is fixed is
199
+ each endpoint's block below it and the requests table after the blocks,
200
+ because the contract scripts read exactly these: the `#### `VERB /route``
201
+ heading (contract-draft writes api-draft.json from it), the **Operation id**
202
+ field (the id the service serves, which the frontend plan binds to) and the
203
+ `| Request | Outcome |` table (contract-requests). A project that publishes no
204
+ endpoint omits both. -->
205
+
206
+ | Component | Behaviour (signature) | Pre / Post / Raises | Serves |
207
+ |---|---|---|---|
208
+ | `[Component]` | `[operation](args) -> result` | [pre / post / raises] | FR-00X · US-Y |
209
+
210
+ #### `[VERB] [/route]` — [purpose] · Serves US-Y · FR-00X
211
+
212
+ - **Operation id**: `[OperationId]` <!-- the endpoint's name in the published API document. An endpoint the code already serves keeps the id it publishes, spelled exactly as the code spells it; where it publishes none, write `—` and record a §5 DA-n saying so — never an invented id, which the service's exported document would not carry. A NEW endpoint takes an id unique across the service, and implement gives it exactly that. -->
213
+ - **Auth**: [required role/scope, as enforced]
214
+ - **Request**: `[path/query params]` + body `{ [field]: [type], … }`
215
+ - **Success**: `2xx` → `{ [response shape] }`
216
+ - **Errors** *(the error contract)*:
217
+
218
+ | Status | When | Body shape |
219
+ |---|---|---|
220
+ | [status] | [when it is returned] | [body shape] |
221
+
222
+ - **Maps to**: `[Component]` (§2.n)
223
+
224
+ #### Requests from the frontend lane
225
+
226
+ <!-- THE ANSWERS to the frontend plan's "Contract deltas needed" table, one row
227
+ per RQ-n it raises. PUBLISH: add the endpoint above — under the proposed
228
+ operation id unless it clashes with one this service already uses — and name
229
+ the id here. DECLINE: say why in one line; a request the spec does not
230
+ support is declined as a spec gap for `specify`, never published on a guess.
231
+ Keep every answered row; write `None` instead of the table when the frontend
232
+ plan raises no request — always so for a plan reversed from code, whose
233
+ frontend binds only to what the service already serves. -->
234
+
235
+ | Request | Outcome | Operation id | Why |
236
+ |---|---|---|---|
237
+ | RQ-[n] | published / declined | `[OperationId]` / — | [one line — required when declined] |
238
+
189
239
  ### 2.[N] External Integrations
190
240
 
191
241
  <!-- PINNED ANCHOR — the heading MUST contain the word "Integration"; the
@@ -71,7 +71,18 @@
71
71
  # =============================================================================
72
72
 
73
73
  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.
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.
75
+ # 2.3.0 — the SERVICE half is reversed only where the slice has one (svc_lane: a backend lane
76
+ # is declared and the slice has service files), the twin of ui_lane. A frontend-only
77
+ # project gets no backend plan and no cross-slice edges, and its model is read from
78
+ # the UI files; a UI-only slice in a repo with both lanes records lanes=[frontend].
79
+ # A failed fidelity check names the two ways to resolve it — re-adopt, or correct
80
+ # the documents by hand — and what each leaves behind.
81
+ # 2.2.0 — the reversed plans take part in the API ledger between the lanes: the backend plan
82
+ # writes each endpoint the slice publishes as an endpoint block (`#### `VERB /route``
83
+ # + **Operation id**, the id the code publishes — `—` and a DA-n where it publishes
84
+ # none, never an invented one) with a "Requests from the frontend lane" table, and the
85
+ # frontend plan's Contract Binding rows cite those ids.
75
86
  # 2.1.0 — every reverse step is scoped to the FEATURE, not its aggregate: the boundary map's
76
87
  # operation list (and a sibling feature's operations, which are not this one's)
77
88
  # reaches the spec, model, plan, UI plan, source-map and fidelity steps. The spec step
@@ -87,7 +98,7 @@ description: The JIT per-feature step of brownfield development — reverse-engi
87
98
  # 1.3.0 — reverse design scaffolds from the DEFAULT `design-template` (onboard's code-derived
88
99
  # override) instead of the never-shipped `brownfield-design-template`, unifying the
89
100
  # whole brownfield pipeline on one code-derived template.
90
- version: 2.1.0
101
+ version: 2.3.0
91
102
  agent_agnostic: true
92
103
  dialect: aw-spec.orchestrator/v1
93
104
 
@@ -128,8 +139,12 @@ vars:
128
139
  be_declared: "false"
129
140
  fe_no_surface: "false" # a frontend lane is declared, this slice owns none of its surface, and a backend lane carries it
130
141
  feature_lanes: "" # what record_lanes wrote — empty when the feature follows every declared lane
142
+ xref_edge_count: "0" # xref_record runs only where the slice has a service half
131
143
  fe_root: ""
132
144
  ui_lane: "false" # THIS slice reverses a UI half: a frontend lane is declared AND the slice owns UI files
145
+ svc_lane: "false" # THIS slice reverses a SERVICE half: a backend lane is declared AND the slice has service files
146
+ be_no_surface: "false" # a backend lane is declared, this slice has no service code, and a frontend lane carries it
147
+ model_files: "" # what the model is reversed from: the service files, or the UI files on a slice with none
133
148
  ui_count: "0" # how many UI files the concept attributes to this aggregate (aw-inventory ui_source_files)
134
149
  ui_source_files: ""
135
150
  frontend_plan: ""
@@ -424,14 +439,6 @@ workflow:
424
439
  outputs:
425
440
  - { name: domain_model, from: "$.domain_model" }
426
441
 
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
442
  - id: version_init
436
443
  action: execute_script
437
444
  script: { ref: actions }
@@ -502,6 +509,69 @@ workflow:
502
509
  outputs:
503
510
  - { name: feature_lanes, from: "$.lanes" }
504
511
 
512
+ # 2d. DOES THIS SLICE HAVE A SERVICE HALF? The twin of 2b: a backend lane must be DECLARED
513
+ # and this aggregate must have service files (the concept's source_files). A
514
+ # frontend-only project declares no backend lane, and its concepts list every file as
515
+ # ui_source_files, so a backend plan there would describe code that does not exist and
516
+ # the fidelity gate would score it as fabricated. Three ordered sets, as in 2b.
517
+ - id: svc_lane_off
518
+ action: set
519
+ vars: { svc_lane: "false" }
520
+ - id: svc_lane_on
521
+ action: set
522
+ condition: "{{be_declared}}"
523
+ vars: { svc_lane: "true" }
524
+ - id: svc_lane_no_code
525
+ action: set
526
+ condition: "{{source_count}} == 0"
527
+ vars: { svc_lane: "false" }
528
+
529
+ # 2e. RECORD A UI-ONLY SLICE in a repo that declares both lanes — the twin of 2c, so plan
530
+ # and the analyze gates do not demand a backend plan for a slice with no service code.
531
+ - id: be_no_surface_off
532
+ action: set
533
+ vars: { be_no_surface: "false" }
534
+ - id: be_no_surface_declared
535
+ action: set
536
+ condition: "{{be_declared}}"
537
+ vars: { be_no_surface: "true" }
538
+ - id: be_no_surface_has_code
539
+ action: set
540
+ condition: "{{source_count}} != 0"
541
+ vars: { be_no_surface: "false" }
542
+ - id: be_no_surface_no_frontend
543
+ action: set
544
+ condition: "{{ui_lane}} != true"
545
+ vars: { be_no_surface: "false" }
546
+ - id: record_lanes_frontend
547
+ condition: "{{be_no_surface}} == true"
548
+ action: execute_script
549
+ script: { ref: actions }
550
+ subcommand: feature-lanes
551
+ args: ["--feature-dir", "{{feature_dir}}", "--set", "frontend"]
552
+ outputs:
553
+ - { name: feature_lanes, from: "$.lanes" }
554
+
555
+ # What the domain model is reversed FROM. The service files where the slice has them; a
556
+ # slice with none keeps its domain in the UI code — the types, states and rules a
557
+ # frontend-only app holds — so the model is read from those.
558
+ - id: model_files_service
559
+ action: set
560
+ vars: { model_files: "{{source_files}}" }
561
+ - id: model_files_ui
562
+ condition: "{{svc_lane}} != true"
563
+ action: set
564
+ vars: { model_files: "{{ui_source_files}}" }
565
+
566
+ - id: scaffold_plan
567
+ condition: "{{svc_lane}} == true"
568
+ action: execute_script
569
+ script: { ref: actions }
570
+ subcommand: plan-scaffold
571
+ args: ["--feature-dir", "{{feature_dir}}", "--lane", "backend"] # `design-template` = onboard's code-derived override (greenfield base if onboard has not run)
572
+ outputs:
573
+ - { name: backend_plan, from: "$.plan_doc" }
574
+
505
575
  # Scaffold the LANE's plan from ITS template pair (--lane frontend resolves
506
576
  # ui-design-template -> frontend-plan.md), the twin of scaffold_plan above. onboard
507
577
  # GENERATED that override from this repo's own UI source, so the reversed frontend plan
@@ -734,7 +804,8 @@ workflow:
734
804
  - Add `origin: reverse-engineered` near the identity block. Do NOT write the Context
735
805
  version or the Change History version column — scripts stamp those.
736
806
 
737
- Source files to read (the slice's code): {{source_files}}
807
+ Source files to read (the slice's code — its UI files where it has no service code,
808
+ the one place a frontend-only app keeps its types, states and rules): {{model_files}}
738
809
 
739
810
  # ═══════════ REVERSE BACKEND PLAN — from the SPEC + MODEL + CODE, AS-BUILT ═══
740
811
  # Anchored to the reversed spec's ids (§4 Traceability → forward-compatible),
@@ -744,6 +815,7 @@ workflow:
744
815
  # backend-plan.md IS onboard's code-derived `design-template` override — its §2
745
816
  # component sections follow the code's own folders/modules, not a fixed layering.
746
817
  - id: ctx_reverse_plan
818
+ condition: "{{svc_lane}} == true"
747
819
  action: build_context
748
820
  context:
749
821
  - { path: "{{backend_plan}}" } # the scaffolded code-derived template = structure + embedded guidance
@@ -754,6 +826,7 @@ workflow:
754
826
  - { path: "{{bundle_dir}}/architecture/overview.md", optional: true } # the as-built architecture (inventory) — cite, don't re-derive
755
827
  - { path: .appweaver/memory/constitution.technical.md, optional: true } # technology base ONLY — no domain
756
828
  - id: reverse_plan
829
+ condition: "{{svc_lane}} == true"
757
830
  action: agent_author
758
831
  include_standards: true # the technical/architecture sticky set (reference only — the CODE wins)
759
832
  tools: Read # grounded in the CODE, which arrives as {{source_files}} paths
@@ -798,6 +871,21 @@ workflow:
798
871
  the EXTERNAL INTEGRATIONS table (heading contains "Integration") realising the
799
872
  spec's IC-. Realise CN- (structural → §1.2 invariants/events; retention/audit →
800
873
  §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.
801
889
  - AS-BUILT DEBT, not architecture drift. §6 records ONLY debt the CODE ITSELF
802
890
  evidences — stubs/no-ops, dead or duplicate code, torn seams (shared table/
803
891
  transaction), TODOs, hard-coded values. The code being organised differently
@@ -835,7 +923,7 @@ workflow:
835
923
  context:
836
924
  - { path: "{{frontend_plan}}" } # the scaffolded code-derived lane template = structure + embedded guidance
837
925
  - { 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
926
+ - { path: "{{backend_plan}}", optional: true } # the backend plan — the contracts the UI consumes, never re-specified here; absent with no service lane
839
927
  - { path: "{{domain_model}}", optional: true } # the ubiquitous language labels and states must use — READ, never written
840
928
  - { path: .appweaver/memory/constitution.ui.md, optional: true } # the lane's UX-* base, reversed by onboard from this same UI
841
929
  - { path: "{{bundle_dir}}/architecture/frontend.md", optional: true } # the as-built UI architecture + the SHARED surface no slice owns
@@ -863,9 +951,17 @@ workflow:
863
951
  - STAY IN THE LANE. Realise the spec's Experience Standards (UX-) and the
864
952
  interaction its User Stories imply. The backend plan (in context) supplies
865
953
  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
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
867
957
  model is in context, use its UBIQUITOUS LANGUAGE exactly in labels, states and
868
958
  messages, and cite BR-nnn rather than restating a rule.
959
+ - 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
962
+ implement finds the endpoint, and how a later change that alters or removes it is
963
+ reported to this lane. The "Contract deltas needed" table reads `None`: the UI calls
964
+ only what the service already serves.
869
965
  - STRUCTURE FOLLOWS THE UI CODE. Fill each section from the lane's real shape —
870
966
  its actual routes and screens, its own composition axes (however it groups
871
967
  components), where client state and server data-fetching really live, its
@@ -890,7 +986,7 @@ workflow:
890
986
  context:
891
987
  - { path: .appweaver/templates/source-map-template.md } # the sidecar structure
892
988
  - { path: "{{spec_file}}" }
893
- - { path: "{{backend_plan}}" }
989
+ - { path: "{{backend_plan}}", optional: true } # the service lane's plan, where this slice has one
894
990
  - { path: "{{frontend_plan}}", optional: true } # the lane's design, where this slice has one
895
991
  - { path: "{{concept_path}}" } # both file lists to map
896
992
  - id: source_map
@@ -945,7 +1041,7 @@ workflow:
945
1041
  context:
946
1042
  - "{{spec_file}}"
947
1043
  - "{{domain_model}}"
948
- - "{{backend_plan}}"
1044
+ - { path: "{{backend_plan}}", optional: true } # the service lane's plan, where this slice has one
949
1045
  - { path: "{{frontend_plan}}", optional: true } # the lane's plan, where this slice has one
950
1046
  - "{{feature_dir}}/source-map.md"
951
1047
  - "{{concept_path}}"
@@ -1006,7 +1102,12 @@ workflow:
1006
1102
  condition: "{{report_needed}} == true"
1007
1103
  action: agent_author
1008
1104
  include_standards: false
1009
- context: ["{{spec_file}}", "{{domain_model}}", "{{backend_plan}}", "{{feature_dir}}/source-map.md"]
1105
+ context:
1106
+ - "{{spec_file}}"
1107
+ - "{{domain_model}}"
1108
+ - { path: "{{backend_plan}}", optional: true }
1109
+ - { path: "{{frontend_plan}}", optional: true }
1110
+ - "{{feature_dir}}/source-map.md"
1010
1111
  output_target: { type: file, path: "{{feature_dir}}/adopt-review-report.md" }
1011
1112
  task: |
1012
1113
  Write an Adopt Review report (the target) for the feature '{{feature_name}}' (acting on {{aggregates_csv}}) —
@@ -1014,8 +1115,10 @@ workflow:
1014
1115
  - "# Adopt Review — {{feature_name}}" + today's date + "origin: reverse-engineered".
1015
1116
  - "verdict:" line — "BLOCKING ✗ · <n>" if blocking gaps exist, else "REVIEW ○ · <m> item(s)".
1016
1117
  - "## 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).
1118
+ resolved before the slice is trustworthy, one of two ways: re-adopt — remove
1119
+ {{feature_dir}} and run /appweaver adopt {{feature_name}} again, which reverses
1120
+ the documents afresh from the code, so edits made to them by hand are not kept —
1121
+ or correct the documents by hand and keep them.
1019
1122
  Blocking gaps: {{blocking_gaps}}
1020
1123
  - "## Review items" (close out of band): table | # | kind | where | confirm |.
1021
1124
  These are inferred-vs-certain calls, as-built quirks, and missing-invariant
@@ -1034,7 +1137,7 @@ workflow:
1034
1137
  action: verify_context
1035
1138
  require_true: fidelity_ok
1036
1139
  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."
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."
1038
1141
 
1039
1142
  # ═══════════ OKF CONCEPT — COMPLETE the shallow scan concept (deep reverse) ═══
1040
1143
  # onboard wrote this aggregate's OKF concept SHALLOW (a discovery stub — enough to pick the
@@ -1051,7 +1154,8 @@ workflow:
1051
1154
  - { path: "{{concept_path}}" } # the shallow onboard concept to complete
1052
1155
  - { path: "{{spec_file}}" } # the deep reversed spec (entities, US, BR/CN/DC/IC)
1053
1156
  - { path: "{{domain_model}}" } # the deep reversed model (entities, boundary, invariants)
1054
- - { path: "{{backend_plan}}" } # the deep reversed plan (components, ports, integrations)
1157
+ - { path: "{{backend_plan}}", optional: true } # the deep reversed service plan (components, ports, integrations), where the slice has one
1158
+ - { path: "{{frontend_plan}}", optional: true } # the deep reversed UI plan, where the slice has one
1055
1159
  - { path: "{{feature_dir}}/source-map.md" } # the source anchor (files, seam, quirks)
1056
1160
  - { path: .appweaver/templates/okf-concept-template.md } # OKF v0.1 format authority
1057
1161
  - id: complete_concept
@@ -1109,6 +1213,7 @@ workflow:
1109
1213
  # reconcile-on-write helper as forward implement, stamped origin reverse-engineered,
1110
1214
  # additive to the inventory bundle. Runs only after fidelity passes.
1111
1215
  - id: xref_record
1216
+ condition: "{{svc_lane}} == true" # edges are read off the service plan, as forward implement records them
1112
1217
  action: execute_script
1113
1218
  script: { ref: xref }
1114
1219
  subcommand: record
@@ -1331,9 +1436,10 @@ outputs:
1331
1436
  frontend_plan: "{{frontend_plan}}" # empty where this slice has no UI half
1332
1437
  domain_model: "{{domain_model}}" # the CONTEXT's, shared — this run ADDED an aggregate section to it
1333
1438
  bounded_context: "{{context}}"
1439
+ svc_lane: "{{svc_lane}}"
1334
1440
  ui_lane: "{{ui_lane}}"
1335
1441
  ui_count: "{{ui_count}}"
1336
- feature_lanes: "{{feature_lanes}}" # empty = every declared lane; [backend] = a service-only slice in a repo with a frontend
1442
+ 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
1443
  operations: "{{operations_csv}}" # this feature's operations, from the boundary map
1338
1444
  baseline_commit: "{{baseline_commit}}"
1339
1445
  source_map: "{{feature_dir}}/source-map.md"
@@ -1352,4 +1458,4 @@ outputs:
1352
1458
  composed_file: "{{composed_file}}"
1353
1459
  technical_reconcile_ok: "{{technical_ok}}"
1354
1460
  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."
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."