aw-spec-cli 0.1.33 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +76 -81
  2. package/assets/.appweaver/DESIGN-FORMAT.md +57 -25
  3. package/assets/.appweaver/STACK-PROFILES.md +95 -60
  4. package/assets/.appweaver/orchestrator/LAUNCHER.md +1 -1
  5. package/assets/.appweaver/orchestrator/modules/delta-plan.orch.yaml +173 -32
  6. package/assets/.appweaver/orchestrator/modules/domain-select.orch.yaml +29 -1
  7. package/assets/.appweaver/orchestrator/modules/foundation-pack.orch.yaml +7 -4
  8. package/assets/.appweaver/orchestrator/modules/foundation-spec.orch.yaml +49 -60
  9. package/assets/.appweaver/orchestrator/modules/tech-select.orch.yaml +58 -0
  10. package/assets/.appweaver/orchestrator/orchestrator.py +26 -8
  11. package/assets/.appweaver/orchestrator/rev-templates/delta-design-template.skeleton.md +20 -20
  12. package/assets/.appweaver/orchestrator/rev-templates/design-template.skeleton.md +22 -48
  13. package/assets/.appweaver/orchestrator/rev-templates/ui-delta-design-template.skeleton.md +9 -7
  14. package/assets/.appweaver/orchestrator/rev-templates/ui-design-template.skeleton.md +5 -5
  15. package/assets/.appweaver/orchestrator/workflows/aw-spec-adopt.orch.yaml +239 -40
  16. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan-delta.orch.yaml +254 -43
  17. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-plan.orch.yaml +248 -68
  18. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice-delta.orch.yaml +251 -20
  19. package/assets/.appweaver/orchestrator/workflows/aw-spec-analyze-slice.orch.yaml +316 -10
  20. package/assets/.appweaver/orchestrator/workflows/aw-spec-build.orch.yaml +6 -47
  21. package/assets/.appweaver/orchestrator/workflows/aw-spec-close-delta.orch.yaml +62 -16
  22. package/assets/.appweaver/orchestrator/workflows/aw-spec-finalize-delta.orch.yaml +132 -9
  23. package/assets/.appweaver/orchestrator/workflows/aw-spec-fix-test-coverage.orch.yaml +965 -0
  24. package/assets/.appweaver/orchestrator/workflows/aw-spec-implement.orch.yaml +222 -169
  25. package/assets/.appweaver/orchestrator/workflows/aw-spec-onboard.orch.yaml +21 -22
  26. package/assets/.appweaver/orchestrator/workflows/aw-spec-plan.orch.yaml +453 -120
  27. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-lane.orch.yaml +2 -2
  28. package/assets/.appweaver/orchestrator/workflows/aw-spec-select-stack.orch.yaml +83 -6
  29. package/assets/.appweaver/orchestrator/workflows/aw-spec-specify.orch.yaml +45 -2
  30. package/assets/.appweaver/scripts/bash/aw-spec-actions.sh +324 -74
  31. package/assets/.appweaver/scripts/powershell/aw-spec-actions.ps1 +275 -44
  32. package/assets/.appweaver/scripts/python/aw-foundation.py +16 -13
  33. package/assets/.appweaver/scripts/python/aw-gaps.py +11 -3
  34. package/assets/.appweaver/scripts/python/aw-model-guard.py +295 -0
  35. package/assets/.appweaver/scripts/python/aw-plan-coverage.py +412 -3
  36. package/assets/.appweaver/scripts/python/aw-plangate.py +23 -16
  37. package/assets/.appweaver/scripts/python/aw-stack.py +1794 -265
  38. package/assets/.appweaver/stack-profiles/dotnet-webapi.json +0 -2
  39. package/assets/.appweaver/stack-profiles/java-spring.json +0 -2
  40. package/assets/.appweaver/templates/api-plan-template.md +107 -0
  41. package/assets/.appweaver/templates/delta-api-plan-template.md +87 -0
  42. package/assets/.appweaver/templates/delta-design-template.md +25 -23
  43. package/assets/.appweaver/templates/delta-spec-template.md +3 -3
  44. package/assets/.appweaver/templates/design-template.md +20 -42
  45. package/assets/.appweaver/templates/domain-model-template.md +3 -0
  46. package/assets/.appweaver/templates/ui-delta-design-template.md +11 -8
  47. package/assets/.appweaver/templates/ui-design-template.md +4 -4
  48. package/assets/foundations/foundation.spec.template.txt +25 -18
  49. package/assets/guardrails/README.md +61 -25
  50. package/assets/guardrails/methodology/ux/accessibility-standards.md +38 -0
  51. package/assets/guardrails/methodology/ux/content-design.md +34 -0
  52. package/assets/guardrails/methodology/ux/interaction-states.md +45 -0
  53. package/assets/guardrails/methodology/ux/journey-mapping.md +41 -0
  54. package/assets/guardrails/project-specific/domain/domain-requirements.md +25 -38
  55. package/assets/guardrails/project-specific/domain-rule-bindings.md +33 -69
  56. package/assets/guardrails/project-specific/frontend/ui-requirements.md +2 -2
  57. package/assets/guardrails/project-specific/service/technical-requirements.md +2 -2
  58. package/assets/guardrails/project-specific/tech-rule-bindings.md +16 -13
  59. package/assets/guardrails/sample-healthcare/domain/domain-requirements.md +58 -0
  60. package/assets/guardrails/sample-healthcare/domain-rule-bindings.md +138 -0
  61. package/assets/guardrails/sample-healthcare/frontend/ui-requirements.md +43 -0
  62. package/assets/guardrails/sample-healthcare/service/technical-requirements.md +40 -0
  63. package/assets/guardrails/sample-healthcare/tech-rule-bindings.md +303 -0
  64. package/dist/commands/init.d.ts.map +1 -1
  65. package/dist/commands/init.js +216 -7
  66. package/dist/commands/init.js.map +1 -1
  67. package/dist/utils/config.js +1 -1
  68. package/dist/utils/config.js.map +1 -1
  69. package/dist/utils/foundation.d.ts +36 -0
  70. package/dist/utils/foundation.d.ts.map +1 -0
  71. package/dist/utils/foundation.js +109 -0
  72. package/dist/utils/foundation.js.map +1 -0
  73. package/dist/utils/paths.js +1 -1
  74. package/dist/utils/paths.js.map +1 -1
  75. package/package.json +1 -1
