aw-spec-cli 0.1.18 → 0.1.19

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 (53) hide show
  1. package/assets/.appweaver/DESIGN-FORMAT.md +45 -21
  2. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +62 -14
  3. package/assets/.appweaver/orchestrator/modules/delta-spec.orch.yaml +6 -4
  4. package/assets/.appweaver/orchestrator/modules/lane-gate.orch.yaml +9 -1
  5. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +21 -0
  6. package/assets/.appweaver/orchestrator/orchestrator.py +23 -1
  7. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +18 -11
  8. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -3
  9. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +298 -109
  10. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +161 -34
  11. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +125 -24
  12. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +18 -7
  13. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +182 -39
  14. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +300 -35
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +190 -31
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +93 -8
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +34 -7
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-recall-delta.orch.yaml +319 -0
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +125 -24
  20. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +846 -35
  21. package/assets/.appweaver/scripts/bash/common.sh +28 -1
  22. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +843 -46
  23. package/assets/.appweaver/scripts/python/aw-inventory.py +3 -2
  24. package/assets/.appweaver/templates/design-template.md +41 -30
  25. package/assets/.appweaver/templates/spec-template.md +7 -4
  26. package/assets/.appweaver/templates/ui-design-template.md +7 -3
  27. package/assets/guardrails/domain/healthcare/auditing.domain.md +3 -3
  28. package/assets/guardrails/domain/pharma/auditing.domain.md +3 -3
  29. package/assets/guardrails/technology/dotnet-webapi/api-versioning.md +1 -1
  30. package/assets/guardrails/technology/dotnet-webapi/auditing.md +1 -1
  31. package/assets/guardrails/technology/dotnet-webapi/authorization.md +1 -1
  32. package/assets/guardrails/technology/dotnet-webapi/clean-architecture.md +1 -1
  33. package/assets/guardrails/technology/dotnet-webapi/cqrs.md +1 -1
  34. package/assets/guardrails/technology/dotnet-webapi/data-classification.md +1 -1
  35. package/assets/guardrails/technology/dotnet-webapi/eventing.md +1 -1
  36. package/assets/guardrails/technology/dotnet-webapi/folder-structure.md +1 -1
  37. package/assets/guardrails/technology/dotnet-webapi/modular-monolith.md +1 -1
  38. package/assets/guardrails/technology/dotnet-webapi/persistence.md +1 -1
  39. package/assets/guardrails/technology/dotnet-webapi/vertical-slices.md +1 -1
  40. package/assets/guardrails/technology/dotnet-webapi/zero-trust.md +1 -1
  41. package/assets/guardrails/technology/nextjs/accessibility-implementation.md +1 -1
  42. package/assets/guardrails/technology/nextjs/api-integration.dotnet-webapi.md +1 -1
  43. package/assets/guardrails/technology/nextjs/api-integration.java-spring.md +1 -1
  44. package/assets/guardrails/technology/nextjs/auth-session.md +1 -1
  45. package/assets/guardrails/technology/nextjs/component-architecture.md +1 -1
  46. package/assets/guardrails/technology/nextjs/design-system.md +1 -1
  47. package/assets/guardrails/technology/nextjs/error-handling.md +1 -1
  48. package/assets/guardrails/technology/nextjs/forms-and-validation.md +1 -1
  49. package/assets/guardrails/technology/nextjs/i18n.md +1 -1
  50. package/assets/guardrails/technology/nextjs/performance-budgets.md +1 -1
  51. package/assets/guardrails/technology/nextjs/routing-navigation.md +1 -1
  52. package/assets/guardrails/technology/nextjs/state-management.md +1 -1
  53. package/package.json +1 -1
@@ -42,6 +42,22 @@
42
42
  # spec.md — analyze-plan already conformed the plans to it, so the plan is the complete,
43
43
  # authoritative source for WHICH elements exist, where each lives and what surface they
44
44
  # publish, and anything missing from it is fixed in `plan`, never compensated for here.
45
+ #
46
+ # AND THAT SENTENCE IS NOW CHECKED. "analyze-plan already conformed them" was an assumption
47
+ # about the order the operator ran things in: the lane gates test that the plan FILE EXISTS
48
+ # and nothing more, so `plan` → edit spec.md → `implement` generated at full length from a
49
+ # plan nobody had held against the spec it now contradicts. analyze-plan leaves a receipt of
50
+ # the bytes it proved and plan-gate-check reads it here, on the bootstrap path, before any
51
+ # lane generates. A feature with no receipt has simply never been gated since the receipt
52
+ # existed — reported, never refused. One that HAS a receipt and no longer matches it stops.
53
+ # 3.2.0 — AND NOT delta-spec.md EITHER. The change path used to inline it "only so the
54
+ # paired tests can cite traceability ids", which made the rule true on the bootstrap path
55
+ # and false on the one where changes actually ship: a document in the payload is a document
56
+ # the generator can reason from, so a gap in a delta plan could be quietly filled from the
57
+ # requirement text and the plan never found to be wrong. Both paths now carry plan + model
58
+ # and nothing else. The ids are still cited and still required — the delta plan names the
59
+ # spec id each element serves, which is exactly what analyze-plan-delta conformed, and an
60
+ # element naming none is a planning gap to surface rather than a traceability to invent.
45
61
  # But since the model split a plan CITES an invariant by id and never restates it, so the
46
62
  # bounded context's domain-model.md is in context too, as the authority on the entity
47
63
  # names, the aggregate boundary, the lifecycle states and the TEXT of every BR-nnn the plan
@@ -76,6 +92,14 @@
76
92
  # found only elsewhere in the repository is the service lane's, and accepting it is how a
77
93
  # frontend that was never scaffolded is generated into anyway and reports green.
78
94
  #
95
+ # A LANE'S GAP DISABLES THAT LANE, NOT THE RUN. Three things a lane needs are checked per
96
+ # lane and reported per lane: its source path is on disk, its plan is there, and a project
97
+ # is scaffolded under it. Any of them missing skips that lane with the command that fixes
98
+ # it named, and every other declared lane still generates; only when NOTHING can run does
99
+ # the run stop, with all the reasons printed. The whole-run stop is reserved for states
100
+ # with no correct lane to fall back on — no lane declared at all, a lane declaring no
101
+ # source path, and two lanes claiming overlapping subtrees.
102
+ #
79
103
  # WHAT IT DOES NOT DO. It never writes spec.md, a master plan or the domain model, and it
80
104
  # never closes a change — finalize-delta merges the change's spec and model halves,
81
105
  # analyze-slice verifies the slice, and close-delta archives it and bumps the version.
