aw-spec-cli 0.1.15 → 0.1.16

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.
@@ -0,0 +1,87 @@
1
+ # =============================================================================
2
+ # spec-state-gate (orchestrator) — the ledger tripwire, gated on STATE
3
+ #
4
+ # Included by every phase that READS spec.md as an oracle and does not stamp it.
5
+ # It answers one question before that phase starts doing work:
6
+ #
7
+ # is spec.md's version an honest name for spec.md's contents?
8
+ #
9
+ # WHY THE `ok` BOOLEAN IS NOT ENOUGH. spec-check's decisive branch asks only "is
10
+ # there a version ROW above the version the kit last recorded", and never opens the
11
+ # body. Two of its states are therefore ok:true with a body nobody has re-baselined:
12
+ #
13
+ # ledgered_edit a row exists above the recorded version — the edit was RECORDED,
14
+ # but the kit has not re-hashed, so every LATER edit lands inside
15
+ # the same window and passes the same test. Verified: a requirement
16
+ # DELETED with no row naming it passes.
17
+ # header_behind the same, with the document's header not yet caught up.
18
+ #
19
+ # Both are legitimate transient states and both have the same remedy — spec-stamp,
20
+ # which re-baselines the hash and closes the window. That remedy is already named in
21
+ # these phases' own refusal messages and in SPEC-EDIT-PROTOCOL.md.
22
+ #
23
+ # WHY NOT FLIP `ok` INSTEAD. Because specify is the stamper. It MUST keep accepting
24
+ # both states — it is the command that resolves them — so making spec-check refuse
25
+ # them would wedge the one workflow that can fix them. The distinction belongs at the
26
+ # consumer, which is what this module is.
27
+ #
28
+ # WHAT IT DOES NOT DO. It never stamps, never edits and never repairs. Every state
29
+ # spec-check already refuses (unrowed_edit, unledgered_edit, rollback, no_ledger,
30
+ # delta_open) still stops on `ok`, with spec-check's own message.
31
+ #
32
+ # Inputs (via include `with`):
33
+ # command — the phase being gated, quoted back in the failure message
34
+ #
35
+ # Produces: spec_ok / spec_state (the raw answers) and spec_current (true when the
36
+ # state is one this phase may read from), for a caller that wants to say more.
37
+ # =============================================================================
38
+
39
+ name: spec-state-gate
40
+ dialect: aw-spec.orchestrator/v1
41
+
42
+ scripts:
43
+ actions:
44
+ sh: .appweaver/scripts/bash/aw-spec-actions.sh
45
+ ps1: .appweaver/scripts/powershell/aw-spec-actions.ps1
46
+
47
+ workflow:
48
+
49
+ - id: spec_guard_read
50
+ action: execute_script
51
+ script: { ref: actions }
52
+ subcommand: spec-check
53
+ args: ["--feature-dir", "{{feature_dir}}"]
54
+ outputs:
55
+ - { name: spec_ok, from: "$.ok" }
56
+ - { name: spec_state, from: "$.state" }
57
+
58
+ # The states spec-check itself refuses. Kept first and kept separate: its own error
59
+ # text says which one and why, and this module has nothing to add to it.
60
+ - id: spec_guard
61
+ action: verify_context
62
+ require_true: spec_ok
63
+ on_false: stop
64
+ on_false_message: "{{command}} cannot run: spec.md has moved outside the ledger (state: {{spec_state}}), so its version no longer names its contents and nothing read from it can be trusted. Record what changed in a Change History row naming the ids, then re-run spec-stamp to re-baseline — or, when the edit touched no requirement at all, spec-stamp alone (see .appweaver/SPEC-EDIT-PROTOCOL.md)."
65
+
66
+ # The two states that pass `ok` and still leave the body un-baselined. Ordered sets
67
+ # rather than a compound condition — the evaluator takes single comparisons only, so
68
+ # the refusal is the default and each acceptable state lifts it.
69
+ - id: spec_state_default
70
+ action: set
71
+ vars: { spec_current: "false" }
72
+ - id: spec_state_clean
73
+ condition: "{{spec_state}} == clean"
74
+ action: set
75
+ vars: { spec_current: "true" }
76
+ # A pre-ledger spec the kit has never stamped. Legitimate: nothing has been recorded
77
+ # against it, so there is no window between a row and a baseline to fall into.
78
+ - id: spec_state_unstamped
79
+ condition: "{{spec_state}} == unstamped"
80
+ action: set
81
+ vars: { spec_current: "true" }
82
+
83
+ - id: spec_state_gate
84
+ action: verify_context
85
+ require_true: spec_current
86
+ on_false: stop
87
+ on_false_message: "{{command}} cannot run: spec.md is in state '{{spec_state}}' — the edit is recorded in a Change History row, but the kit has not re-baselined the document, so its recorded hash still describes the text before the edit. Until it does, every FURTHER edit passes the same tripwire unnoticed, including a requirement deleted with no row naming it. Run /appweaver specify (which stamps), or re-run spec-stamp to re-baseline directly, then re-run {{command}}."
@@ -51,7 +51,7 @@
51
51
  # • the plan lags what the ledger moved → /appweaver plan · /appweaver plan-ui