@@ -21,6 +21,15 @@
21
21
  # (referred to directly, as-is — NOT mapped) plus the caller's minimal seed, so an
22
22
  # unconfigured / greenfield project is never blocked. This keeps greenfield-safety.
23
23
  #
24
+ # SOFT, NOT SILENT. Where a map exists but loads less than it says, the module WARNS
25
+ # and carries on: the map has no `## /{{command}}` heading (absent or misspelled — a
26
+ # heading that is present and deliberately empty does not warn), a mapped file is not on
27
+ # disk, or a bullet uses a placeholder the kit does not know. The engine returns each
28
+ # warning in the run result, so the operator sees it even though the run continues.
29
+ # A stack with NO rule folder under guardrails/technology/ is reported once, as that
30
+ # folder: it is a supported setup (STACK-PROFILES.md §7) that select-stack already
31
+ # reported, not a list of typos. Files missing from a folder that exists are listed.
32
+ #
24
33
  # Runs in the CALLER's context: shares vars + standards + the caller's `scripts:`
25
34
  # map + replay cache (include semantics). HITL steps suspend via the checkpoint
26
35
  # channel under every strategy; non-interactive default: selection → all.
@@ -52,11 +61,60 @@ workflow:
52
61
  outputs:
53
62
  - { name: tech_domain, from: "$.domain" }
54
63
  - { name: tg_techmap_exists, from: "$.techrulemap_exists" }
64
+ - { name: tg_heading_found, from: "$.heading_found" }
55
65
  - { name: tg_candidates, from: "$.candidates" }