@@ -91,8 +115,8 @@
91
115
  # =============================================================================
92
116
 
93
117
  name: aw-spec-implement
94
- description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open it generates from the master plans; with one or more open it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, leaving the masters untouched. Every lane is gated on its own plan and its own scaffolded project, and a lane missing either is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
95
- version: 3.0.0
118
+ description: "Generate the aggregate's source files and paired tests from its lane plans — the backend lane from backend-plan.md when a backend stack is declared, the frontend lane from frontend-plan.md when a frontend stack is, both when both are. One command covers backend-only, frontend-only and full-stack projects and absorbs the former implement-ui and implement-delta. Self-detecting off the changes on disk — with none open it generates from the master plans; with one or more open it ASKS which change and generates only what that change's delta plans mark ADDED or MODIFIED, leaving the masters untouched. Every lane is gated on its own source path, its own plan and its own scaffolded project, and a lane missing any of them is reported and skipped rather than failing the other lane; each runnable lane confirms before it writes. Model-driven, and it does not read the spec — analyze-plan already conformed the plans to it. The plan says WHAT to build; the bounded context's domain-model.md, in context alongside it, says what the domain IS — the entity names, the boundary and the text of every invariant the plan cites by id — joined on the delta path by that change's own model delta, since the master is not merged until finalize-delta. The stack binds here from the lane declaration and the constitution halves; the plans stay technology-agnostic."
119
+ version: 3.4.0
96
120
  agent_agnostic: true
97
121
  dialect: aw-spec.orchestrator/v1
98
122
 
@@ -138,6 +162,20 @@ vars:
138
162
  delta_frontend_plan: ""
139
163
  delta_frontend_plan_exists: "false"
140
164
  wave: ""
165
+ # THE GATE RECEIPT — was this feature's plan pair ever PROVEN against this spec, and has
166
+ # anything moved since. analyze-plan writes it on a green master run; this phase is the
167
+ # reader. Bootstrap path only: a delta generates from delta plans, which analyze-plan-delta
168
+ # gates, and the master receipt says nothing about those.
169
+ gate_tracked: "false"
170
+ gate_fresh: "false"
171
+ gate_reason: ""
172
+ gate_at: ""
173
+ # DEFAULTED PERMISSIVE, DELIBERATELY, and it is the one flag here that is. Every tree that
174
+ # predates the receipt has none, and "never gated" is an unknown, not a stale plan —
175
+ # refusing on a missing file would block every existing feature on its first run. The flag
176
+ # is lowered only by a receipt that EXISTS and no longer matches: that tree has been through
177
+ # the gate once, so the bytes moving since is a fact, not an absence of one.
178
+ may_generate: "true"
141
179
  # PER-LANE STATE. Each lane resolves its own plan, its own project and its own answer;
142
180
  # a lane that is not declared never resolves any of them and its steps never run.
143
181
  # The engine has no compound conditions, so each AND is an ordered assignment: a step
@@ -182,22 +220,32 @@ workflow:
182
220
 
183
221
  # 0. THE LANE MODEL. lane-verify with NO --require, because this phase requires neither
184
222
  # kind: a backend-only and a frontend-only project are both correct states, and which
185
- # lanes exist is exactly what it is here to find out. It still reports every DECLARED
186
- # lane whose source path is empty, missing or overlapping another's — a stack named
187
- # against a path that is not there is a mistake in a phase that writes code — and it
188
- # always exits 0, so the reason reaches the gate below as text.
223
+ # lanes exist is exactly what it is here to find out.
224
+ #
225
+ # --allow-missing-root, because a lane whose source path is not on disk is THAT LANE'S
226
+ # gap, not the run's. This phase is per-lane throughout — a missing plan or a missing
227
+ # project is reported at step 5 and the other lane still generates — and a missing root
228
+ # is the same kind of gap, most often a declared frontend that `foundation` has not
229
+ # unpacked yet. With the check left on it arrives here instead, as a whole-run stop
230
+ # that takes the ready lane down with it. It is re-checked per lane at step 5 from
231
+ # `*_root_exists`, which lane-verify reports whether or not the on-disk half is armed.
232
+ #
233
+ # What still stops the run: no lane declared at all, a lane declaring no source path,
234
+ # and two lanes claiming overlapping subtrees. Those are declaration errors with no
235
+ # correct lane to fall back to, and none of them is a state any phase can work around.
189
236
  - id: lane_verify
190
237
  action: execute_script
191
238
  on_error: continue
192
239
  script: { ref: actions }
193
240
  subcommand: lane-verify
194
- args: ["--command", "/appweaver implement"]
241
+ args: ["--command", "/appweaver implement", "--allow-missing-root"]
195
242
  outputs:
196
243
  - { name: lane_ok, from: "$.ok" }
197
244
  - { name: lane_issues, from: "$.message" }
198
245
  - { name: be_declared, from: "$.backend_declared" }
199
246
  - { name: be_lane_id, from: "$.backend_lane_id" }
200
247
  - { name: be_source_path, from: "$.backend_source_path" }
248
+ - { name: be_root_exists, from: "$.backend_root_exists" }
201
249
  - { name: be_profile, from: "$.backend_profile" }
202
250
  - { name: be_title, from: "$.backend_title" }
203
251
  - { name: be_language, from: "$.backend_language" }
@@ -207,6 +255,7 @@ workflow:
207
255
  - { name: fe_declared, from: "$.frontend_declared" }
208
256
  - { name: fe_lane_id, from: "$.frontend_lane_id" }
209
257
  - { name: fe_source_path, from: "$.frontend_source_path" }
258
+ - { name: fe_root_exists, from: "$.frontend_root_exists" }
210
259
  - { name: fe_profile, from: "$.frontend_profile" }
211
260
  - { name: fe_title, from: "$.frontend_title" }
212
261
  - { name: fe_language, from: "$.frontend_language" }
@@ -318,6 +367,10 @@ workflow:
318
367
  outputs:
319
368
  - { name: cr_id, from: "$.cr_id" }
320
369
  - { name: cr_dir, from: "$.cr_dir" }
370
+ # Resolved for reporting and DELIBERATELY NOT put in any context — see the header.
371
+ # This phase realises the delta PLAN; the requirement text reaching the generator is
372
+ # what lets a gap in the plan be compensated for instead of found. Do not add it to
373
+ # ctx_backend_delta / ctx_frontend_delta.
321
374
  - { name: delta_spec, from: "$.delta_spec" }
322
375
  # THIS CHANGE'S MODEL HALF — the structure the delta plans realise. It is context for