52
52
  # • the plan is wrong and the spec is right → repair here, or hand-edit the plan and
53
53
  # re-run plan with accept_manual: true
54
- # • the SPEC is wrong → /appweaver sync-spec (never repaired here)
54
+ # • the SPEC is wrong → /appweaver sync-spec (⌛ not yet built — until it is, fold the change back by hand under .appweaver/SPEC-EDIT-PROTOCOL.md) (never repaired here)
55
55
  # • a gate-overlay constraint was never lifted into the spec (LIFT-FAILURE:)
56
56
  # → /appweaver specify
57
57
  #
@@ -87,6 +87,10 @@ vars:
87
87
  fe_state: ""
88
88
  fe_derived: ""
89
89
  fe_changed: ""
90
+ # The plan shapes this run certifies, re-read after any repair (step 8b). Empty means the
91
+ # lane was not in scope, which every reader treats as "not certified", never as a mismatch.
92
+ be_final_sha: ""
93
+ fe_final_sha: ""
90
94
  current_version: ""
91
95
  # Freshness gates default FALSE so a probe that could not run fails CLOSED. The
92
96
  # message vars carry a generic default for the same reason — an empty stop message
@@ -108,6 +112,9 @@ vars:
108
112
  found_count: "0"
109
113
  found_refs: ""
110
114
  repaired: "false"
115
+ # The ids a repair pass actually rewrote — the model's own declaration, and NOT the same
116
+ # set as gap_refs (which holds every finding, including the ones left deliberately open).
117
+ repaired_refs: ""
111
118
  be_stamp_ok: "true" # vacuously true until a repair actually stamps
112
119
  fe_stamp_ok: "true"
113
120
  be_stamp_error: ""
@@ -198,19 +205,10 @@ workflow:
198
205
  # gate compares each plan's derived version against the spec's ledger, so a spec
199
206
  # that moved OUTSIDE the ledger makes every one of those comparisons a lie: the
200
207
  # plans would be certified current against a version the spec no longer is.
201
- - id: spec_guard_read
202
- action: execute_script
203
- script: { ref: actions }
204
- subcommand: spec-check
205
- args: ["--feature-dir", "{{feature_dir}}"]
206
- outputs:
207
- - { name: spec_ok, from: "$.ok" }
208
- - { name: spec_state, from: "$.state" }
209
208
  - id: spec_guard
210
- action: verify_context
211
- require_true: spec_ok
212
- on_false: stop
213
- on_false_message: "spec.md has moved outside the ledger (state: {{spec_state}}), so its version no longer names its contents and no plan can be certified against it. Record what changed in a Change History row (or fold it back through /appweaver sync-spec), re-run spec-stamp to re-baseline, then re-run analyze-design."
209
+ action: include
210
+ module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
211
+ with: { command: "/appweaver analyze-design" }
214
212
 
215
213
  # 2c. LEGACY GUARD. Nothing in the current model opens a change — specify writes the
216
214
  # spec directly and the delta family is retired (deletion scheduled). has_change
@@ -404,8 +402,8 @@ workflow:
404
402
  - id: analyze
405
403
  action: agent_query
406
404
  include_standards: true # the domain gate overlays (the checklist) ride in the standards