66
+ - { name: tg_missing, from: "$.missing_elsewhere" }
67
+ - { name: tg_absent, from: "$.absent_corpora" }
68
+ - { name: tg_absent_text, from: "$.absent_corpora_text" }
69
+ - { name: tg_absent_files, from: "$.absent_files" }
70
+ - { name: tg_unknown, from: "$.unknown_placeholders" }
56
71
  - { name: tg_mismatch, from: "$.stack_mismatch" }
57
72
  - { name: tg_mismatched, from: "$.mismatched_stacks" }
58
73
  - { name: tg_declared_stacks, from: "$.declared_stacks" }
59
74
 
75
+ # 1b. WHAT THE MAP SAYS AND DOES NOT LOAD — warnings, never stops. Each fires only where
76
+ # the map exists; with no map at all there is nothing to misread. The engine has no
77
+ # "list is not empty" test, so each list sets a flag false when it has entries.
78
+ - id: tg_flags
79
+ action: set
80
+ vars: { tg_all_on_disk: "true", tg_placeholders_known: "true", tg_corpora_present: "true" }
81
+ - id: tg_flag_missing
82
+ condition: "{{tg_missing}} != []"
83
+ action: set
84
+ vars: { tg_all_on_disk: "false" }
85
+ - id: tg_flag_absent
86
+ condition: "{{tg_absent}} != []"
87
+ action: set
88
+ vars: { tg_corpora_present: "false" }
89
+ - id: tg_flag_unknown
90
+ condition: "{{tg_unknown}} != []"
91
+ action: set
92
+ vars: { tg_placeholders_known: "false" }
93
+ - id: warn_no_heading
94
+ condition: "{{tg_techmap_exists}} == true"
95
+ action: verify_context
96
+ require_true: tg_heading_found
97
+ on_false: continue
98
+ on_false_message: "WARNING — guardrails/{{tech_domain}}/tech-rule-bindings.md has no `## /{{command}}` heading, so this step loads no technical rules from the map. If that is not intended, add the heading and its rule files — it must read exactly `## /{{command}}`."
99
+ - id: warn_absent_corpus
100
+ condition: "{{tg_techmap_exists}} == true"
101
+ action: verify_context
102
+ require_true: tg_corpora_present
103
+ on_false: continue
104
+ on_false_message: "WARNING — no technical rule set for {{tg_absent_text}} (the folder is not there), so the {{tg_absent_files}} file(s) guardrails/{{tech_domain}}/tech-rule-bindings.md maps there under `## /{{command}}` were not loaded. A stack without a rule set is supported: this step works from ai-rules.md and the technical constitution. To give the lane stack rules, write that folder with the file names the map lists, or point the map at your own files."
105
+ - id: warn_missing
106
+ condition: "{{tg_techmap_exists}} == true"
107
+ action: verify_context
108
+ require_true: tg_all_on_disk
109
+ on_false: continue
110
+ on_false_message: "WARNING — guardrails/{{tech_domain}}/tech-rule-bindings.md maps these files under `## /{{command}}`, but they are not on disk, so they were not loaded: {{tg_missing}}. Correct the paths, or add the files."
111
+ - id: warn_unknown_placeholder
112
+ condition: "{{tg_techmap_exists}} == true"
113
+ action: verify_context
114
+ require_true: tg_placeholders_known
115
+ on_false: continue
116
+ on_false_message: "WARNING — these bullets under `## /{{command}}` in guardrails/{{tech_domain}}/tech-rule-bindings.md use a placeholder the kit does not know, so they were not loaded: {{tg_unknown}}. The known placeholders are backend_stack, frontend_stack, stack, backend_ecosystem and frontend_ecosystem, each in double braces."
117
+
60
118
  # 1a. STACK MISMATCH — the map points at a technical corpus for a stack this repo
61
119
  # does not declare. These files become STICKY STANDARDS: the generator obeys
62
120
  # them for the rest of the run, so a Spring repo mapped to the .NET corpus is