323
376
  # every generating lane, exactly as the master model is: the delta plan cites the
@@ -350,6 +403,49 @@ workflow:
350
403
  action: set
351
404
  vars: { fe_plan: "{{delta_frontend_plan}}", fe_plan_ok: "{{delta_frontend_plan_exists}}" }
352
405
 
406
+ # 3b. WERE THESE PLANS PROVEN AGAINST THIS SPEC. This phase does not read spec.md — the
407
+ # header's reason is "analyze-plan already conformed the plans to it", which was an
408
+ # assumption about the order the operator ran things in and was checked by nothing:
409
+ # the lane gates below test that the plan FILE EXISTS and stop there. `plan`, edit
410
+ # spec.md, `implement` therefore generated from a plan that was never held against the
411
+ # spec it now contradicts, silently and at full length.
412
+ #
413
+ # plan-gate-check compares today's bytes against the receipt analyze-plan left when it
414
+ # passed — spec.md, the context's domain-model.md and each gated lane plan. The plans'
415
+ # own `**Version**:` headers cannot answer this and must not be used: close-delta stamps
416
+ # them and spec.md to the same number in lockstep whether or not a plan was re-derived,
417
+ # so the comparison reads equal always.
418
+ #
419
+ # BOOTSTRAP ONLY. A delta run generates from delta plans that analyze-plan-delta gates;
420
+ # the master receipt is silent about them, and reading it there would refuse a change
421
+ # for drift in documents this run is not building from.
422
+ - id: plan_gate_check
423
+ condition: "{{mode}} == bootstrap"
424
+ action: execute_script
425
+ script: { ref: actions }
426
+ subcommand: plan-gate-check
427
+ args: ["--feature-dir", "{{feature_dir}}"]
428
+ outputs:
429
+ - { name: gate_tracked, from: "$.tracked" }
430
+ - { name: gate_fresh, from: "$.gate_fresh" }
431
+ - { name: gate_reason, from: "$.reason" }
432
+ - { name: gate_at, from: "$.gated" }
433
+
434
+ # The ordered assignment is the AND: only a TRACKED feature can lower the flag, and
435
+ # what lowers it is the gate no longer being fresh. An untracked one never reaches
436
+ # this step's assignment and generates, which is the whole accommodation for trees
437
+ # that have not been gated since the receipt existed.
438
+ - id: flag_gate_block
439
+ condition: "{{gate_tracked}} == true"
440
+ action: set
441
+ vars: { may_generate: "{{gate_fresh}}" }
442
+
443
+ - id: require_gate_fresh
444
+ action: verify_context
445
+ require_true: may_generate
446
+ on_false: stop
447
+ on_false_message: "implement REFUSED — the master plans are no longer the ones analyze-plan passed: {{gate_reason}}. This phase generates from the plans and does NOT read the spec, on the strength of that gate having conformed them to it, so generating now would realise a plan against requirements nobody checked it against. Re-run /appweaver analyze-plan — it re-checks all four checks, repairs the drifting plan on approval, and re-issues the receipt — then re-run /appweaver implement. If what changed was a plan you re-derived with /appweaver plan main, that is the same path: the re-derived plans have not been gated yet. Nothing was generated."
448
+
353
449
  # 4. THE PROJECT each declared lane generates INTO. Deterministic structure gate:
354
450
  # detect-project searches the LANE'S OWN subtree for an ecosystem manifest (.sln/
355
451
  # .csproj, pom.xml, build.gradle, package.json, go.mod, Cargo.toml, pyproject.toml),
@@ -396,6 +492,12 @@ workflow:
396
492
  require_true: be_plan_ok
397
493
  on_false: continue
398
494
  on_false_message: "BACKEND LANE SKIPPED — lane `{{be_lane_id}}` ({{be_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{be_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
495
+ - id: warn_be_root
496
+ condition: "{{be_declared}} == true"
497
+ action: verify_context
498
+ require_true: be_root_exists
499
+ on_false: continue
500
+ on_false_message: "BACKEND LANE SKIPPED — lane `{{be_lane_id}}` ({{be_title}}) declares source path `{{be_source_path}}` in .appweaver/stacks.json, and that path is not a directory in this repository. A lane owns its subtree and this phase writes into it, so there is nothing to generate into: unpack a project there with /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{be_profile}}, create one with the stack's own tooling, or re-run /appweaver select-stack and point the lane at the directory that really holds its source. Nothing is generated for this lane; any other declared lane still runs."
399
501
  - id: warn_be_project
400
502
  condition: "{{be_declared}} == true"
401
503
  action: verify_context
@@ -408,6 +510,12 @@ workflow:
408
510
  require_true: fe_plan_ok
409
511
  on_false: continue
410
512
  on_false_message: "FRONTEND LANE SKIPPED — lane `{{fe_lane_id}}` ({{fe_title}}) is declared in .appweaver/stacks.json, but the plan this run generates from is not there: {{fe_plan}}. Run /appweaver plan (then /appweaver analyze-plan for the masters, or /appweaver analyze-plan-delta for a change) and re-run implement. Nothing is generated for this lane; any other declared lane still runs."
513
+ - id: warn_fe_root
514
+ condition: "{{fe_declared}} == true"
515
+ action: verify_context
516
+ require_true: fe_root_exists
517
+ on_false: continue
518
+ on_false_message: "FRONTEND LANE SKIPPED — lane `{{fe_lane_id}}` ({{fe_title}}) declares source path `{{fe_source_path}}` in .appweaver/stacks.json, and that path is not a directory in this repository. A lane owns its subtree and this phase writes into it, so there is nothing to generate into: unpack a project there with /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{fe_profile}}, create one with the stack's own tooling, or re-run /appweaver select-stack and point the lane at the directory that really holds its source. Nothing is generated for this lane; any other declared lane still runs."
411
519
  - id: warn_fe_project
412
520
  condition: "{{fe_declared}} == true"
413
521
  action: verify_context
@@ -415,14 +523,18 @@ workflow:
415
523
  on_false: continue
416
524
  on_false_message: "FRONTEND LANE SKIPPED — no frontend project found under the {{fe_lane_id}} lane's source path `{{fe_source_path}}`. detect-project searched that subtree and then the whole repository, and answered at scope `{{fe_project_scope}}`; `lane` is the only scope this lane accepts. Scaffold a project there — /appweaver foundation <FoundationName> <AppName> where a bundle ships for {{fe_profile}}, or the stack's own tooling — and re-run implement. A manifest found elsewhere in the repository does NOT count: that one belongs to another lane, and generating this lane's code against it is how a frontend that was never built reports green."