407
- schema: { ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", lane: "backend|frontend|cross", spec_ref: "<FR/BR/CN/DC/IC/UX/SC id, or the overlay name, or —>", where: "<document + section>", gap: "<how it drifts>", route: "<the command that fixes it>" } ] }
408
- mock: { ok: true, gap_count: 0, gap_refs: "", backend_repair: false, frontend_repair: false, gaps: [] }
405
+ schema: { ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", repaired_refs: "<comma-separated spec ids THIS repair actually rewrote — empty on the first pass>", backend_repair: false, frontend_repair: false, gaps: [ { severity: "high", lane: "backend|frontend|cross", spec_ref: "<FR/BR/CN/DC/IC/UX/SC id, or the overlay name, or —>", where: "<document + section>", gap: "<how it drifts>", route: "<the command that fixes it>" } ] }
406
+ mock: { ok: true, gap_count: 0, gap_refs: "", repaired_refs: "", backend_repair: false, frontend_repair: false, gaps: [] }
409
407
  task: |
410
408
  spec.md is the ANCHOR. Judge the feature's PLAN DOCUMENTS against it and list
411
409
  EVERY drift as a gap. In scope this run: backend-plan.md = {{be_in_scope}},
@@ -487,6 +485,15 @@ workflow:
487
485
  another command. Set gap_count to the number of gaps and gap_refs to their
488
486
  spec ids, comma-separated. Set ok=true ONLY when every plan in scope fully
489
487
  conforms to the spec AND no checklist constraint is unaccounted for.
488
+
489
+ `repaired_refs` IS A DIFFERENT SET FROM `gap_refs`, and it goes into the plans'
490
+ own Change History. Set it to the spec ids THIS PASS ACTUALLY REWROTE a plan for
491
+ — empty on a pass that repaired nothing, which includes the first pass of every
492
+ run. Never copy gap_refs into it: that set holds every finding, including the
493
+ gaps you were told to leave OPEN (route sync-spec or specify), a LIFT-FAILURE
494
+ whose spec_ref is an overlay name rather than an id, and any "—". Recording one
495
+ of those claims the plan was conformed for a requirement nobody touched, in the
496
+ ledger story-plan and the next /appweaver plan read.
490
497
  outputs:
491
498
  - { name: analyze_ok, from: "$.ok" }
492
499
  - { name: analyze_gaps, from: "$.gaps" }
@@ -494,6 +501,7 @@ workflow:
494
501
  - { name: gap_refs, from: "$.gap_refs" }
495
502
  - { name: be_needs_repair, from: "$.backend_repair" }
496
503
  - { name: fe_needs_repair, from: "$.frontend_repair" }
504
+ - { name: repaired_refs, from: "$.repaired_refs" }
497
505
 
498
506
  # The report's subject is the state the gate FOUND, not what survived repair —
499
507
  # a verdict that only ever describes the repaired document cannot show that a
@@ -521,7 +529,13 @@ workflow:
521
529
  Change History. Choose Report and Skip whenever the fix belongs elsewhere —
522
530
  a wrong SPEC goes through /appweaver sync-spec, a LIFT-FAILURE through
523
531
  /appweaver specify.
524
- options: ["Repair", "Report and Skip"]
532
+ # REPORT FIRST, BECAUSE THE FIRST OPTION IS THE UNATTENDED ANSWER. An `ask` with
533
+ # nobody at the terminal resolves to option one, so an unattended run of this gate
534
+ # takes whichever branch is written here. Repairing means REWRITING both feature
535
+ # plans and re-stamping them, and a CI job that rewrites the documents it was asked
536
+ # to check reports on a state it created. Reporting is the safe default: it writes
537
+ # the report, leaves the plans untouched, and the person who reads it chooses.
538
+ options: ["Report and Skip", "Repair"]
525
539
  answer_var: repair_choice
526
540
 
527
541
  - id: skip_break
@@ -598,7 +612,16 @@ workflow:
598
612
  action: execute_script
599
613
  script: { ref: actions }
600
614
  subcommand: plan-stamp
601
- args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{gap_refs}}", "--note", "Conformance repair (analyze-design)"]
615
+ # --ids is WHAT THIS REPAIR WROTE, not every gap the pass found. gap_refs is the
616
+ # whole finding set — including gaps the repair prompt is told to leave OPEN (route
617
+ # sync-spec / specify), a LIFT-FAILURE whose spec_ref is an overlay name rather than
618
+ # an id, and the literal "—" the schema permits. Stamping those recorded, in the
619
+ # plan's own Change History, that the repair conformed ids it never touched — and
620
+ # that ledger is what story-plan and the next /appweaver plan read.
621
+ # --note carries the plan's tracking STATE, because a repair over an `edited`
622
+ # document re-baselines a hand edit that /appweaver plan would have refused without
623
+ # accept_manual: true. Admitting the edit is right; adopting it silently is not.
624
+ args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{repaired_refs}}", "--note", "Conformance repair (analyze-design) over a plan in state '{{be_state}}'"]
602
625
  outputs:
603
626
  - { name: be_stamp_ok, from: "$.ok" }
604
627
  - { name: be_stamp_error, from: "$.error" }
@@ -640,7 +663,9 @@ workflow:
640
663
  action: execute_script
641
664
  script: { ref: actions }
642
665
  subcommand: plan-stamp