@@ -240,6 +240,11 @@ class Orchestrator:
240
240
  # paths an agent_generate reply named as written that were not on disk —
241
241
  # run-cumulative, reported at the end beside missing_required.
242
242
  self.reported_missing = []
243
+ # the messages of gates that failed and let the run CONTINUE — distinct, in
244
+ # order, returned in the run result. Under --json the run log is not printed,
245
+ # so this list is the only way a warning reaches the operator. A resumed run
246
+ # replays from the top, which rebuilds the list rather than doubling it.
247
+ self.warnings = []
243
248
  # agent_author targets a --dry-run left unwritten; required_files gates
244
249
  # count them as present, so a dry run reaches the steps after them.
245
250
  self.dry_run_unwritten = []
@@ -1049,23 +1054,29 @@ class Orchestrator:
1049
1054
  self.emit(f"[execute_script] -> {json.dumps(data)[:200]}")
1050
1055
 
1051
1056
  # -- F2: gates ---------------------------------------------------------------
1052
- def _gate_failed(self, p, outcome, msg):
1057
+ def _gate_failed(self, p, outcome, msg, explicit=True):
1053
1058
  """Apply a failed gate's outcome: `stop` ends the skill, `return` ends the module
1054
1059
  being included (see Return), anything else continues. `message_var` binds the
1055
1060
  resolved message first, so a caller that continues or returns can still report
1056
- why — a message that only reaches the run log is not seen by the operator."""
1061
+ why — a message that only reaches the run log is not seen by the operator.
1062
+ A gate that continues with a message of its own (`explicit`) and no
1063
+ `message_var` adds it to the run's `warnings`; a gate with `message_var`
1064
+ reports through the variable instead."""
1057
1065
  if p.get("message_var"):
1058
1066
  self.vars[p["message_var"]] = msg
1059
1067
  if outcome == "stop":
1060
1068
  raise Stop(msg)
1061
1069
  if outcome == "return":
1062
1070
  raise Return(msg)
1071
+ if explicit and not p.get("message_var") and msg not in self.warnings:
1072
+ self.warnings.append(msg)
1063
1073
 
1064
1074
  def act_verify_context(self, step):
1065
1075
  """F2 — the four decl gate forms: required_files (+allow_missing),
1066
1076
  require_non_empty (+on_empty), require_true (+on_false),
1067
1077
  require_value/equals (+on_mismatch). Messages interpolate {{vars}}.
1068
- Outcomes: stop | return | continue; `message_var` binds a failed gate's message."""
1078
+ Outcomes: stop | return | continue; `message_var` binds a failed gate's message,
1079
+ and a continued gate's own message is otherwise collected into `warnings`."""
1069
1080
  p = step.get("parameters") or step
1070
1081
  if p.get("require_non_empty"):
1071
1082
  var = p["require_non_empty"]
@@ -1073,7 +1084,7 @@ class Orchestrator:
1073
1084
  if not val:
1074
1085
  msg = self.resolve(p.get("on_empty_message", f"{var} is empty"))
1075
1086
  self.emit(f"[verify_context] {var} is empty: {msg}")
1076
- self._gate_failed(p, p.get("on_empty"), msg)
1087
+ self._gate_failed(p, p.get("on_empty"), msg, "on_empty_message" in p)
1077
1088
  else:
1078
1089
  self.emit(f"[verify_context] {var} non-empty")
1079
1090
  if p.get("require_true"):
@@ -1084,7 +1095,7 @@ class Orchestrator:
1084
1095
  if not truthy:
1085
1096
  msg = self.resolve(p.get("on_false_message", f"{var} is not satisfied"))
1086
1097
  self.emit(f"[verify_context] gate failed ({var}): {msg}")
1087
- self._gate_failed(p, p.get("on_false", "stop"), msg)
1098
+ self._gate_failed(p, p.get("on_false", "stop"), msg, "on_false_message" in p)
1088
1099
  else:
1089
1100
  self.emit(f"[verify_context] gate ok ({var})")