417
525
 
418
- # 6. RUNNABLE = declared AND its plan is there AND its project is there, as an ordered
419
- # assignment plus a knockdown, since the engine has no compound conditions. An
420
- # undeclared lane stays false either way — its `declared` value is what the raise
421
- # assigns.
526
+ # 6. RUNNABLE = declared AND its source path is on disk AND its plan is there AND its
527
+ # project is there, as one ordered assignment plus a knockdown per condition, since
528
+ # the engine has no compound conditions. An undeclared lane stays false either way —
529
+ # its `declared` value is what the raise assigns.
422
530
  - id: be_runnable_raise
423
531
  condition: "{{be_plan_ok}} == true"
424
532
  action: set
425
533
  vars: { be_runnable: "{{be_declared}}" }
534
+ - id: be_runnable_clear_root
535
+ condition: "{{be_root_exists}} == false"
536
+ action: set
537
+ vars: { be_runnable: "false" }
426
538
  - id: be_runnable_clear
427
539
  condition: "{{be_project_ok}} == false"
428
540
  action: set
@@ -431,6 +543,10 @@ workflow:
431
543
  condition: "{{fe_plan_ok}} == true"
432
544
  action: set
433
545
  vars: { fe_runnable: "{{fe_declared}}" }
546
+ - id: fe_runnable_clear_root
547
+ condition: "{{fe_root_exists}} == false"
548
+ action: set
549
+ vars: { fe_runnable: "false" }
434
550
  - id: fe_runnable_clear
435
551
  condition: "{{fe_project_ok}} == false"
436
552
  action: set
@@ -471,7 +587,7 @@ workflow:
471
587
  action: verify_context
472
588
  require_true: generate_any
473
589
  on_false: stop
474
- on_false_message: "Nothing to implement — no declared lane is both ready and confirmed. Either every declared lane was skipped for a reason printed above (its plan or its project is missing), or each one that was ready was declined at the prompt. Backend lane declared {{be_declared}}, ready {{be_runnable}}; frontend lane declared {{fe_declared}}, ready {{fe_runnable}}. Fix the reasons named above, or re-run and confirm a lane."
590
+ on_false_message: "Nothing to implement — no declared lane is both ready and confirmed. Either every declared lane was skipped for a reason printed above (its source path, its plan or its project is missing), or each one that was ready was declined at the prompt. Backend lane declared {{be_declared}}, ready {{be_runnable}}; frontend lane declared {{fe_declared}}, ready {{fe_runnable}}. Fix the reasons named above, or re-run and confirm a lane."
475
591
 
476
592
  # 8. EACH GENERATING LANE'S authoring MODE — the run's mode where the lane is going, and
477
593
  # `skip` where it is not, which silences both of that lane's branches without a
@@ -490,18 +606,21 @@ workflow:
490
606
  # backend's heading is keyed by mode (see the header). No blocking gate: an
491
607
  # unconfigured project soft-continues, since the constitution halves still carry each
492
608
  # lane's ratified base.
609
+ #
610
+ # EACH LANE'S RULES ARE LOADED BESIDE ITS OWN STEPS, AND THE SECOND LANE REPLACES
611
+ # THE FIRST'S. Both includes used to run here, before either lane generated — and
612
+ # sticky standards only accumulate, so on a full-stack run the backend generator was
613
+ # shown `## /aw-spec-implement-ui` and the frontend generator the service set. Each
614
+ # prompt says which lane it is in, but it said so inside an envelope that contradicted
615
+ # it, and the envelope is the part the model cannot argue with. The frontend include
616
+ # now sits immediately before the FRONTEND sections below and passes replace, which
617
+ # resets to the workflow's declared seed (ai-rules) before loading the UI set.
493
618
  - id: tech_select
494
619
  condition: "{{be_go}} == true"
495
620
  action: include
496
621
  module: .appweaver/orchestrator/modules/tech-select.orch.yaml
497
622
  with: { command: "{{be_tech_command}}" }
498
623
 
499
- - id: tech_select_ui
500
- condition: "{{fe_go}} == true"
501
- action: include
502
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
503
- with: { command: aw-spec-implement-ui }
504
-
505
624
  # 10. Pre-execution hooks — see the hook-phase vars above.
506
625
  - id: resolve_pre_hooks
507
626
  action: execute_script
@@ -605,14 +724,22 @@ workflow:
605
724
  # ═══════════ BACKEND LANE — delta, generate ONLY what the change adds ════════
606
725
  # The delta plan is the authoritative source of WHAT changed — analyze-plan-delta already
607
726
  # conformed it to the delta spec. The MASTER plan is the BASE (the unchanged elements the
608
- # delta depends on), and the delta spec is here ONLY so the paired tests can cite
609
- # traceability ids.
727
+ # delta depends on).
728
+ #
729
+ # NO DELTA-SPEC HERE, exactly as the bootstrap path carries no spec. It used to be
730
+ # inlined "only so the paired tests can cite traceability ids", but a document in the
731
+ # payload is a document the generator can reason from, and the whole point of the
732
+ # contract is that this phase realises the PLAN: with the requirement text in reach, a
733
+ # gap in the plan can be silently compensated for from the spec and the plan is never
734
+ # found to be wrong. The ids are still available and still required — the delta plan
735
+ # cites the spec id each element serves, which is what analyze-plan-delta conformed.
736
+ # A plan that does not name an id is a planning gap to surface, and that is now visible
737
+ # instead of being papered over here.
610
738
  - id: ctx_backend_delta
611
739
  condition: "{{be_mode}} == delta"
612
740
  action: build_context
613
741
  context:
614
742
  - { path: "{{be_plan}}" } # this change's delta-backend-plan.md
615
- - { path: "{{delta_spec}}", optional: true }
616
743
  - { path: "{{delta_model}}", optional: true } # this change's MODEL half — what it ADDS to / CHANGES in the model
617
744
  - { path: "{{backend_plan}}", optional: true } # the master plan — READ ONLY, never amended here
618
745
  - { path: "{{domain_model}}", optional: true } # the master model — the domain as it stands BEFORE this change
@@ -642,9 +769,12 @@ workflow:
642
769
  winning wherever they differ — it is the newer statement, and the master has not
643
770
  been merged yet (finalize-delta does that after this phase). Read there what every
644
771
  BR-nnn the delta plan cites actually requires; the plan records WHERE each is