643
- args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{gap_refs}}", "--note", "Conformance repair (analyze-design)"]
666
+ # Same two rules as the service lane: the ids this repair actually wrote, and the
667
+ # tracking state the repair ran over. See stamp_be.
668
+ args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}", "--ids", "{{repaired_refs}}", "--note", "Conformance repair (analyze-design) over a plan in state '{{fe_state}}'"]
644
669
  outputs:
645
670
  - { name: fe_stamp_ok, from: "$.ok" }
646
671
  - { name: fe_stamp_error, from: "$.error" }
@@ -680,6 +705,12 @@ workflow:
680
705
  - "# Plan Analyze Report — <aggregate>", then a line naming the spec version and
681
706
  each gated plan with its tracking state.
682
707
  - A verdict line: "verdict: CONFORMS ✓" or "verdict: GAPS ✗ · <n> open".
708
+ WHERE A REPAIR RAN OVER A HAND-EDITED PLAN, say so on that line — backend-plan.md
709
+ tracking state was "{{be_state}}" and frontend-plan.md "{{fe_state}}", and an
710
+ `edited` or `edited_behind` there means the repair re-baselined a hand edit that
711
+ /appweaver plan would have refused without accept_manual: true. Name the document,
712
+ the state, and that the edit is now adopted into the plan's baseline — whoever made
713
+ it should confirm the repair kept its intent.
683
714
  When repair ran, say so and give both numbers — found and still open.
684
715
  - "## Coverage (spec → plan)": a table | spec id | type | lane(s) it needs |
685
716
  design home | status |, one row per spec id you can see in the spec, marking
@@ -694,6 +725,31 @@ workflow:
694
725
  Do not invent ids, versions or dates beyond the values given above.
695
726
  Return the complete report markdown only.
696
727
 
728
+ # 8b. THE SHAPE BEING CERTIFIED, read at the last possible moment. Taken here rather than
729
+ # from the freshness probes at the top, because the repair loop may have rewritten and
730
+ # re-stamped both plans in between — recording the pre-repair hash would certify a
731
+ # document that no longer exists and fail every downstream comparison on a green run.
732
+ # Per lane and conditional: a lane out of scope has no hash and gets an empty field,
733
+ # which the readers treat as "this lane was not certified" rather than as a mismatch.
734
+ - id: be_final_hash
735
+ condition: "{{be_in_scope}} == true"
736
+ action: execute_script
737
+ on_error: continue
738
+ script: { ref: actions }
739
+ subcommand: plan-check
740
+ args: ["--doc", "{{be_plan}}", "--feature-dir", "{{feature_dir}}"]
741
+ outputs:
742
+ - { name: be_final_sha, from: "$.body_sha256" }
743
+ - id: fe_final_hash
744
+ condition: "{{fe_in_scope}} == true"
745
+ action: execute_script
746
+ on_error: continue
747
+ script: { ref: actions }
748
+ subcommand: plan-check
749
+ args: ["--doc", "{{fe_plan}}", "--feature-dir", "{{feature_dir}}"]
750
+ outputs:
751
+ - { name: fe_final_sha, from: "$.body_sha256" }
752
+
697
753
  # 9. The machine-readable verdict, written beside the report by the SCRIPT — CI
698
754
  # cannot branch on prose, and a verdict is exactly the kind of dated, derived
699
755
  # record the model never writes. `ok` from this step means the file was written;
@@ -703,7 +759,15 @@ workflow:
703
759
  script: { ref: actions }
704
760
  subcommand: gate-result
705
761
  # The lenient loader needs a flow sequence on ONE line.