1090
1101
  if p.get("require_value"):
@@ -1094,7 +1105,7 @@ class Orchestrator:
1094
1105
  if actual != expected:
1095
1106
  msg = self.resolve(p.get("on_mismatch_message", f"{var} != {expected}"))
1096
1107
  self.emit(f"[verify_context] value gate failed ({var}={actual!r}): {msg}")
1097
- self._gate_failed(p, p.get("on_mismatch", "stop"), msg)
1108
+ self._gate_failed(p, p.get("on_mismatch", "stop"), msg, "on_mismatch_message" in p)
1098
1109
  else:
1099
1110
  self.emit(f"[verify_context] value gate ok ({var}=={expected})")
1100
1111
  for rf in p.get("required_files", []) or []:
@@ -1585,7 +1596,8 @@ class Orchestrator:
1585
1596
  self.emit(f"[return] workflow ended early: {str(r)[:200]}")
1586
1597
  return {"status": "ok", "outputs": self.resolve(self.wf.get("outputs", {}) or {}),
1587
1598
  "vars": self.vars, "missing_required": list(self.missing_required),
1588
- "reported_not_written": list(self.reported_missing)}
1599
+ "reported_not_written": list(self.reported_missing),
1600
+ "warnings": list(self.warnings)}
1589
1601
 
1590
1602
 
1591
1603
  # ─────────────────────────────────────────────────────────────────────────────
@@ -2086,7 +2098,8 @@ def main(argv=None):
2086
2098
  except Stop as e:
2087
2099
  result = {"status": "stopped", "reason": str(e), "vars": orch.vars,
2088
2100
  "missing_required": list(orch.missing_required),
2089
- "reported_not_written": list(orch.reported_missing)}
2101
+ "reported_not_written": list(orch.reported_missing),
2102
+ "warnings": list(orch.warnings)}
2090
2103
  _clear_checkpoint(opts)
2091
2104
 
2092
2105
  if opts.json:
@@ -2110,6 +2123,11 @@ def main(argv=None):
2110
2123
  "later step read them:")
2111
2124
  for m in result["reported_not_written"]:
2112
2125
  print(f" - {m}")
2126
+ if result.get("warnings"):
2127
+ print(f"\nWARNING: {len(result['warnings'])} warning(s) from gates that let "
2128
+ "the run continue:")
2129
+ for w in result["warnings"]:
2130
+ print(f" - {w}")
2113
2131
  if result.get("outputs"):
2114
2132
  print(json.dumps(result["outputs"], indent=2))
2115
2133
  return 0 if result["status"] == "ok" else 1
@@ -26,7 +26,11 @@
26
26
  • ONE MASTER PER LANE — a change is planned against
27
27
  specs/<Context>/<Feature>/backend-plan.md; it never opens a second plan
28
28
  document and never edits the master. Neither does finalize-delta: the
29
- master is re-derived from the merged spec and model by `plan main`.
29
+ master is re-derived from the merged spec, model and API plan by `plan main`.
30
+ • THE CHANGE'S API IS NOT PLANNED HERE — its endpoints and the requests it
31
+ answers are in delta-api-plan.md beside this file, from the kit's
32
+ delta-api-plan-template, the same in every project. This document plans
33
+ what realises them.
30
34
  • ADDED / MODIFIED / REMOVED — the complete classification. A shared element
31
35
  is MODIFIED in place, never duplicated into the change.
32
36
  • EVERY ROW SERVES A SPEC ID — the delta reads the delta-spec and the domain
@@ -53,13 +57,15 @@
53
57
 
54
58
  **Model delta**: `specs/[Context]/domain-model.[CR-ID].delta.md` <!-- "none" when behaviour-only -->
55
59
 
60
+ **API delta**: `changes/[CR-ID]/delta-api-plan.md` <!-- the endpoints this change adds, changes or removes, which this plan realises -->
61
+
56
62
  <!--
57
63
  This delta applies to the SINGLE master plan at