645
- enforced and never restates it. You have NO domain rule files and NO spec beyond
646
- the delta-spec's ids — if a domain specific is in none of them, STOP and surface it
647
- (it is fixed in specify / plan / analyze-plan-delta), never invent it from memory.
772
+ enforced and never restates it. You have NO domain rule files and NO spec AT ALL —
773
+ not the master and not this change's delta-spec. The requirement ids are in the
774
+ delta plan, which cites the one each element serves; that is the whole of what you
775
+ get. If a domain specific is in neither the plan nor the model, STOP and surface it
776
+ (it is fixed in specify / plan / analyze-plan-delta), never invent it from memory
777
+ and never infer it from an id.
648
778
  - THE MODEL IS READ, NEVER MINED FOR WORK. Realise only what the DELTA PLAN calls
649
779
  for: an item in the model delta that no delta-plan element realises is a PLANNING
650
780
  gap — surface it, do not implement it. Everything in the master model that this
@@ -682,7 +812,10 @@ workflow:
682
812
  the port isn't present, code to it and note that as the prerequisite.
683
813
  - Author a paired test for every ADDED/MODIFIED behaviour / use-case / endpoint;
684
814
  derive the cases from the delta plan's pre/post/invariant contracts and cite the
685
- delta-spec id (FR / BR / CN / DC / IC / US).
815
+ requirement id THE DELTA PLAN NAMES for that element (FR / BR / CN / DC / IC / US).
816
+ The spec is not in context: take the id from the plan and cite it verbatim, never
817
+ guess one from the element's shape. A delta-plan element that names no id is a
818
+ planning gap — surface it rather than inventing the traceability.
686
819
  - REGRESSION: for each element in the delta plan's "Regression targets", verify (add
687
820
  or adjust a test where needed) that the existing behaviour still holds after a
688
821
  shared element changed — but do NOT rewrite that existing behaviour.
@@ -714,6 +847,22 @@ workflow:
714
847
  outputs:
715
848
  - { name: okf_concept, from: "$.concept" }
716
849
 
850
+ # ══════════════ THE LANE SWAP — the frontend's rules replace the backend's ═══
851
+ # build_context already scoped the two lanes' PAYLOADS apart; this is the same boundary
852
+ # for their STANDARDS, which the envelope carries and which nothing used to reset. It
853
+ # runs here rather than up with the backend's include because standards accumulate:
854
+ # loading both before either lane generated is precisely what showed each generator the
855
+ # other lane's rule set. `replace` resets to the declared seed (ai-rules stays) and then
856
+ # loads `## /aw-spec-implement-ui`, so from here down the envelope is the frontend's.
857
+ #
858
+ # Nothing after the frontend sections regenerates backend code — the OKF steps that
859
+ # follow run include_standards: false — so the swap needs no undo.
860
+ - id: tech_select_ui
861
+ condition: "{{fe_go}} == true"
862
+ action: include
863
+ module: .appweaver/orchestrator/modules/tech-select.orch.yaml
864
+ with: { command: aw-spec-implement-ui, replace: "true" }
865
+
717
866
  # ═══════════ FRONTEND LANE — bootstrap, generate from the MASTER plan ════════
718
867
  # The experience twin of the service generation, one lane over. Its own build_context,
719
868
  # because the envelope persists across steps and the two lanes must not see each other's
@@ -815,7 +964,8 @@ workflow:
815
964
  action: build_context
816
965
  context:
817
966
  - { path: "{{fe_plan}}" } # this change's delta-frontend-plan.md
818
- - { path: "{{delta_spec}}", optional: true }
967
+ # No delta-spec — see the service lane's note above: this phase realises the PLAN,
968
+ # and the ids the tests cite come from the plan's own rows.
819
969
  - { path: "{{delta_model}}", optional: true } # this change's MODEL half — new terms, states and rules the UI must speak
820
970
  - { path: "{{frontend_plan}}", optional: true } # the master plan — READ ONLY, never amended here
821
971
  - { path: "{{domain_model}}", optional: true } # the master model — the ubiquitous language as it stands BEFORE this change
@@ -846,9 +996,10 @@ workflow:
846
996
  READ TOGETHER, the delta winning wherever they differ — the master has not been
847
997
  merged yet. A TERM THIS CHANGE INTRODUCES OR RENAMES IS IN THE DELTA, and the UI
848
998
  must speak it exactly as the delta spells it, in labels, states and messages. Read
849
- there what any BR-nnn the delta plan cites requires. NO domain rule files, and no
850
- spec beyond the delta-spec's ids; if a domain specific is in none of them, STOP and
851
- surface it.
999
+ there what any BR-nnn the delta plan cites requires. NO domain rule files, and NO
1000
+ spec at all — not the master and not this change's delta-spec; the requirement ids
1001
+ come from the delta plan, which cites the one each element serves. If a domain
1002
+ specific is in neither the plan nor the model, STOP and surface it.
852
1003
  - THE MODEL IS READ, NEVER MINED FOR WORK, and reading a rule is not permission to
853
1004
  move it. Realise only what the DELTA PLAN calls for — an item in the model delta
854
1005
  that no delta-plan element realises is a PLANNING gap, and a client-side check
@@ -872,7 +1023,9 @@ workflow:
872
1023
  evaluation.
873
1024
  - Author a paired test for every ADDED/MODIFIED screen, interaction and state,
874
1025
  including the unwanted-behaviour states and an accessibility assertion per
875
- interactive surface, citing the delta-spec id (UX-/US-/FR-/CN-/DC-/IC-/SC-).
1026
+ interactive surface, citing the requirement id THE DELTA PLAN NAMES for that row
1027
+ (UX-/US-/FR-/CN-/DC-/IC-/SC-). The spec is not in context: take the id from the
1028
+ plan and cite it verbatim. A row that names no id is a planning gap — surface it.
876
1029
  - REGRESSION: for each element in the delta plan's regression targets, verify (add or
877
1030
  adjust a test where needed) that the existing behaviour still holds after a shared
878
1031
  component or state container changed — but do NOT rewrite that behaviour.
@@ -1010,5 +1163,11 @@ outputs:
1010
1163
  delta_model: "{{delta_model}}" # its second half on the delta path — empty on the bootstrap path
1011
1164
  okf_concept: "{{okf_concept}}"
1012
1165
  xref_edge_count: "{{xref_edge_count}}"