706
- args: ["--gate", "analyze-design", "--feature-dir", "{{feature_dir}}", "--ok", "{{analyze_ok}}", "--spec-version", "{{current_version}}", "--report", "{{report_path}}", "--gap-count", "{{found_count}}", "--gap-refs", "{{found_refs}}", "--field", "gaps_open={{gap_count}}", "--field", "repaired={{repaired}}", "--field", "backend_plan={{be_plan}}", "--field", "backend_in_scope={{be_in_scope}}", "--field", "backend_state={{be_state}}", "--field", "backend_derived={{be_derived}}", "--field", "frontend_plan={{fe_plan}}", "--field", "frontend_in_scope={{fe_in_scope}}", "--field", "frontend_state={{fe_state}}", "--field", "frontend_derived={{fe_derived}}"]
762
+ # backend_sha256 / frontend_sha256 are WHAT THIS RUN CERTIFIED — the shape each plan was
763
+ # in when it was judged. Nothing else in the verdict can say it: backend_state and
764
+ # backend_derived do not move when a plan is hand-edited, so a plan approved here and
765
+ # edited afterwards was indistinguishable from one still in the approved shape. story-plan
766
+ # and both implements compare against these, which is what makes law 40 (approved,
767
+ # changed, never re-approved must not reach code) enforceable rather than stated.
768
+ # RE-READ AFTER ANY REPAIR: the loop above may have rewritten and re-stamped the plans, so
769
+ # the hash recorded has to be the one on disk NOW, not the one the freshness probe saw.
770
+ args: ["--gate", "analyze-design", "--feature-dir", "{{feature_dir}}", "--ok", "{{analyze_ok}}", "--spec-version", "{{current_version}}", "--report", "{{report_path}}", "--gap-count", "{{found_count}}", "--gap-refs", "{{found_refs}}", "--field", "gaps_open={{gap_count}}", "--field", "repaired={{repaired}}", "--field", "backend_plan={{be_plan}}", "--field", "backend_in_scope={{be_in_scope}}", "--field", "backend_state={{be_state}}", "--field", "backend_derived={{be_derived}}", "--field", "backend_sha256={{be_final_sha}}", "--field", "frontend_plan={{fe_plan}}", "--field", "frontend_in_scope={{fe_in_scope}}", "--field", "frontend_state={{fe_state}}", "--field", "frontend_derived={{fe_derived}}", "--field", "frontend_sha256={{fe_final_sha}}"]
707
771
  outputs:
708
772
  - { name: result_file, from: "$.result_file" }
709
773
 
@@ -234,6 +234,17 @@ workflow:
234
234
  on_false: stop
235
235
  on_false_message: "No spec.md for the current feature. Run /appweaver specify first — the spec is this gate's oracle, and code cannot be conformed to a contract that does not exist."
236
236
 
237
+ # THE ORACLE HAS TO BE HONEST ABOUT ITS OWN VERSION. This is the only gate whose
238
+ # declared oracle IS spec.md — every judgement below resolves an id's meaning from it
239
+ # — and it stamps {{current_version}} into the machine-readable verdict a pipeline
240
+ # branches on. A spec whose version no longer names its contents makes that stamp a
241
+ # false claim about what was certified, so the fence every other consumer carries
242
+ # belongs here most of all.
243
+ - id: spec_state_guard
244
+ action: include
245
+ module: .appweaver/orchestrator/modules/spec-state-gate.orch.yaml
246
+ with: { command: "/appweaver analyze-slice" }
247
+
237
248
  # 1. WHICH STORY, if any. An id in the input scopes the gate to that story; anything
238
249
  # else widens it to the feature. Deliberately NO ask: this gate has to be runnable
239
250
  # unattended, and a gate that stops to ask cannot run in CI. Naming a story narrows
@@ -302,7 +313,7 @@ workflow:
302
313
  condition: "{{story_resolved}} == false"
303
314
  action: set
304
315
  vars:
305
- scope_brief: "SCOPE — the WHOLE FEATURE, because no story was named. In scope: every FR- and BR- you can see in spec.md EXCEPT these, which have OPEN stories and are work in flight rather than drift: {{inflight_csv}}. EXTENT — the whole of each in-scope id. Also in scope are the CN-/DC-/IC-/UX- the spec binds this feature by."
316
+ scope_brief: "SCOPE — the WHOLE FEATURE, because no story was named. In scope: every FR- and BR- you can see in spec.md EXCEPT these, which have OPEN stories and are work in flight rather than drift: {{inflight_csv}}. EXTENT — the whole of each in-scope id. Also in scope are the CN-/DC-/IC-/UX- the spec binds this feature by, and the SC- Success Criteria — this phase is the one the spec names as their verifier, and a feature-scoped run is the only scope at which a feature-level outcome can be judged. SC- is scored differently from every other class; see the SUCCESS CRITERIA rules below."
306
317
  - id: bridge_story
307
318
  condition: "{{story_be_exists}} == true"
308
319
  action: set
@@ -386,6 +397,14 @@ workflow:
386
397
  args: ["--feature-dir", "{{feature_dir}}", "--ids", "{{exercises_csv}}"]
387
398
  outputs:
388
399
  - { name: all_paths, from: "$.all_paths" }
400
+ # THE SAME FILES AS A CSV, for the steps that name them in a prompt instead of
401
+ # inlining them. `context_from: all_paths` APPENDS every one of those paths to the
402
+ # envelope, and the engine reads the envelope IN FULL into the user block — so this
403
+ # gate inlined the feature's whole codebase four times over, and again on every fix
404
+ # iteration. Every consumer of that list can open a file itself; what it needed was
405
+ # to be told which files, not to be handed their contents.
406
+ - { name: all_csv, from: "$.all_csv" }
407
+ - { name: all_count, from: "$.all_count" }
389
408
  - { name: paths_missing, from: "$.all_missing_csv" }