58
64
  specs/[Context]/[Feature]/backend-plan.md — never a new plan doc. It does not
59
65
  touch the master, and neither does finalize-delta: the master is DERIVED from
60
- spec.md and domain-model.md, the two documents finalize-delta merges, so
61
- `plan main` re-derives it rather than patching it from this file. close-delta
62
- archives this document as the record of how the change was built.
66
+ spec.md, domain-model.md and api-plan.md, the documents finalize-delta merges,
67
+ so `plan main` re-derives it rather than patching it from this file.
68
+ close-delta archives this document as the record of how the change was built.
63
69
 
64
70
  IT PLANS AGAINST THE EFFECTIVE MODEL — the master domain-model.md, this change's
65
71
  own model delta, and the PREDECESSOR deltas (the open ones in a LOWER wave,
@@ -124,21 +130,14 @@
124
130
  [Which of the master's sections ripple; shared elements touched; whether a data
125
131
  migration is needed (yes/no); any contract change other systems couple to.]
126
132
 
127
- ## Contract deltas for the frontend lane
133
+ ## API changes
128
134
 
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. -->
135
+ <!-- The endpoints this change adds, changes or removes — its Contract deltas table and
136
+ their blocks — and the frontend lane's requests it answers are in delta-api-plan.md,
137
+ beside this file, not here. Keep this section as the one line below; omit it where the
138
+ code publishes no endpoint. -->
138
139
 
139
- | Operation id | Contract | Change | Shape | Serves |
140
- |---|---|---|---|---|
141
- | `[OperationId]` | `[contract]` | [added / changed / removed] | [request → response field shapes] | IC-[nnn] |
140
+ The endpoints this change adds, changes or removes, and the requests it answers, are in [delta-api-plan.md](delta-api-plan.md).
142
141
 
143
142
  ## Other open deltas
144
143
 
@@ -164,8 +163,9 @@ changed.]
164
163
  ## On merge
165
164
 
166
165
  `finalize-delta` merges the change's SPEC half into `spec.md` and its MODEL half into the
167
- context's `domain-model.md`, in one transaction, and leaves the change OPEN. It does NOT
168
- merge this document: the master plan is derived from those two, so `plan main` re-derives
169
- it rather than patching it. `close-delta` archives this file with the change, bumps the
166
+ context's `domain-model.md`, in one transaction, merges its `delta-api-plan.md` into the
167
+ feature's `api-plan.md` by script, and leaves the change OPEN. It does NOT merge this
168
+ document: the master plan is derived from those three, so `plan main` re-derives it rather
169
+ than patching it. `close-delta` archives this file with the change, bumps the
170
170
  minor version and writes the Change History rows. None of it is this document's job — it
171
171
  records the change, it does not apply it.
@@ -30,13 +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
+ • The published endpoints are NOT in this template. Each feature keeps them
34
+ in its api-plan.md, from the kit's api-plan-template — one form for every
35
+ project, because the contract scripts read it (`contract-draft` drafts
36
+ api-draft.json from it, `contract-requests` reads its operation ids and
37
+ requests table, `contract-merge` merges a change into it). The §2 axis
38
+ that publishes them keeps the project's own name and its component table,
39
+ and ends with the one line pointing to api-plan.md.
40
40
  • The traceability spine (§4) and both ledgers (§5 DA-n, §6 WA-n) survive
41
41
  every layout — the analyze gates read them.
42
42
  • The §0–§6 numbering itself: `plan`'s bootstrap_bf branch names those
@@ -69,6 +69,8 @@
69
69
 
70
70
  **Source model**: `specs/[Context]/domain-model.md` <!-- where the context has one -->
71
71
 
72
+ **API plan**: `specs/[Context]/[Feature]/api-plan.md` <!-- the endpoints this service publishes, which §2 realises -->
73
+
72
74
  **Version**: v1.0
73
75
 
74
76
  **Status**: Draft
@@ -176,8 +178,9 @@
176
178
  the titles are the code's answer — the slots below are illustrative