1013
- note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else."
1166
+ # Bootstrap path only. tracked=false means this feature has never passed analyze-plan
1167
+ # since the gate began leaving a receipt — an unknown, not a stale plan, and it blocks
1168
+ # nothing.
1169
+ plan_gate_tracked: "{{gate_tracked}}"
1170
+ plan_gate_fresh: "{{gate_fresh}}"
1171
+ plan_gate_at: "{{gate_at}}"
1172
+ note: "Lanes come from .appweaver/stacks.json — backend declared {{be_declared}} (ready {{be_runnable}}, implemented {{be_go}}), frontend declared {{fe_declared}} (ready {{fe_runnable}}, implemented {{fe_go}}). A declared lane that was skipped printed its reason above and did not stop the other one; an undeclared lane produces nothing and that is not a failure. Each generating lane read its PLAN for what to build and {{domain_model}} for what the domain is — plus {{delta_model}} on the delta path, since the master model does not carry the change until finalize-delta merges it. Every one of those documents was READ and none was written: this phase generates code and nothing else. On the bootstrap path the plans were checked against analyze-plan's receipt before anything generated (tracked={{gate_tracked}}, fresh={{gate_fresh}}, gated {{gate_at}}) — tracked=false means this feature has never passed analyze-plan since the gate began recording what it proved, which is an unknown rather than a stale plan and blocks nothing; run /appweaver analyze-plan once to start the record."
1014
1173
  next: "This run was mode `{{mode}}`, change `{{active_change}}` (empty on the bootstrap path). BOOTSTRAP → /appweaver build → analyze-slice → run. DELTA → /appweaver build → analyze-slice-delta {{active_change}} → finalize-delta → analyze-slice → close-delta, with the change still OPEN throughout — compile first, then analyze-slice-delta verifies that every element the delta plans marked ADDED or MODIFIED is actually in the code with a paired test (the gate that catches a lane you declined at the prompt above), then finalize-delta merges it into spec.md + the context's domain-model.md while leaving it open, analyze-slice verifies the slice against those merged masters while a red result is still recoverable, and close-delta LAST archives the change and bumps the version."
@@ -9,8 +9,8 @@
9
9
  # 1. FEATURE MAP — a HIGH-LEVEL OKF discovery index of the features/aggregates the
10
10
  # code contains (folders/concepts + a bounded-context map + the tech stack). It
11
11
  # is a shallow map to pick from, NOT a deep reverse — the deep per-feature
12
- # reverse (spec.md + design.md) is /appweaver adopt, done just-in-time when
13
- # you are about to develop that feature.
12
+ # reverse (spec.md + the context's domain-model.md + the lane plans) is
13
+ # /appweaver adopt, done just-in-time when you are about to develop that feature.
14
14
  #
15
15
  # 2. GOVERNANCE SCAFFOLD — the PROJECT-level governing tier, established once from
16
16
  # the whole-repo vantage so forward development is consistent from the first
@@ -123,7 +123,7 @@
123
123
 
124
124
  name: aw-spec-onboard
125
125
  description: Onboard an existing codebase for brownfield DEVELOPMENT — a deterministic whole-repo scan produces a high-level OKF feature map (folders/concepts) PLUS the project-level governance scaffold (split constitution + guardrails/projects/brf-<domain>/ with its DOMAIN rule files at domain/ + domain-rule-bindings + code-derived TECHNICAL rule files at service/ + tech-rule-bindings + activated domain), all provisional-from-scan and refined per slice by adopt. Where the scan finds a frontend lane in the CODE, it also authors that lane's half — constitution.ui.md (UX-*) — and its code-derived rule files at brf-<domain>/frontend/, mapped under `## /aw-spec-ui-constitution`; a backend-only repo gets none of it. Every rule tier is reverse-engineered from the CODE alone (never a greenfield rule file), evidenced-only, and WIRED per-workflow so the whole forward pipeline AND adopt govern against THIS codebase — never a greenfield fallback. It also reverse-engineers the repo's own technical structure into a project-specific design + delta-design template pair, installed as the project template overrides, and — where the lane exists — the frontend lane's own pair from the UI source. Every authoring step is shaped by a NEUTRAL rev-templates skeleton; no greenfield constitution, rule file or document template is ever placed in context. Accepts an optional concerns list to look for (onboard decides which are actually present). Reads any declared lane, never writes one. No migration content; never modifies source.
126
- version: 1.6.0
126
+ version: 1.7.0
127
127
  agent_agnostic: true
128
128
  dialect: aw-spec.orchestrator/v1
129
129
 
@@ -405,8 +405,9 @@ workflow:
405
405
 
406
406
  THIS IS A MAP, NOT A DEEP REVERSE. Keep every feature concept SHALLOW — enough
407
407
  to recognize and pick the feature to develop next. The deep per-feature reverse
408
- (spec.md + design.md) is /appweaver adopt, run just-in-time when a feature is
409
- about to be developed. Do NOT deep-model invariants/behaviour here.
408
+ (spec.md + the context's domain-model.md + the lane plans) is /appweaver adopt, run
409
+ just-in-time when a feature is about to be developed. Do NOT deep-model
410
+ invariants/behaviour here.
410
411
 
411
412
  Read the substrate first. Its `aggregate_signals` are CANDIDATE feature roots,
412
413
  each with its files grouped by layer plus detected entities; `ui_attribution`
@@ -648,8 +649,8 @@ workflow:
648
649
  charter; a later step in this same run authors it, so wire the bullet now), then the
649
650
  domain identity/regulatory/interop files → domain principles.
650
651
  - `## /aw-spec-specify` — the FULL domain set (specify models the domain, lifts CN-/DC-/IC-).
651
- - `## /aw-spec-plan` — `_None — design loads no domain file. Domain reaches design only through the spec._`
652
- - `## /aw-spec-implement` — `_None — implement loads no domain file. Domain reaches implement only through design.md._`
652
+ - `## /aw-spec-plan` — `_None — plan loads no domain file. Domain reaches plan through the spec and the context's domain-model.md._`
653
+ - `## /aw-spec-implement` — `_None — implement loads no domain file. Domain reaches implement through the lane plans and the context's domain-model.md._`
653
654
  - `## /aw-spec-gate` — the conformance-checklist set (the *.domain.md files).
654
655
  Each bullet is a workspace-relative path `- guardrails/projects/{{domain_slug}}/domain/<file>`.
655
656
  Write ONLY inside guardrails/projects/{{domain_slug}}/ (domain/ + the map). Report the
@@ -1415,7 +1416,13 @@ workflow:
1415
1416
  # override_active ⇒ design_mode == bootstrap_bf) then authors EVERY new feature against the
1416
1417
  # as-built architecture, and a CHANGE authors its delta-design against the SAME code-derived
1417
1418
  # sections (cr-setup scaffolds the delta from resolve_template("delta-design-template") ⇒