390
409
  - { name: coverage_digest, from: "$.digest" }
391
410
  - { name: be_count, from: "$.backend_count" }
@@ -443,18 +462,23 @@ workflow:
443
462
  vars:
444
463
  test_log: "{{test_log}}"
445
464
  no_test_edit_rule: "{{no_test_edit_rule}}"
465
+ all_csv: "{{all_csv}}"
466
+ all_count: "{{all_count}}"
446
467
  context:
447
468
  - { path: "{{spec_file}}" }
448
- context_from: all_paths
449
469
  - id: fix
450
470
  action: agent_generate
451
471
  tools: "Read,Write,Edit,Bash"
452
472
  permission_mode: acceptEdits
453
473
  task: |
454
474
  Tests failed. Make them pass by fixing the code (or a genuinely wrong
455
- test) in the files in context, keeping behaviour aligned with the spec's
456
- acceptance criteria (spec in context). {{no_test_edit_rule}} Test log:
475
+ test), keeping behaviour aligned with the spec's acceptance criteria (spec
476
+ in context). {{no_test_edit_rule}} Test log:
457
477
  {{test_log}}
478
+
479
+ This slice's code, both lanes ({{all_count}} file(s)) — READ the ones the
480
+ failures point at; they are named, not inlined:
481
+ {{all_csv}}
458
482
  outputs:
459
483
  - { name: fixed_files, from: "$.files" }
460
484
 
@@ -549,8 +573,10 @@ workflow:
549
573
  to a FUNCTIONAL story citing the requirement it moves. Two ways out, and they are not
550
574
  interchangeable: restore the tests and make the refactor behaviour-preserving, or
551
575
  accept that this work changes behaviour — close this story as cancelled, raise a
552
- functional story for the change through /appweaver sync-spec (if the contract moves)
553
- or /appweaver add-story, and build it there where an FR- can be traced to it.
576
+ functional story for the change through /appweaver sync-spec (⌛ not yet built — until it is, fold the change back by hand under .appweaver/SPEC-EDIT-PROTOCOL.md) (if the contract moves)
577
+ or by raising a story by hand from .appweaver/templates/story-template.md
578
+ (/appweaver add-story, which mints the id and stamps the frontmatter, is ⌛ not yet
579
+ built), and build it there where an FR- can be traced to it.
554
580
 
555
581
  # ═══════ PASS 3 — DOMAIN CONFORMANCE: code vs the gate overlay checklist ═══════
556
582
  # With tests green, scan the feature's CODE against the domain gate overlays (the
@@ -578,18 +604,26 @@ workflow:
578
604
  action: build_context
579
605
  vars:
580
606
  no_test_edit_rule: "{{no_test_edit_rule}}"
607
+ all_csv: "{{all_csv}}"
608
+ all_count: "{{all_count}}"
581
609
  context:
582
610
  - { path: "{{spec_file}}" }
583
- context_from: all_paths
584
611
  - id: check_overlays
585
612
  action: agent_query
613
+ # A query step is PAYLOAD-ONLY unless it declares tools. It read the code because
614
+ # context_from inlined every file; naming them instead without granting Read would
615
+ # leave it reasoning about a codebase it cannot open.
616
+ tools: Read
586
617
  include_standards: true # the gate overlays (checklist) are in the standards
587
618
  schema: { ok: true, violations: [ { rule: "<overlay rule / id>", where: "<file:symbol>", issue: "<what is violated>" } ] }
588
619
  mock: { ok: true, violations: [] }
589
620
  task: |
590
621
  The domain gate checklist is loaded in the standards (the overlay
591
- forbidden-patterns, refusal conditions, and CI gates). Check the feature's
592
- generated code + tests (in context) against EACH checklist rule and list
622
+ forbidden-patterns, refusal conditions, and CI gates). This slice's code and
623
+ tests, both lanes ({{all_count}} file(s)) — READ them; they are named, not
624
+ inlined:
625
+ {{all_csv}}
626
+ Check them against EACH checklist rule and list
593
627
  EVERY violation, e.g.: a hard delete of a retention-protected aggregate /
594
628
  version history / audit record; a second write path bypassing the aggregate
595
629
  or interop adapter; an invented/free-text coded value where a validated code
@@ -666,12 +700,16 @@ workflow:
666
700
  referrer_names: "{{referrer_names}}"