177
179
  positions, not a required set. In each: the components that live there,
178
180
  their behaviour in signature form, and the contracts they expose or
179
- require. The endpoints the service PUBLISHES take the endpoint block form
180
- shown in the axis below. -->
181
+ require. The endpoints the service PUBLISHES are in the feature's
182
+ api-plan.md; the axis below that publishes them ends with the line
183
+ pointing there. -->
181
184
 
182
185
  ### 2.1 [PROJECT_STRUCTURAL_AXIS_NAME]
183
186
 
@@ -194,47 +197,18 @@
194
197
 
195
198
  ### 2.[M] [AXIS_THAT_PUBLISHES_THE_ENDPOINTS]
196
199
 
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. -->
200
+ <!-- The axis this project publishes its endpoints from keeps the project's own
201
+ name and its component table. The endpoints themselves — route, verb,
202
+ operation id, shapes, the error contract — and the answers to the frontend
203
+ lane's requests are in the feature's api-plan.md, in the kit's fixed form the
204
+ contract scripts read, and each maps to a component here. End the axis with
205
+ the line below; a project that publishes no endpoint omits it. -->
205
206
 
206
207
  | Component | Behaviour (signature) | Pre / Post / Raises | Serves |
207
208
  |---|---|---|---|
208
209
  | `[Component]` | `[operation](args) -> result` | [pre / post / raises] | FR-00X · US-Y |
209
210
 
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] |
211
+ The endpoints this service publishes — route, verb, operation id, request, success and errors — and its answers to the frontend lane's requests are in [api-plan.md](api-plan.md), the API contract the frontend lane binds to.
238
212
 
239
213
  ### 2.[N] External Integrations
240
214
 
@@ -302,9 +276,9 @@
302
276
  ## Change History
303
277
 
304
278
  <!-- A row is appended when this plan is RE-DERIVED to carry a change: the plan
305
- is derived from spec.md and the context's domain-model.md, and finalize-delta
306
- merges a change into those two, never into this one (`plan main` re-derives
307
- it). close-delta bumps the version and stamps it into the header, so a header
279
+ is derived from spec.md, the context's domain-model.md and api-plan.md, and
280
+ finalize-delta merges a change into those three, never into this one (`plan
281
+ main` re-derives it). close-delta bumps the version and stamps it into the header, so a header
308
282
  ahead of the newest row means the plan has not been re-derived since. A
309
283
  change is planned as a DELTA in the change's own directory, never as a new
310
284
  plan document. Newest last. -->
@@ -47,7 +47,7 @@
47
47
 
48
48
  **Delta spec**: `changes/[CR-ID]/delta-spec.md`
49
49
 
50
- **Service delta**: [`changes/[CR-ID]/delta-backend-plan.md`, or `None — experience-only change`]
50
+ **API delta**: [`changes/[CR-ID]/delta-api-plan.md`, or `None — the change leaves the API unchanged`]
51
51
 
52
52
  <!--
53
53
  This delta applies to the SINGLE master plan at
@@ -82,7 +82,7 @@ it serves.]
82
82
  "Section" names a section of THIS project's master frontend plan — the same
83
83
  section titles the generated frontend plan template carries. A binding row's
84
84
  element LEADS with the operation id the service names the endpoint by — implement
85
- checks it against the exported API document. -->
85
+ checks it against the API document combined from the API plans' drafts. -->
86
86
 
87
87
  | Section (§ in frontend-plan.md) | Element | Kind | Serves |
88
88
  |---|---|---|---|
@@ -115,8 +115,9 @@ it serves.]
115
115
  ## Contract impact
116
116
 
117
117
  [What this change needs from the service lane, bound against its published surface —
118
- the delta-spec's IC- ids and the service delta's "Contract deltas for the frontend
119
- lane" table: bindings added, changed or no longer used. Anything the experience
118
+ the delta-spec's IC- ids and the change's API delta (delta-api-plan.md) — its Contract
119
+ deltas table and endpoint blocks — and, for an endpoint the change leaves alone, the
120
+ feature's api-plan.md: bindings added, changed or no longer used. Anything the experience
120
121
  requires that the contract does not offer is raised back to the service lane —
121
122
  never worked around in the client. Or `None`.]