1418
- # the override) — no adopt required, consistent with the master design.md adopt emits.
1419
+ # the override) — no adopt required, consistent with the backend-plan.md adopt emits.
1420
+ #
1421
+ # THE TEMPLATE IDENTIFIERS KEEP THE `design-template` SPELLING DELIBERATELY. The DOCUMENTS
1422
+ # are backend-plan.md / frontend-plan.md now; the template NAMES are not, because a repo
1423
+ # that already ran onboard carries its override at overrides/design-template.md and
1424
+ # renaming the identifier would make that file stop resolving — silently falling back to
1425
+ # the kit's greenfield template and imposing a layering the codebase never chose.
1419
1426
  #
1420
1427
  # GROUNDED IN CODE + REVERSE-ENGINEERED FILES ONLY. The Part-1 OKF feature map is a SHALLOW
1421
1428
  # discovery index (by design — NOT a deep reverse), so it is NOT the authority: the section
@@ -1597,6 +1604,83 @@ workflow:
1597
1604
  on_empty: stop
1598
1605
  on_empty_message: "No delta-design template was written to .appweaver/templates/overrides/delta-design-template.md. Re-run onboard after confirming the design template override was generated."
1599
1606
 
1607
+ # ═══════ CODE-DERIVED DOMAIN-MODEL TEMPLATE (the context's structure) ════════
1608
+ # The same argument as the design pair, for the document that carries the DOMAIN. A
1609
+ # bounded context's domain-model.md is where the aggregate root, its boundary, its
1610
+ # entities and the TEXT of every BR-nnn live; `specify` fills it on the forward path and
1611
+ # `adopt` fills it per reversed slice. Without a code-derived override both author
1612
+ # against the kit's GREENFIELD model template, whose shape assumes a domain that was
1613
+ # designed before it was built — the one assumption a brownfield repo is guaranteed to
1614
+ # break.
1615
+ #
1616
+ # NOT GATED ON A LANE. The domain model is ubiquitous language and invariants, which a
1617
+ # frontend-only project has as much as any other; WHERE an invariant is enforced is the
1618
+ # backend plan's concern and is gated with the service block above.
1619
+ #
1620
+ # A TEMPLATE OF A TEMPLATE. What this writes is not a filled model — it is the project's
1621
+ # domain-model TEMPLATE, keeping every [PLACEHOLDER] and authoring comment. What onboard
1622
+ # replaces is the STRUCTURE, never the content, and it must name NO aggregate and NO
1623
+ # entity: naming one would hand the downstream author the answer to the single question
1624
+ # the reverse exists to ask the code.
1625
+ - id: ctx_model_template
1626
+ action: build_context
1627
+ context:
1628
+ - { path: "{{inventory_json}}" } # PRIMARY: the COMPLETE deterministic substrate
1629
+ - { path: .appweaver/orchestrator/rev-templates/domain-model-template.skeleton.md } # SHAPE ONLY: the neutral bones + the pinned anchors
1630
+ - { path: "{{constitution_domain_file}}", optional: true } # the reversed DOMAIN frame — vocabulary, not structure
1631
+ - { path: "{{bundle_dir}}/aggregates/index.md", optional: true } # SECONDARY (shallow): the bounded-context / feature map
1632
+ - id: gen_model_template
1633
+ action: agent_generate
1634
+ tools: "Read,Write,Edit"
1635
+ permission_mode: acceptEdits
1636
+ task: |
1637
+ REVERSE-ENGINEER this repo's DOMAIN MODEL convention into a PROJECT-SPECIFIC
1638
+ domain-model TEMPLATE and Write it to
1639
+ .appweaver/templates/overrides/domain-model-template.md (create the overrides/ dir if
1640
+ absent). It is a TEMPLATE — section headers with [PLACEHOLDER] tokens + authoring
1641
+ HTML comments that `specify` (forward) and `adopt` (per reversed slice) fill per
1642
+ context — NOT a filled model.
1643
+
1644
+ GROUND EVERYTHING IN THE CODE. Read REPRESENTATIVE source files; the OKF map in
1645
+ context is a SHALLOW discovery index and corroborates, it does not decide.
1646
+
1647
+ KEEP THE PINNED ANCHORS from the skeleton in context — each is machinery something
1648
+ keys on, not taste:
1649
+ - `## Aggregate: <Name>` is the MERGE UNIT. finalize-delta merges a model delta one
1650
+ such section at a time, so the heading FORM cannot be reshaped per project. Keep it
1651
+ as ONE repeatable block.
1652
+ - `### Invariants` carries stable BR-nnn ids — specs cite them and the conformance
1653
+ gates trace them, so the id class cannot be renamed.
1654
+ - `**Serving specs**:` is what makes a cross-spec aggregate visible from the model
1655
+ side; keep it.
1656
+ - The Change History table is the model's version ledger. close-delta appends to it
1657
+ and stamps the context version; the model never writes its own.
1658
+
1659
+ WHAT YOU MAY ADAPT is everything else: which subsections an aggregate section carries
1660
+ and what they are CALLED, in this codebase's own vocabulary. If the code expresses
1661
+ structure through something it names differently, template THAT and say in a comment
1662
+ which code paths evidence it.
1663
+
1664
+ NAME NO AGGREGATE, NO ENTITY AND NO TECHNOLOGY. Every [BRACKET] token stays a token.
1665
+ Write the authoring comments so the downstream author derives from the code the way
1666
+ the skeleton's BROWNFIELD DERIVATION note describes — aggregate roots from the types
1667
+ referenced by id but never navigated into and the transaction boundaries the
1668
+ persistence layer commits; entity vs value object by IDENTITY; invariants from the
1669
+ guards and the persistence constraints, recording that the CODE is their current
1670
+ authority; the ubiquitous language from the names the code actually uses, recording
1671
+ BOTH where code and business disagree rather than silently picking one.
1672
+
1673
+ Write ONLY the override file; modify no source. Report the file written and the
1674
+ structural choices you derived, with the code paths that evidence them.
1675
+ outputs:
1676
+ - { name: model_template_files, from: "$.files" }
1677
+
1678
+ - id: verify_model_template
1679
+ action: verify_context
1680
+ require_non_empty: model_template_files
1681
+ on_empty: stop
1682
+ on_empty_message: "No domain-model template was written to .appweaver/templates/overrides/domain-model-template.md. Without it every reversed slice authors its aggregate against the kit's GREENFIELD model template, whose shape assumes a domain designed before it was built. Ensure the scan substrate (inventory_json) is present and re-run."
1683
+
1600
1684
  # ═══════ UI DESIGN TEMPLATES (frontend lane only) — the LANE's own pair ═══════