667
701
  referrer_count: "{{referrer_count}}"
668
702
  integrity_log: "{{integrity_log}}"
703
+ all_csv: "{{all_csv}}"
704
+ all_count: "{{all_count}}"
669
705
  context:
670
706
  - { path: "{{spec_file}}" }
671
- context_from: all_paths
672
707
  - id: write_broken_referrers
673
708
  action: agent_author
674
709
  condition: "{{integrity_ok}} != true"
710
+ # An author step is PAYLOAD-ONLY unless it declares tools. The triage report has to
711
+ # name what this slice CHANGED that the referrers depend on, which means reading it.
712
+ tools: Read
675
713
  include_standards: false # a triage report, not generated code
676
714
  output_target: { type: file, path: "{{feature_dir}}/broken-referrers.md" }
677
715
  task: |
@@ -681,6 +719,9 @@ workflow:
681
719
  referrer (that is the referrer's own slice's job). Referrer blast radius
682
720
  ({{referrer_count}}): {{referrer_names}}. Failing test log:
683
721
  {{integrity_log}}
722
+ This slice's code, both lanes ({{all_count}} file(s)) — READ what it changed on the
723
+ published surface to say WHY the referrers broke; the files are named, not inlined:
724
+ {{all_csv}}
684
725
  Write markdown, in this order:
685
726
  - `# Broken Referrers — <feature>` and a one-line statement of what this file is:
686
727
  aggregates that USE this feature and whose tests went red when this feature's
@@ -730,6 +771,8 @@ workflow:
730
771
  bridge_note: "{{bridge_note}}"
731
772
  coverage_digest: "{{coverage_digest}}"
732
773
  stale_note: "{{stale_note}}"
774
+ all_csv: "{{all_csv}}"
775
+ all_count: "{{all_count}}"
733
776
  context:
734
777
  - { path: "{{spec_file}}" } # THE ORACLE — the contract's own wording
735
778
  - { path: "{{story_path}}", optional: true } # the extent: what this slice claims of each id
@@ -739,12 +782,11 @@ workflow:
739
782
  - { path: "{{fe_plan}}", optional: true }
740
783
  - { path: "{{feature_dir}}/interfaces.md", optional: true } # the published surface, cited by IF- id
741
784
  - { path: "{{feature_dir}}/adr.md", optional: true } # ratified decisions
742
- context_from: all_paths # the CODE — both lanes, from the OKF concept
743
785
  - id: trace
744
786
  action: agent_query
745
787
  tools: Read # follow a call out of the listed files when a trace needs it
746
788
  include_standards: false # the contract is the spec, not a rule corpus
747
- schema: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", coverage: "<one line per in-scope id — id | what the spec requires | the design element that maps it | the code that realises it | DELIVERED or GAP>", gaps: [ { severity: "high", spec_ref: "<FR/BR/CN/DC/IC/UX id>", kind: "code-missing|code-wrong|bridge-missing|spec-wrong", where: "<file:symbol, or the document>", gap: "<what the spec requires and what is actually there>", route: "<the command that fixes it>" } ], bridge_gaps: "<one line per in-scope id no design maps, or empty>" }
789
+ schema: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "<comma-separated spec ids the gaps touch>", coverage: "<one line per in-scope id — id | what the spec requires | the design element that maps it | the code that realises it | DELIVERED or GAP>", gaps: [ { severity: "high", spec_ref: "<FR/BR/CN/DC/IC/UX/SC id>", kind: "code-missing|code-wrong|bridge-missing|spec-wrong", where: "<file:symbol, or the document>", gap: "<what the spec requires and what is actually there>", route: "<the command that fixes it>" } ], bridge_gaps: "<one line per in-scope id no design maps, or empty>" }
748
790
  mock: { ok: true, bridge_ok: true, gap_count: 0, gap_refs: "", coverage: "", gaps: [], bridge_gaps: "" }
749
791
  task: |
750
792
  spec.md is the ORACLE. Judge the CODE against it and report every id it fails to
@@ -757,9 +799,11 @@ workflow:
757
799
 
758
800
  {{bridge_note}}
759
801
 
760
- The code in context is this slice's whole OKF file list, both lanes. Use Read to
761
- follow anything it calls that a trace needs. Do NOT modify a single file — this
762
- step is read-only by contract.
802
+ THIS SLICE'S CODE, both lanes, from the OKF concept ({{all_count}} file(s)) — READ
803
+ the ones each id's trace needs, and follow anything they call. They are named here
804
+ rather than inlined, because a real codebase does not fit a payload:
805
+ {{all_csv}}
806
+ Do NOT modify a single file — this step is read-only by contract.
763
807
 