122
123
 
@@ -150,8 +151,9 @@ shared state container or a navigation edge changed.]
150
151
  ## On merge
151
152
 
152
153
  `finalize-delta` merges the change's SPEC half into `spec.md` and its MODEL half into the
153
- context's `domain-model.md`, in one transaction, and leaves the change OPEN. It does NOT
154
- merge this document: the master plan is derived from those two, so `plan main` re-derives
155
- it rather than patching it. `close-delta` archives this file with the change and bumps the
154
+ context's `domain-model.md`, in one transaction, merges its `delta-api-plan.md` into the
155
+ feature's `api-plan.md` by script, and leaves the change OPEN. It does NOT merge this
156
+ document: the master plan is derived from those three, so `plan main` re-derives it rather
157
+ than patching it. `close-delta` archives this file with the change and bumps the
156
158
  ONE version both lanes share. None of it is this document's job — it records the change,
157
159
  it does not apply it.
@@ -40,7 +40,7 @@
40
40
 
41
41
  §5 IS THE LANE BOUNDARY. Contract binding is the only place the service lane
42
42
  enters: the frontend plan reads the WHOLE spec but only the service's
43
- PUBLISHED SURFACE — the spec's IC- ids and the API contracts backend-plan.md
43
+ PUBLISHED SURFACE — the spec's IC- ids and the endpoints api-plan.md
44
44
  states. (⌛ `plan` will emit that surface as a design-contract.md projection;
45
45
  the rule does not change when it does, only what §5 reads.) The requirement
46
46
  is shared and must not fork; the implementation is not shared and must not
@@ -92,7 +92,7 @@
92
92
  the aggregate's structure. This lane consumes the published surface at §5, not
93
93
  the domain, so the model is not in its context.)
94
94
  - THE PUBLISHED SURFACE IS THE ONLY SERVICE INPUT (§5) — the spec's IC- ids and
95
- the API contracts backend-plan.md states, never the service's internals.
95
+ the endpoints api-plan.md states, never the service's internals.
96
96
 
97
97
  STRUCTURE FOLLOWS THIS LANE'S CODE
98
98
  - [GENERATED_LANE_STRUCTURE_NOTE] — form: one or two sentences naming the
@@ -219,11 +219,11 @@ does none.]
219
219
 
220
220
  <!-- THE LANE BOUNDARY. One row per service operation this feature's UI depends
221
221
  on, bound against the service's PUBLISHED SURFACE — the spec's IC- ids and
222
- the API contracts backend-plan.md states — never its internals: no table,
222
+ the endpoints api-plan.md states — never its internals: no table,
223
223
  handler or persistence assumption crosses this line. Where the surface does
224
224
  not name an operation yet, bind against the spec's own IC-/FR- surface and
225
225
  record that as a §8 assumption. The OPERATION ID is the endpoint's name in the
226
- service's published API document (as backend-plan.md records it) — implement
226
+ service's published API document (as api-plan.md records it) — implement
227
227
  finds the endpoint by it; `—` where none is named yet. -->
228
228
 
229
229
  | Screen / component | Operation id | Operation (from the contract) | Sends (field shape) | Receives (field shape) | Failure handling | Serves |
@@ -234,7 +234,7 @@ does none.]
234
234
 
235
235
  <!-- THE REQUEST LEDGER. One row per operation the experience needs that the
236
236
  contract does not offer — raised back to the service lane, never worked
237
- around in the client. Each request keeps its RQ-n for life. The backend plan
237
+ around in the client. Each request keeps its RQ-n for life. The API plan
238
238
  answers in its "Requests from the frontend lane" table, published under an
239
239
  operation id or declined with the reason; an answered request is re-bound
240
240
  above to that id. Write `None` instead of the table when there is none. -->