1601
1685
  # The lane's twin of the two steps above, on the same terms: reverse-engineered from
1602
1686
  # the UI SOURCE, shaped by a NEUTRAL skeleton, installed in the project override slot
@@ -1820,6 +1904,7 @@ outputs:
1820
1904
  delta_design_template_override: ".appweaver/templates/overrides/delta-design-template.md"
1821
1905
  design_template_files: "{{design_template_files}}"
1822
1906
  delta_design_template_files: "{{delta_design_template_files}}"
1907
+ model_template_files: "{{model_template_files}}" # the context's domain-model template — not lane-gated
1823
1908
  # The lane's own template pair. Empty on a backend-only repo — no lane, no files.
1824
1909
  ui_design_template_files: "{{ui_design_template_files}}"
1825
1910
  ui_delta_design_template_files: "{{ui_delta_design_template_files}}"
@@ -85,7 +85,7 @@
85
85
 
86
86
  name: aw-spec-plan
87
87
  description: "Author the feature's lane plans from the spec and the bounded context's domain model — backend-plan.md when a backend lane is declared, frontend-plan.md when a frontend lane is, both when both are. Lanes come from .appweaver/stacks.json, so one command covers backend-only, frontend-only and full-stack projects; the former separate design-ui command is folded in. It ASKS what it is planning, the way implement asks what it is building: the master (`main`), one of the OPEN CHANGES, or a new plan-only change for a refactor with no spec delta — the argument doubles as the answer, and the menu appears only once a master exists, so a first run bootstraps with no prompt. `main` on a feature that already has masters RE-DERIVES them from the merged spec.md + domain-model.md — the operation finalize-delta's contract assumes when it merges a change's spec and model halves and no lane plan — and is confirmed before it rewrites anything. A change instead gets delta-backend-plan.md / delta-frontend-plan.md inside changes/<CR-id>/ while the masters stay untouched, written against the effective model (the master model, the change's own model delta, and the pending model deltas of LOWER waves, which are its predecessors) and against the feature's real source files from its OKF concept. Master authoring is template-driven: the kit's templates by default, or the code-derived as-built structure when onboard installed an override. Authoring only — a master gates at analyze-plan; a change ships through the CR chain."
88
- version: 5.0.0
88
+ version: 5.1.0
89
89
  agent_agnostic: true
90
90
  dialect: aw-spec.orchestrator/v1
91
91
 
@@ -200,18 +200,21 @@ workflow:
200
200
  # project's standards are exactly what they were before the two commands merged.
201
201
  # No blocking gate: an unconfigured project soft-continues (the constitutions still
202
202
  # carry each lane's ratified base).
203
+ #
204
+ # ONLY THE BACKEND'S RULES LOAD HERE. The frontend's used to load alongside them,
205
+ # before either lane authored anything, and sticky standards only accumulate — so the
206
+ # service plan was authored with `## /aw-spec-plan-ui` in its envelope and the
207
+ # experience plan with the service set. Each prompt names its lane, but it said so
208
+ # inside an envelope that contradicted it. The UI set now loads at the frontend
209
+ # boundary instead, with `replace`, in both places a frontend document is authored:
210
+ # tech_select_ui_master before the master frontend sections, and the delta-plan
211
+ # module's own swap before the frontend delta plan.
203
212
  - id: tech_select
204
213
  condition: "{{be_declared}} == true"
205
214
  action: include
206
215
  module: .appweaver/orchestrator/modules/tech-select.orch.yaml
207
216
  with: { command: aw-spec-plan }
208
217
 
209
- - id: tech_select_ui
210
- condition: "{{fe_declared}} == true"
211
- action: include
212
- module: .appweaver/orchestrator/modules/tech-select.orch.yaml
213
- with: { command: aw-spec-plan-ui }
214
-
215
218
  # 2. Resolve the current feature and require its spec.
216
219
  - id: feature_paths
217
220
  action: execute_script
@@ -722,6 +725,23 @@ workflow:
722
725
  (the spec ids and BR-nnn that drove it). Do NOT invent a version number — the
723
726
  version spine belongs to close-delta.
724
727
 
728
+ # ══════════════ THE LANE SWAP — the frontend's rules replace the backend's ═══
729
+ # Both tech-select includes used to run up at step 4, before either lane authored
730
+ # anything, and sticky standards only accumulate — so on a full-stack run the backend
731
+ # plan was authored with `## /aw-spec-plan-ui` in its envelope and the frontend plan with
732
+ # the service set. Each prompt names its lane, but it did so inside an envelope that
733
+ # contradicted it, and the envelope is the half the model cannot argue with.
734
+ #
735
+ # `fe_mode != skip` is what makes this the MASTER path's swap and not the delta path's:
736
+ # fe_mode is only set to a real mode by the master branches above, so on a `plan CR-…`
737
+ # run this does not fire and the delta module below does its own swap at its own
738
+ # frontend boundary.
739
+ - id: tech_select_ui_master
740
+ condition: "{{fe_mode}} != skip"
741
+ action: include
742
+ module: .appweaver/orchestrator/modules/tech-select.orch.yaml
743
+ with: { command: aw-spec-plan-ui, replace: "true" }
744
+
725
745
  # ═══════════ FRONTEND LANE — bootstrap, greenfield experience template ═══════
726
746
  # The experience twin of the service plan, and the MEDIATOR between spec.md and the
727
747
  # frontend code. Its sections are the experience, not the four clean-architecture
@@ -993,7 +1013,14 @@ workflow:
993
1013
  - { name: wave, from: "$.wave" }
994
1014
  - { name: predecessor_files, from: "$.predecessor_files" }
995
1015
  - { name: predecessor_count, from: "$.predecessor_count" }
1016
+ - { name: predecessor_ids, from: "$.predecessor_ids" }
996
1017
  - { name: sibling_count, from: "$.sibling_count" }
1018
+ - { name: sibling_ids, from: "$.sibling_ids" }
1019
+ # Predecessors AND siblings in one list: the delta plan is authored against both, and
1020
+ # a build_context takes a single `context_from:` variable. Which entries are which is
1021
+ # carried by predecessor_ids / sibling_ids, because the planner's licence over the two
1022
+ # is opposite — a predecessor may be built on, a sibling may only be designed around.
1023
+ - { name: related_docs, from: "$.related_docs" }
997
1024
 
998
1025
  # Author that change's delta plans — one per declared lane whose MASTER exists, inside
999
1026
  # changes/<CR-id>/ beside its delta-spec. The masters are left UNTOUCHED; the change