764
808
  {{coverage_digest}}
765
809
 
@@ -777,6 +821,29 @@ workflow:
777
821
  a home in each. A UX- lives only in the UI lane; a BR- only in the service
778
822
  lane. Do not report a UI gap for a slice that has no UI lane in scope.
779
823
 
824
+ SUCCESS CRITERIA (SC-) ARE SCORED AS OUTCOMES, NOT AS REALISATIONS. An SC- is a
825
+ measurable result the whole feature is supposed to produce — "a customer can place
826
+ an order in under two minutes", "90% of first-time users complete an order without
827
+ assistance" — so no design element "maps" one and no file "realises" one. Four
828
+ rules, and they override steps (2) and (3) and the ok/bridge_ok sentences below for
829
+ this class alone:
830
+ • Score each in-scope SC- as MET, NOT MET, or NOT ASSESSABLE FROM A STATIC READ.
831
+ Most are the third: a latency budget, an adoption rate or a support-ticket
832
+ reduction cannot be decided by reading code, and inventing a verdict is worse
833
+ than recording that it needs measuring.
834
+ • NOT ASSESSABLE is not a gap. It does not set ok=false and it is not counted in
835
+ gap_count — say in its `coverage` line what would have to be measured, and by
836
+ what, to settle it.
837
+ • NOT MET does fail ok: reserve it for the case where the code plainly cannot
838
+ satisfy the criterion (the feature has no such surface at all, or the design
839
+ rules the outcome out).
840
+ • An SC- NEVER sets bridge_ok=false and is NEVER a "bridge-missing" gap. There is
841
+ no design element to be missing, so routing one to story-plan sends someone to
842
+ design a document entry that should not exist.
843
+ Every in-scope SC- still gets its `coverage` line. The record is the point: this is
844
+ the phase the spec names as their verifier, and "measured how, and when" is the
845
+ answer it is meant to leave behind.
846
+
780
847
  CLASSIFY every gap by `kind`, because the four routes are different commands:
781
848
  code-missing the design maps the id and no code realises it → /appweaver implement
782
849
  code-wrong code exists and does something else → /appweaver implement
@@ -798,9 +865,11 @@ workflow:
798
865
  Fill `coverage` with ONE LINE PER IN-SCOPE ID, delivered or not — that table is the
799
866
  traceability record this gate exists to leave behind, and it is written on a green
800
867
  run too. Set gap_count and gap_refs from `gaps`. Set ok=true ONLY when every
801
- in-scope id is delivered as the spec words it. Set bridge_ok=false when any
802
- in-scope id has no design mapping it, INDEPENDENTLY of ok — working code with a
803
- documentation gap is a true state and must be reportable as one.
868
+ in-scope FR-/BR-/CN-/DC-/IC-/UX- is delivered as the spec words it and no in-scope
869
+ SC- is NOT MET — an SC- scored not-assessable leaves ok exactly as it was. Set
870
+ bridge_ok=false when any in-scope id OTHER THAN AN SC- has no design mapping it,
871
+ INDEPENDENTLY of ok — working code with a documentation gap is a true state and must
872
+ be reportable as one.
804
873
  {{stale_note}}
805
874
  outputs:
806
875
  - { name: trace_ok, from: "$.ok" }
@@ -993,4 +1062,4 @@ outputs:
993
1062
  okf_orphans: "{{okf_orphans}}"
994
1063
  stale_ids: "{{stale_csv}}"
995
1064
  active_change: "{{active_change}}"
996
- next: "CHANGE OPEN ({{active_change}}): /appweaver close-delta — the slice delivers the contract, is domain-conformant and broke no referrer, and its delta is already merged into the master docs, so the change may now be archived and the version bumped. NO OPEN CHANGE: /appweaver close-story {{story}} to ratify its ADRs, flip its interface entries live and archive its design passes — then /appweaver run."
1065
+ next: "CHANGE OPEN ({{active_change}}): /appweaver close-delta — the slice delivers the contract, is domain-conformant and broke no referrer, and its delta is already merged into the master docs, so the change may now be archived and the version bumped. NO OPEN CHANGE: /appweaver close-story {{story}} would ratify its ADRs, flip its interface entries live and archive its design passes — but it is ⌛ NOT BUILT YET, so nothing closes a story and this one stays OPEN. Until it lands, do not read a green run here as the story being delivered: its ids stay in flight, a feature-scoped analyze-slice keeps subtracting them, and its ADRs and interface entries stay `proposed`. Then /appweaver run."