@codyswann/lisa 4.32.3 → 4.33.0

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 (208) hide show
  1. package/all/copy-overwrite/scripts/lib/gate-failure-diagnosis.mjs +94 -6
  2. package/all/copy-overwrite/scripts/lisa-gates.mjs +259 -5
  3. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +20 -39
  4. package/all/merge/.claude/settings.json +1 -3
  5. package/cdk/merge/.claude/settings.json +1 -1
  6. package/dist/cli/doctor-declared-contexts.d.ts +1 -1
  7. package/dist/cli/doctor-declared-contexts.d.ts.map +1 -1
  8. package/dist/cli/doctor-declared-contexts.js +48 -7
  9. package/dist/cli/doctor-declared-contexts.js.map +1 -1
  10. package/dist/cli/gate-report-drift-section.d.ts.map +1 -1
  11. package/dist/cli/gate-report-drift-section.js +2 -0
  12. package/dist/cli/gate-report-drift-section.js.map +1 -1
  13. package/dist/configs/vitest/base.d.ts +81 -0
  14. package/dist/configs/vitest/base.d.ts.map +1 -1
  15. package/dist/configs/vitest/base.js +106 -0
  16. package/dist/configs/vitest/base.js.map +1 -1
  17. package/dist/configs/vitest/cdk.d.ts +2 -2
  18. package/dist/configs/vitest/cdk.d.ts.map +1 -1
  19. package/dist/configs/vitest/cdk.js +4 -2
  20. package/dist/configs/vitest/cdk.js.map +1 -1
  21. package/dist/configs/vitest/harper-fabric.d.ts +2 -2
  22. package/dist/configs/vitest/harper-fabric.d.ts.map +1 -1
  23. package/dist/configs/vitest/harper-fabric.js +4 -2
  24. package/dist/configs/vitest/harper-fabric.js.map +1 -1
  25. package/dist/configs/vitest/nestjs.d.ts +2 -2
  26. package/dist/configs/vitest/nestjs.d.ts.map +1 -1
  27. package/dist/configs/vitest/nestjs.js +4 -2
  28. package/dist/configs/vitest/nestjs.js.map +1 -1
  29. package/dist/configs/vitest/phaser.d.ts +2 -2
  30. package/dist/configs/vitest/phaser.d.ts.map +1 -1
  31. package/dist/configs/vitest/phaser.js +4 -2
  32. package/dist/configs/vitest/phaser.js.map +1 -1
  33. package/dist/configs/vitest/typescript.d.ts +2 -2
  34. package/dist/configs/vitest/typescript.d.ts.map +1 -1
  35. package/dist/configs/vitest/typescript.js +4 -2
  36. package/dist/configs/vitest/typescript.js.map +1 -1
  37. package/dist/core/gate-context-owners.d.ts +18 -0
  38. package/dist/core/gate-context-owners.d.ts.map +1 -1
  39. package/dist/core/gate-context-owners.js +7 -0
  40. package/dist/core/gate-context-owners.js.map +1 -1
  41. package/dist/core/gate-declaration-drift.d.ts +37 -0
  42. package/dist/core/gate-declaration-drift.d.ts.map +1 -1
  43. package/dist/core/gate-declaration-drift.js +100 -26
  44. package/dist/core/gate-declaration-drift.js.map +1 -1
  45. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  46. package/dist/core/lisa-owned-hash-ledger.js +6 -0
  47. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  48. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  49. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  50. package/dist/core/upstream-evidence-manifest.js +47 -29
  51. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  52. package/dist/health/declared-checks-inspection.d.ts.map +1 -1
  53. package/dist/health/declared-checks-inspection.js +23 -7
  54. package/dist/health/declared-checks-inspection.js.map +1 -1
  55. package/dist/migrations/index.d.ts +1 -0
  56. package/dist/migrations/index.d.ts.map +1 -1
  57. package/dist/migrations/index.js +3 -0
  58. package/dist/migrations/index.js.map +1 -1
  59. package/dist/migrations/prune-retired-claude-deny-rules.d.ts +39 -0
  60. package/dist/migrations/prune-retired-claude-deny-rules.d.ts.map +1 -0
  61. package/dist/migrations/prune-retired-claude-deny-rules.js +152 -0
  62. package/dist/migrations/prune-retired-claude-deny-rules.js.map +1 -0
  63. package/expo/merge/.claude/settings.json +1 -1
  64. package/harper-fabric/merge/.claude/settings.json +1 -1
  65. package/nestjs/merge/.claude/settings.json +1 -1
  66. package/package.json +4 -4
  67. package/phaser/merge/.claude/settings.json +1 -1
  68. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa/.codex-plugin/skills/lisa-github-add-journey/SKILL.md +1 -0
  71. package/plugins/lisa/.codex-plugin/skills/lisa-github-validate-issue/SKILL.md +28 -1
  72. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +1 -0
  73. package/plugins/lisa/.codex-plugin/skills/lisa-jira-add-journey/SKILL.md +1 -0
  74. package/plugins/lisa/.codex-plugin/skills/lisa-jira-validate-ticket/SKILL.md +28 -1
  75. package/plugins/lisa/.codex-plugin/skills/lisa-linear-add-journey/SKILL.md +1 -0
  76. package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +27 -0
  77. package/plugins/lisa/.codex-plugin/skills/lisa-reproduce-bug/SKILL.md +8 -0
  78. package/plugins/lisa/.codex-plugin/skills/lisa-tdd-implementation/SKILL.md +1 -0
  79. package/plugins/lisa/.codex-plugin/skills/lisa-ticket-triage/SKILL.md +8 -0
  80. package/plugins/lisa/hooks/inject-resolved-config.mjs +12 -4
  81. package/plugins/lisa/hooks/inject-resolved-config.sh +39 -8
  82. package/plugins/lisa/rules/eager/control-reachability.md +39 -0
  83. package/plugins/lisa/rules/eager/falsifiable-checks.md +2 -0
  84. package/plugins/lisa/rules/eager/work-item-definition-of-ready.md +1 -1
  85. package/plugins/lisa/rules/reference/control-reachability.md +122 -0
  86. package/plugins/lisa/rules/reference/falsifiable-checks.md +2 -0
  87. package/plugins/lisa/skills/lisa-github-add-journey/SKILL.md +1 -0
  88. package/plugins/lisa/skills/lisa-github-validate-issue/SKILL.md +28 -1
  89. package/plugins/lisa/skills/lisa-implement/SKILL.md +1 -0
  90. package/plugins/lisa/skills/lisa-jira-add-journey/SKILL.md +1 -0
  91. package/plugins/lisa/skills/lisa-jira-validate-ticket/SKILL.md +28 -1
  92. package/plugins/lisa/skills/lisa-linear-add-journey/SKILL.md +1 -0
  93. package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +27 -0
  94. package/plugins/lisa/skills/lisa-reproduce-bug/SKILL.md +8 -0
  95. package/plugins/lisa/skills/lisa-tdd-implementation/SKILL.md +1 -0
  96. package/plugins/lisa/skills/lisa-ticket-triage/SKILL.md +8 -0
  97. package/plugins/lisa-agy/plugin.json +1 -1
  98. package/plugins/lisa-agy/skills/lisa-github-add-journey/SKILL.md +1 -0
  99. package/plugins/lisa-agy/skills/lisa-github-validate-issue/SKILL.md +28 -1
  100. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +1 -0
  101. package/plugins/lisa-agy/skills/lisa-jira-add-journey/SKILL.md +1 -0
  102. package/plugins/lisa-agy/skills/lisa-jira-validate-ticket/SKILL.md +28 -1
  103. package/plugins/lisa-agy/skills/lisa-linear-add-journey/SKILL.md +1 -0
  104. package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +27 -0
  105. package/plugins/lisa-agy/skills/lisa-reproduce-bug/SKILL.md +8 -0
  106. package/plugins/lisa-agy/skills/lisa-tdd-implementation/SKILL.md +1 -0
  107. package/plugins/lisa-agy/skills/lisa-ticket-triage/SKILL.md +8 -0
  108. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  111. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-copilot/hooks/inject-resolved-config.mjs +12 -4
  115. package/plugins/lisa-copilot/hooks/inject-resolved-config.sh +39 -8
  116. package/plugins/lisa-copilot/rules/eager/control-reachability.md +39 -0
  117. package/plugins/lisa-copilot/rules/eager/falsifiable-checks.md +2 -0
  118. package/plugins/lisa-copilot/rules/eager/work-item-definition-of-ready.md +1 -1
  119. package/plugins/lisa-copilot/rules/reference/control-reachability.md +122 -0
  120. package/plugins/lisa-copilot/rules/reference/falsifiable-checks.md +2 -0
  121. package/plugins/lisa-copilot/skills/lisa-github-add-journey/SKILL.md +1 -0
  122. package/plugins/lisa-copilot/skills/lisa-github-validate-issue/SKILL.md +28 -1
  123. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +1 -0
  124. package/plugins/lisa-copilot/skills/lisa-jira-add-journey/SKILL.md +1 -0
  125. package/plugins/lisa-copilot/skills/lisa-jira-validate-ticket/SKILL.md +28 -1
  126. package/plugins/lisa-copilot/skills/lisa-linear-add-journey/SKILL.md +1 -0
  127. package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +27 -0
  128. package/plugins/lisa-copilot/skills/lisa-reproduce-bug/SKILL.md +8 -0
  129. package/plugins/lisa-copilot/skills/lisa-tdd-implementation/SKILL.md +1 -0
  130. package/plugins/lisa-copilot/skills/lisa-ticket-triage/SKILL.md +8 -0
  131. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-cursor/hooks/inject-resolved-config.mjs +12 -4
  133. package/plugins/lisa-cursor/hooks/inject-resolved-config.sh +39 -8
  134. package/plugins/lisa-cursor/rules/control-reachability-reference.mdc +127 -0
  135. package/plugins/lisa-cursor/rules/control-reachability.mdc +44 -0
  136. package/plugins/lisa-cursor/rules/falsifiable-checks-reference.mdc +2 -0
  137. package/plugins/lisa-cursor/rules/falsifiable-checks.mdc +2 -0
  138. package/plugins/lisa-cursor/rules/work-item-definition-of-ready.mdc +1 -1
  139. package/plugins/lisa-cursor/skills/lisa-github-add-journey/SKILL.md +1 -0
  140. package/plugins/lisa-cursor/skills/lisa-github-validate-issue/SKILL.md +28 -1
  141. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +1 -0
  142. package/plugins/lisa-cursor/skills/lisa-jira-add-journey/SKILL.md +1 -0
  143. package/plugins/lisa-cursor/skills/lisa-jira-validate-ticket/SKILL.md +28 -1
  144. package/plugins/lisa-cursor/skills/lisa-linear-add-journey/SKILL.md +1 -0
  145. package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +27 -0
  146. package/plugins/lisa-cursor/skills/lisa-reproduce-bug/SKILL.md +8 -0
  147. package/plugins/lisa-cursor/skills/lisa-tdd-implementation/SKILL.md +1 -0
  148. package/plugins/lisa-cursor/skills/lisa-ticket-triage/SKILL.md +8 -0
  149. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  150. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  151. package/plugins/lisa-expo-agy/plugin.json +1 -1
  152. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  153. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  154. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  155. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  156. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  157. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  158. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  159. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  160. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  161. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  162. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  163. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  164. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  165. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  166. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  167. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  168. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  169. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  170. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  171. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  172. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  173. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  174. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  175. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  176. package/plugins/lisa-rails-agy/plugin.json +1 -1
  177. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  178. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  179. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  180. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  181. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  182. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  183. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  184. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  185. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  186. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  187. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  188. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  189. package/plugins/src/base/hooks/inject-resolved-config.mjs +12 -4
  190. package/plugins/src/base/hooks/inject-resolved-config.sh +39 -8
  191. package/plugins/src/base/rules/eager/control-reachability.md +39 -0
  192. package/plugins/src/base/rules/eager/falsifiable-checks.md +2 -0
  193. package/plugins/src/base/rules/eager/work-item-definition-of-ready.md +1 -1
  194. package/plugins/src/base/rules/reference/control-reachability.md +122 -0
  195. package/plugins/src/base/rules/reference/falsifiable-checks.md +2 -0
  196. package/plugins/src/base/skills/lisa-github-add-journey/SKILL.md +1 -0
  197. package/plugins/src/base/skills/lisa-github-validate-issue/SKILL.md +28 -1
  198. package/plugins/src/base/skills/lisa-implement/SKILL.md +1 -0
  199. package/plugins/src/base/skills/lisa-jira-add-journey/SKILL.md +1 -0
  200. package/plugins/src/base/skills/lisa-jira-validate-ticket/SKILL.md +28 -1
  201. package/plugins/src/base/skills/lisa-linear-add-journey/SKILL.md +1 -0
  202. package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +27 -0
  203. package/plugins/src/base/skills/lisa-reproduce-bug/SKILL.md +8 -0
  204. package/plugins/src/base/skills/lisa-tdd-implementation/SKILL.md +1 -0
  205. package/plugins/src/base/skills/lisa-ticket-triage/SKILL.md +8 -0
  206. package/rails/merge/.claude/settings.json +1 -1
  207. package/scripts/two-channel-couplings.json +1 -1
  208. package/typescript/merge/.claude/settings.json +1 -1
@@ -66,6 +66,7 @@ TDD Cycle:
66
66
  - The team lead may not waive, defer, or mark that user-visible regression spec as optional, "if cheap", or equivalent. The only exits are a recorded absence of an end-to-end harness for the affected platform, or a genuine technical blocker with a linked build-ready follow-up ticket created before merge and referenced from the PR and source work item.
67
67
  - For frontend work, the `bdd-e2e-coverage` rule governs what that spec is sealing: before writing it, add or update the Gherkin scenario with its stable ID in the project's behavior contract, then write aligned automation in the project's configured runner for each platform the scenario requires. The scenario is the specification the RED test encodes — write the scenario first, in the same PR.
68
68
  - A regression spec is not complete merely because it exists. Completion evidence must prove the spec actually ran and passed in PR CI with a named log line, reporter output, or equivalent execution record. Guard against `test.skip`, suite-level environment gates, shard filters, and "0 tests" passes.
69
+ - When the work item **prescribes an existing test as the control** — "that test must go red; if it still passes the fix did nothing" — the `control-reachability` rule governs what a green control means. It is two facts, not one: the change had no effect (revisit the change), or the fixture never reached the changed path (fix or extend the control — do **not** revert). They demand opposite actions, and the cheaper reading wins by default when nothing forces the distinction. **Establish which holds before acting on the stopping rule, and prove reachability by execution, never by reading the fixture** — a temporary `throw` at the top of the changed block run under that one test, or coverage scoped to that test alone. Never revert on an unexplained green; an unexplained control is a blocked observation, not a verdict. If the item carries a `[CONTROL: <test> | reaches: <input>]` marker, that marker is the claim you are checking.
69
70
 
70
71
  ### GREEN Phase
71
72
 
@@ -156,6 +156,14 @@ Produce a table:
156
156
 
157
157
  Every verification method must be specific enough that an automated agent could execute it.
158
158
 
159
+ ### Prescribed controls must be reachable
160
+
161
+ When the ticket pins an **existing** test as a red-before-green control — "that test must go red", "fails before the fix and passes after", "if it still passes the fix did nothing" — apply the `control-reachability` rule before accepting it. The stopping rule is only valid if the test's fixture exercises the path the change touches; when it does not, the test stays green for an unrelated reason and the ticket instructs the implementer to revert a correct fix.
162
+
163
+ - Confirm the ticket carries the `[CONTROL: <test-identifier> | reaches: <input-or-field>]` marker gate S20 requires, and that the named input is actually present in that test's fixture. Verify it in the codebase — this is a triage step precisely because triage is the last point before an implementer starts trusting it.
164
+ - A named control with no reachability declaration, or one whose declared input is absent from the fixture, is an **Ambiguity** finding under Phase 3, not a pass. Report it with the specific input the fixture would need, or the recommendation to specify a new test instead.
165
+ - A ticket that introduces a **new** test rather than pinning an existing one carries no such obligation. Do not raise a finding on this basis.
166
+
159
167
  ## Phase 6 -- Verdict
160
168
 
161
169
  Evaluate the findings and produce exactly one verdict:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.32.3",
3
+ "version": "4.33.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -87,6 +87,7 @@ Compose the journey with typed `[EVIDENCE: <artifact-type>: <name>]` markers at
87
87
  6. **Cover happy path AND error path** — At minimum, one success and one failure marker.
88
88
  7. **On a leaf work unit, the markers are binding** — For a Bug / Task / Sub-task / Improvement, every typed `[EVIDENCE: <artifact-type>: <name>]` here is the issue's evidence manifest: validation gate S14 requires at least one, and the issue cannot be closed until each named artifact is captured **in its declared type** and attached (see the "Per-Work-Unit Evidence Contract" in the `verification` rule). Name only evidence you intend to capture — and name all of it.
89
89
  8. **Reference sibling evidence without claiming it** — Use only `[EVIDENCE-REF: <work-item-ref> | <artifact-type>: <kebab-case-name>]` when prose points to an artifact declared by another issue. Never paste, quote, or code-format the sibling's `[EVIDENCE: ...]` marker: that exact prefix creates a local obligation. `EVIDENCE-REF` never satisfies this issue's S14 minimum, uniqueness check, capture list, or completion gate; a runtime-changing leaf still needs at least one local `[EVIDENCE: ...]` marker.
90
+ 9. **A named existing test is a control, and a control declares its reachability** — When the journey pins an *existing* test as a red-before-green control ("must go red", "fails before the fix and passes after"), the `control-reachability` rule requires the issue to say what makes that test reach the changed code: `[CONTROL: <test-identifier> | reaches: <input-or-field>]`, one per named control, validated by gate S20. Name the fixture key, field, argument, or state that carries execution into the change — not the code itself. A test whose fixture never reaches the changed path stays green for a reason unrelated to the change, and the stopping rule then reads as "revert a correct fix". Introducing a **new** test instead carries no such obligation; S20 is `N/A` and no marker is written.
90
91
 
91
92
  ### Step 6: Present to User for Approval
92
93
 
@@ -100,6 +100,7 @@ Per-type content requirements are defined once in the vendor-neutral `work-item-
100
100
  | S17 Improvement measurability | `acceptance-criteria` | true |
101
101
  | S18 Stateless-pickup dry-run | `product-clarity` | true |
102
102
  | S19 Branch Plan derivation | `technical` | false |
103
+ | S20 Named-control reachability | `acceptance-criteria` | true |
103
104
  | F1 Issue type label exists in repo | `structural` | false |
104
105
  | F2 Parent sub-issue exists and is the right type | `structural` | false |
105
106
  | F3 Linked issues exist | `structural` | false |
@@ -326,6 +327,31 @@ Failing a proposed spec is free; failing every existing issue would turn a legac
326
327
 
327
328
  FAIL names both plans (rendered and recomputed) and points the remediation at **the environment, never the branch** — e.g. `"Branch Plan conflicts with the environment mapping. Rendered 'Branch from: release/staging'; recomputed from Target Backend Environment 'production' via deploy.branches → 'main'. Correct the environment, not the branch — the branches are derived. Then re-render."` Never silently choose between the two plans.
328
329
 
330
+ #### S20 — Named-control reachability
331
+
332
+ Enforces the vendor-neutral `control-reachability` rule: **a work item that names an existing test as a red-before-green control must say what makes that test reach the code the change touches.** A control whose reachability cannot be stated is not a control — its green tells the implementer nothing, and the issue's own stopping rule then instructs a revert of a correct fix.
333
+
334
+ **When the gate applies.** Run S20 when the body — `Acceptance Criteria`, `Technical Approach`, or `Validation Journey` — names an **existing** test AND predicts a state change for it when the work lands. Signals: "must go red", "currently passes and must fail", "fails before the fix and passes after", "if it still passes the fix did nothing", or any equivalent stopping rule.
335
+
336
+ **When it must stay silent.** `N/A` otherwise. A issue that introduces a **new** test rather than pinning an existing one carries no reachability obligation and must never be reported as incomplete on this basis. This half is load-bearing: a gate that fires on every work item is one callers learn to route around.
337
+
338
+ **What must be present.** One marker per named control, inside `## Validation Journey`:
339
+
340
+ ```text
341
+ [CONTROL: <test-identifier> | reaches: <input-or-field>]
342
+ ```
343
+
344
+ Parse by the exact `[CONTROL:` prefix and split on the single `|`.
345
+
346
+ - `<test-identifier>` — non-empty; the test file path, the test name, or both. It must be enough to run the test.
347
+ - `reaches:` — the literal key, then a non-empty description of the **input, field, fixture key, argument, or state** that carries execution into the changed code.
348
+
349
+ FAIL when a named existing-test control has no marker, when a marker is malformed (missing the `|`, an empty identifier, a missing or empty `reaches:` half), or when the `reaches:` half restates the assertion instead of naming an input (`reaches: the fix`, `reaches: the changed code path`). Naming the code is not enough; the marker answers "what in this fixture gets execution there".
350
+
351
+ Remediation: `"Name the input or field that makes <test> reach the code this work changes, as [CONTROL: <test> | reaches: <field>]. If the fixture does not reach it, the test cannot observe this change — extend the fixture or specify a new test instead."`
352
+
353
+ `product_relevant: true` — an unfalsifiable stopping rule is a specification defect, and whoever wrote the issue is who can repair it. `lisa-github-write-issue` renders the marker; `control-reachability` carries the implementer-side counterpart, which forbids acting on the stopping rule before the cause of an unmoved control is established.
354
+
329
355
  ### Feasibility Gates (require GitHub lookups; skip in `--spec-only`)
330
356
 
331
357
  #### F1 — Issue type label exists in repo
@@ -487,6 +513,7 @@ Output is a single fenced text block. Callers parse it; do not add free-form pro
487
513
  - [PASS|FAIL|N/A] S17 Improvement measurability — <one-line reason>
488
514
  - [PASS|FAIL|N/A] S18 Stateless-pickup dry-run — <one-line reason>
489
515
  - [PASS|FAIL|N/A] S19 Branch Plan derivation — <one-line reason>
516
+ - [PASS|FAIL|N/A] S20 Named-control reachability — <one-line reason>
490
517
 
491
518
  ### Feasibility Gates (omit this section when --spec-only)
492
519
  - [PASS|FAIL|N/A] F1 Issue type label exists in repo — <one-line reason>
@@ -513,7 +540,7 @@ The verdict is `PASS` if every applicable gate is `PASS`. Any `FAIL` makes the v
513
540
 
514
541
  Same shape and meaning as `lisa-jira-validate-ticket` so downstream PRD-intake skills (Notion, Confluence, Linear, GitHub) can format comments uniformly:
515
542
 
516
- - **gate**: the gate ID (`S1`–`S18`, `F1`–`F5`).
543
+ - **gate**: the gate ID (`S1`–`S20`, `F1`–`F5`).
517
544
  - **category**: the gate's fixed category from the table.
518
545
  - **product_relevant**: matches the gate's table entry. `false` means the failure is an internal data-quality problem the caller should fix without bothering product.
519
546
  - **what**: plain-language, product-readable.
@@ -100,6 +100,7 @@ When deciding the agents to use, consider:
100
100
  * Before any task is implemented, the agent team must explore the codebase for relevant research (documentation, code, git history, etc) and update each task's `metadata.relevant_documentation` with the findings.
101
101
  * Each task must be reviewed by the team to make sure their verification passes.
102
102
  * Each task must have their learnings captured to the ledger by the learner subagent.
103
+ * When the work item prescribes an **existing** test as a red-before-green control ("that test must go red once this lands"), the `control-reachability` rule binds the team. Read the item's `[CONTROL: <test-identifier> | reaches: <input-or-field>]` declaration (gate S20) and confirm the named input is in that test's fixture. If the control does not move when the change lands, **do not act on the stopping rule until the cause is established**: the change had no effect (revisit the change) or the fixture never reached the changed path (fix or extend the control — never revert). Prove reachability by execution — a temporary `throw` in the changed block under that one test, or coverage scoped to it — never by reading the fixture. A named control with no reachability declaration is an unvalidated control; treat it as a triage finding, not a pass.
103
104
 
104
105
  Using the general-purpose agent in Team Lead session, Determine the name of this plan
105
106
 
@@ -111,6 +111,7 @@ h3. Assertions
111
111
  6. **Cover happy path and error path** — At minimum, one success and one failure evidence marker
112
112
  7. **On a leaf work unit, the markers are binding** — For a Bug / Task / Sub-task / Improvement, every typed `[EVIDENCE: <artifact-type>: <name>]` here is the ticket's evidence manifest: validation gate S14 requires at least one, and the ticket cannot be closed until each named artifact is captured **in its declared type** and attached (see the "Per-Work-Unit Evidence Contract" in the `verification` rule). Name only evidence you intend to capture — and name all of it.
113
113
  8. **Reference sibling evidence without claiming it** — Use only `[EVIDENCE-REF: <work-item-ref> | <artifact-type>: <kebab-case-name>]` when prose points to an artifact declared by another ticket. Never paste, quote, or code-format the sibling's `[EVIDENCE: ...]` marker: that exact prefix creates a local obligation. `EVIDENCE-REF` never satisfies this ticket's S14 minimum, uniqueness check, capture list, or completion gate; a runtime-changing leaf still needs at least one local `[EVIDENCE: ...]` marker.
114
+ 9. **A named existing test is a control, and a control declares its reachability** — When the journey pins an *existing* test as a red-before-green control ("must go red", "fails before the fix and passes after"), the `control-reachability` rule requires the ticket to say what makes that test reach the changed code: `[CONTROL: <test-identifier> | reaches: <input-or-field>]`, one per named control, validated by gate S20. Name the fixture key, field, argument, or state that carries execution into the change — not the code itself. A test whose fixture never reaches the changed path stays green for a reason unrelated to the change, and the stopping rule then reads as "revert a correct fix". Introducing a **new** test instead carries no such obligation; S20 is `N/A` and no marker is written.
114
115
 
115
116
  ### Step 6: Present to User for Approval
116
117
 
@@ -97,6 +97,7 @@ Per-type content requirements are defined once in the vendor-neutral `work-item-
97
97
  | S17 Improvement measurability | `acceptance-criteria` | true |
98
98
  | S18 Stateless-pickup dry-run | `product-clarity` | true |
99
99
  | S19 Branch Plan derivation | `technical` | false |
100
+ | S20 Named-control reachability | `acceptance-criteria` | true |
100
101
  | F1 Issue type valid in project | `structural` | false |
101
102
  | F2 Epic parent exists and is an Epic | `structural` | false |
102
103
  | F3 Linked tickets exist | `structural` | false |
@@ -328,6 +329,31 @@ Failing a proposed spec is free; failing every existing ticket would turn a lega
328
329
 
329
330
  FAIL names both plans (rendered and recomputed) and points the remediation at **the environment, never the branch** — e.g. `"Branch Plan conflicts with the environment mapping. Rendered 'Branch from: release/staging'; recomputed from Target Backend Environment 'production' via deploy.branches → 'main'. Correct the environment, not the branch — the branches are derived. Then re-render."` Never silently choose between the two plans.
330
331
 
332
+ #### S20 — Named-control reachability
333
+
334
+ Enforces the vendor-neutral `control-reachability` rule: **a work item that names an existing test as a red-before-green control must say what makes that test reach the code the change touches.** A control whose reachability cannot be stated is not a control — its green tells the implementer nothing, and the ticket's own stopping rule then instructs a revert of a correct fix.
335
+
336
+ **When the gate applies.** Run S20 when the body — `Acceptance Criteria`, `Technical Approach`, or `Validation Journey` — names an **existing** test AND predicts a state change for it when the work lands. Signals: "must go red", "currently passes and must fail", "fails before the fix and passes after", "if it still passes the fix did nothing", or any equivalent stopping rule.
337
+
338
+ **When it must stay silent.** `N/A` otherwise. A ticket that introduces a **new** test rather than pinning an existing one carries no reachability obligation and must never be reported as incomplete on this basis. This half is load-bearing: a gate that fires on every work item is one callers learn to route around.
339
+
340
+ **What must be present.** One marker per named control, inside `h2. Validation Journey`:
341
+
342
+ ```text
343
+ [CONTROL: <test-identifier> | reaches: <input-or-field>]
344
+ ```
345
+
346
+ Parse by the exact `[CONTROL:` prefix and split on the single `|`.
347
+
348
+ - `<test-identifier>` — non-empty; the test file path, the test name, or both. It must be enough to run the test.
349
+ - `reaches:` — the literal key, then a non-empty description of the **input, field, fixture key, argument, or state** that carries execution into the changed code.
350
+
351
+ FAIL when a named existing-test control has no marker, when a marker is malformed (missing the `|`, an empty identifier, a missing or empty `reaches:` half), or when the `reaches:` half restates the assertion instead of naming an input (`reaches: the fix`, `reaches: the changed code path`). Naming the code is not enough; the marker answers "what in this fixture gets execution there".
352
+
353
+ Remediation: `"Name the input or field that makes <test> reach the code this work changes, as [CONTROL: <test> | reaches: <field>]. If the fixture does not reach it, the test cannot observe this change — extend the fixture or specify a new test instead."`
354
+
355
+ `product_relevant: true` — an unfalsifiable stopping rule is a specification defect, and whoever wrote the ticket is who can repair it. `lisa-jira-write-ticket` renders the marker; `control-reachability` carries the implementer-side counterpart, which forbids acting on the stopping rule before the cause of an unmoved control is established.
356
+
331
357
  ### Feasibility Gates (require JIRA lookups; skip in dry-run if requested)
332
358
 
333
359
  #### F1 — Issue type valid in project
@@ -416,6 +442,7 @@ Output is a single fenced text block. Callers parse it; do not add free-form pro
416
442
  - [PASS|FAIL|N/A] S17 Improvement measurability — <one-line reason>
417
443
  - [PASS|FAIL|N/A] S18 Stateless-pickup dry-run — <one-line reason>
418
444
  - [PASS|FAIL|N/A] S19 Branch Plan derivation — <one-line reason>
445
+ - [PASS|FAIL|N/A] S20 Named-control reachability — <one-line reason>
419
446
 
420
447
  ### Feasibility Gates (omit this section when --spec-only)
421
448
  - [PASS|FAIL|N/A] F1 Issue type valid in project — <one-line reason>
@@ -441,7 +468,7 @@ The verdict is `PASS` if and only if every applicable gate is `PASS`. Any `FAIL`
441
468
 
442
469
  ### Failure-detail fields
443
470
 
444
- - **gate**: the gate ID (`S1`–`S18`, `F1`–`F5`).
471
+ - **gate**: the gate ID (`S1`–`S20`, `F1`–`F5`).
445
472
  - **category**: the gate's fixed category from the table above. Callers use this to label or filter comments — `product-clarity`, `acceptance-criteria`, `design-ux`, `scope`, `dependency`, `data`, `technical`, or `structural`.
446
473
  - **product_relevant**: matches the gate's table entry. `false` means the failure is an internal data-quality problem (e.g., the agent built a malformed spec, an issue type is invalid in the project) and the caller should fix it without bothering the product team. `true` means the PRD needs product input to resolve.
447
474
  - **what**: plain-language description of the issue. No gate IDs, no JIRA jargon, no engineering shorthand. A product owner reading this on a Notion comment should understand what is unclear and why.
@@ -87,6 +87,7 @@ Compose the journey with typed `[EVIDENCE: <artifact-type>: <name>]` markers at
87
87
  6. **Cover happy path and error path** — At minimum, one success and one failure evidence marker.
88
88
  7. **On a leaf work unit, the markers are binding** — For a Bug / Task / Sub-task / Improvement, every typed `[EVIDENCE: <artifact-type>: <name>]` here is the item's evidence manifest: validation gate S14 requires at least one, and the item cannot be closed until each named artifact is captured **in its declared type** and attached (see the "Per-Work-Unit Evidence Contract" in the `verification` rule). Name only evidence you intend to capture — and name all of it.
89
89
  8. **Reference sibling evidence without claiming it** — Use only `[EVIDENCE-REF: <work-item-ref> | <artifact-type>: <kebab-case-name>]` when prose points to an artifact declared by another item. Never paste, quote, or code-format the sibling's `[EVIDENCE: ...]` marker: that exact prefix creates a local obligation. `EVIDENCE-REF` never satisfies this item's S14 minimum, uniqueness check, capture list, or completion gate; a runtime-changing leaf still needs at least one local `[EVIDENCE: ...]` marker.
90
+ 9. **A named existing test is a control, and a control declares its reachability** — When the journey pins an *existing* test as a red-before-green control ("must go red", "fails before the fix and passes after"), the `control-reachability` rule requires the item to say what makes that test reach the changed code: `[CONTROL: <test-identifier> | reaches: <input-or-field>]`, one per named control, validated by gate S20. Name the fixture key, field, argument, or state that carries execution into the change — not the code itself. A test whose fixture never reaches the changed path stays green for a reason unrelated to the change, and the stopping rule then reads as "revert a correct fix". Introducing a **new** test instead carries no such obligation; S20 is `N/A` and no marker is written.
90
91
 
91
92
  ### Step 6: Present to User for Approval
92
93
 
@@ -98,6 +98,7 @@ Per-type content requirements are defined once in the vendor-neutral `work-item-
98
98
  | S17 Improvement measurability | `acceptance-criteria` | true |
99
99
  | S18 Stateless-pickup dry-run | `product-clarity` | true |
100
100
  | S19 Branch Plan derivation | `technical` | false |
101
+ | S20 Named-control reachability | `acceptance-criteria` | true |
101
102
  | F1 Issue type valid in team | `structural` | false |
102
103
  | F2 Project parent exists and is in same team | `structural` | false |
103
104
  | F3 Linked items exist | `structural` | false |
@@ -330,6 +331,31 @@ Failing a proposed spec is free; failing every existing Issue would turn a legac
330
331
 
331
332
  FAIL names both plans (rendered and recomputed) and points the remediation at **the environment, never the branch** — e.g. `"Branch Plan conflicts with the environment mapping. Rendered 'Branch from: release/staging'; recomputed from Target Backend Environment 'production' via deploy.branches → 'main'. Correct the environment, not the branch — the branches are derived. Then re-render."` Never silently choose between the two plans.
332
333
 
334
+ #### S20 — Named-control reachability
335
+
336
+ Enforces the vendor-neutral `control-reachability` rule: **a work item that names an existing test as a red-before-green control must say what makes that test reach the code the change touches.** A control whose reachability cannot be stated is not a control — its green tells the implementer nothing, and the Issue's own stopping rule then instructs a revert of a correct fix.
337
+
338
+ **When the gate applies.** Run S20 when the body — `Acceptance Criteria`, `Technical Approach`, or `Validation Journey` — names an **existing** test AND predicts a state change for it when the work lands. Signals: "must go red", "currently passes and must fail", "fails before the fix and passes after", "if it still passes the fix did nothing", or any equivalent stopping rule.
339
+
340
+ **When it must stay silent.** `N/A` otherwise. A Issue that introduces a **new** test rather than pinning an existing one carries no reachability obligation and must never be reported as incomplete on this basis. This half is load-bearing: a gate that fires on every work item is one callers learn to route around.
341
+
342
+ **What must be present.** One marker per named control, inside `## Validation Journey`:
343
+
344
+ ```text
345
+ [CONTROL: <test-identifier> | reaches: <input-or-field>]
346
+ ```
347
+
348
+ Parse by the exact `[CONTROL:` prefix and split on the single `|`.
349
+
350
+ - `<test-identifier>` — non-empty; the test file path, the test name, or both. It must be enough to run the test.
351
+ - `reaches:` — the literal key, then a non-empty description of the **input, field, fixture key, argument, or state** that carries execution into the changed code.
352
+
353
+ FAIL when a named existing-test control has no marker, when a marker is malformed (missing the `|`, an empty identifier, a missing or empty `reaches:` half), or when the `reaches:` half restates the assertion instead of naming an input (`reaches: the fix`, `reaches: the changed code path`). Naming the code is not enough; the marker answers "what in this fixture gets execution there".
354
+
355
+ Remediation: `"Name the input or field that makes <test> reach the code this work changes, as [CONTROL: <test> | reaches: <field>]. If the fixture does not reach it, the test cannot observe this change — extend the fixture or specify a new test instead."`
356
+
357
+ `product_relevant: true` — an unfalsifiable stopping rule is a specification defect, and whoever wrote the Issue is who can repair it. `lisa-linear-write-issue` renders the marker; `control-reachability` carries the implementer-side counterpart, which forbids acting on the stopping rule before the cause of an unmoved control is established.
358
+
333
359
  ### Feasibility Gates (require Linear lookups; skip in dry-run if requested)
334
360
 
335
361
  #### F1 — Issue type valid in team
@@ -422,6 +448,7 @@ Output is a single fenced text block. Callers parse it; do not add free-form pro
422
448
  - [PASS|FAIL|N/A] S17 Improvement measurability — <one-line reason>
423
449
  - [PASS|FAIL|N/A] S18 Stateless-pickup dry-run — <one-line reason>
424
450
  - [PASS|FAIL|N/A] S19 Branch Plan derivation — <one-line reason>
451
+ - [PASS|FAIL|N/A] S20 Named-control reachability — <one-line reason>
425
452
 
426
453
  ### Feasibility Gates (omit when --spec-only)
427
454
  - [PASS|FAIL|N/A] F1 Issue type valid in team — <one-line reason>
@@ -38,6 +38,14 @@ If the real path is unreachable — no credentials, no environment, no data —
38
38
 
39
39
  **A failing test is the preferred form** wherever it can cross the real path: it runs in CI, it becomes the regression guard, and `codify-verification` expects it. A script is the fallback. Manual steps are the last resort and must carry their prerequisites.
40
40
 
41
+ ## When the work item names an existing test as the reproduction
42
+
43
+ A work item may hand you the control instead of asking you to build one: *"`<existing test>` pins today's wrong behaviour; it must go red once this is fixed."* Treat that as a reproduction only once it is **reachable** — the `control-reachability` rule holds that a test whose fixture never exercises the changed path stays green for a reason unrelated to the change, so its green is uninterpretable and the item's stopping rule reads as "revert a correct fix".
44
+
45
+ - **Before trusting it**, check the item for the `[CONTROL: <test-identifier> | reaches: <input-or-field>]` marker gate S20 requires, and confirm the named input is actually in the fixture. A named test with no stated reachability is an unvalidated control, not a reproduction.
46
+ - **If it does not move when the change lands**, stop and establish why: the change had no effect (revisit the change), or the fixture never reached the changed path (fix or extend the control). Prove it by execution — a temporary `throw` in the changed block run under that one test, or coverage scoped to that test alone — never by reading the fixture and concluding it looks right.
47
+ - **Never revert on an unexplained green.** Report the finding — which method you used and what it showed — the way any other reproduction result is reported.
48
+
41
49
  ## Report the failure rate, do not round it
42
50
 
43
51
  Run the reproduction enough times to state a rate. A reproduction that fails half the time cannot prove a fix — one passing run afterwards means nothing at that rate. If the rate is too low to distinguish a fix from luck, say what would raise it: more iterations, a forced schedule, a narrowed trigger, a seeded clock.
@@ -66,6 +66,7 @@ TDD Cycle:
66
66
  - The team lead may not waive, defer, or mark that user-visible regression spec as optional, "if cheap", or equivalent. The only exits are a recorded absence of an end-to-end harness for the affected platform, or a genuine technical blocker with a linked build-ready follow-up ticket created before merge and referenced from the PR and source work item.
67
67
  - For frontend work, the `bdd-e2e-coverage` rule governs what that spec is sealing: before writing it, add or update the Gherkin scenario with its stable ID in the project's behavior contract, then write aligned automation in the project's configured runner for each platform the scenario requires. The scenario is the specification the RED test encodes — write the scenario first, in the same PR.
68
68
  - A regression spec is not complete merely because it exists. Completion evidence must prove the spec actually ran and passed in PR CI with a named log line, reporter output, or equivalent execution record. Guard against `test.skip`, suite-level environment gates, shard filters, and "0 tests" passes.
69
+ - When the work item **prescribes an existing test as the control** — "that test must go red; if it still passes the fix did nothing" — the `control-reachability` rule governs what a green control means. It is two facts, not one: the change had no effect (revisit the change), or the fixture never reached the changed path (fix or extend the control — do **not** revert). They demand opposite actions, and the cheaper reading wins by default when nothing forces the distinction. **Establish which holds before acting on the stopping rule, and prove reachability by execution, never by reading the fixture** — a temporary `throw` at the top of the changed block run under that one test, or coverage scoped to that test alone. Never revert on an unexplained green; an unexplained control is a blocked observation, not a verdict. If the item carries a `[CONTROL: <test> | reaches: <input>]` marker, that marker is the claim you are checking.
69
70
 
70
71
  ### GREEN Phase
71
72
 
@@ -156,6 +156,14 @@ Produce a table:
156
156
 
157
157
  Every verification method must be specific enough that an automated agent could execute it.
158
158
 
159
+ ### Prescribed controls must be reachable
160
+
161
+ When the ticket pins an **existing** test as a red-before-green control — "that test must go red", "fails before the fix and passes after", "if it still passes the fix did nothing" — apply the `control-reachability` rule before accepting it. The stopping rule is only valid if the test's fixture exercises the path the change touches; when it does not, the test stays green for an unrelated reason and the ticket instructs the implementer to revert a correct fix.
162
+
163
+ - Confirm the ticket carries the `[CONTROL: <test-identifier> | reaches: <input-or-field>]` marker gate S20 requires, and that the named input is actually present in that test's fixture. Verify it in the codebase — this is a triage step precisely because triage is the last point before an implementer starts trusting it.
164
+ - A named control with no reachability declaration, or one whose declared input is absent from the fixture, is an **Ambiguity** finding under Phase 3, not a pass. Report it with the specific input the fixture would need, or the recommendation to specify a new test instead.
165
+ - A ticket that introduces a **new** test rather than pinning an existing one carries no such obligation. Do not raise a finding on this basis.
166
+
159
167
  ## Phase 6 -- Verdict
160
168
 
161
169
  Evaluate the findings and produce exactly one verdict:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.32.3",
3
+ "version": "4.33.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.32.3",
3
+ "version": "4.33.0",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.32.3",
3
+ "version": "4.33.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.32.3",
3
+ "version": "4.33.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.32.3",
3
+ "version": "4.33.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.32.3",
3
+ "version": "4.33.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -188,10 +188,18 @@ const CREDENTIAL_SHAPED = [
188
188
  /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
189
189
  // Credentials in a connection string's userinfo. The whole URL goes: the
190
190
  // host is not worth keeping at the price of splicing a line back together
191
- // around the part that was cut out.
192
- /\b[a-z][a-z0-9+.-]*:\/\/[^\s:@,=]+:[^\s@,=]+@[^\s,=]*/g,
193
- // Vendor-prefixed API keys.
194
- /\b(?:[a-z]{2}_(?:live|test)_[A-Za-z0-9]{8,}|gh[pousr]_[A-Za-z0-9]{16,}|xox[abprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{12,}|AIza[0-9A-Za-z_-]{20,}|sk-[A-Za-z0-9_-]{16,})\b/g,
191
+ // around the part that was cut out. Neither userinfo field may contain `/`,
192
+ // because a path separator means the text is a path and not userinfo:
193
+ // `https://host:8443/repos/main@v2` otherwise reads as password `8443/repos/
194
+ // main` at host `v2`, and an ordinary URL is redacted whole.
195
+ /\b[a-z][a-z0-9+.-]*:\/\/[^\s:@,=/]+:[^\s@,=/]+@[^\s,=]*/g,
196
+ // Vendor-prefixed API keys. The two-letter `xx_live_`/`xx_test_` arm names
197
+ // the prefixes that actually issue keys of that shape rather than accepting
198
+ // any two letters: unconstrained, it matched every snake_case value whose run
199
+ // after `_test_`/`_live_` was 8+ alphanumerics (`us_test_deployment`), and a
200
+ // false redaction is a real cost — the block then reports that a project
201
+ // declared something it did not.
202
+ /\b(?:(?:pk|sk|rk|ak)_(?:live|test)_[A-Za-z0-9]{8,}|github_pat_[A-Za-z0-9_]{20,}|gh[pousr]_[A-Za-z0-9]{16,}|xox[abprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{12,}|AIza[0-9A-Za-z_-]{20,}|sk-[A-Za-z0-9_-]{16,})\b/g,
195
203
  // A signed token: three base64url segments, header first.
196
204
  /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
197
205
  // An ARN carries an account number in its fifth field.
@@ -53,19 +53,50 @@ fi
53
53
  # So walk up for the config instead of assuming the declared directory holds it.
54
54
  # The repository root bounds the walk: a config file ABOVE it belongs to some
55
55
  # other project, and adopting it would be the same wrong answer wearing a
56
- # different hat. `.git` is a file in a worktree and a directory otherwise, hence
57
- # `-e`. The depth cap is a symlink-loop guard, not a policy.
56
+ # different hat.
57
+ #
58
+ # `git rev-parse --show-toplevel` — the same command the PROJECT_DIR fallback
59
+ # above already runs — is the authoritative answer to "where does this work tree
60
+ # end". `.git`-presence is only a proxy for it, and the two part company on any
61
+ # layout that relocates the git directory (GIT_DIR/GIT_WORK_TREE set
62
+ # explicitly): the proxy never fires, the walk leaves the repository, and the
63
+ # block renders a neighbouring project's config as though it were this one
64
+ # (CodySwannGT/lisa#3623). So bound on the toplevel, and keep the `.git`-presence
65
+ # proxy for the case the toplevel cannot answer — no repository, or no git.
66
+ # `.git` is a file in a worktree and a directory otherwise, hence `-e`.
67
+ #
68
+ # Both paths are compared in PHYSICAL form, because `--show-toplevel` resolves
69
+ # symlinks and the harness-declared directory does not. On any box whose
70
+ # temporary or home directory is a symlink (macOS `/var` -> `/private/var`) a
71
+ # textual comparison of the two never matches, and a bound that never matches is
72
+ # the unbounded walk again.
73
+ #
74
+ # There is no depth cap. The one that was here claimed to be a symlink-loop
75
+ # guard and could not be: `dirname` is string manipulation, resolves no symlinks,
76
+ # and strictly shortens. What it could do was stop a legitimate walk at 32 levels
77
+ # and report "No Lisa configuration found" for a repository that has one — the
78
+ # loudest possible wrong answer, in the one state this hook exists to make loud.
79
+ # Termination is guarded by the thing that actually threatens it: `dirname`
80
+ # reaching a fixed point (`/` -> `/`, and `.` -> `.` for a relative input).
58
81
  config_root() {
59
- dir="$1"
60
- depth=0
61
- while [ -n "${dir}" ] && [ "${dir}" != "/" ] && [ "${depth}" -lt 32 ]; do
82
+ local dir parent repo_root
83
+ # Physical form, so the comparison against the toplevel below is like-for-like.
84
+ dir="$(cd "$1" 2>/dev/null && pwd -P)" || dir="$1"
85
+ [ -n "${dir}" ] || dir="$1"
86
+ repo_root="$(git -C "${dir}" rev-parse --show-toplevel 2>/dev/null || true)"
87
+ while [ -n "${dir}" ] && [ "${dir}" != "/" ]; do
62
88
  if [ -f "${dir}/.lisa.config.json" ] || [ -f "${dir}/.lisa.config.local.json" ]; then
63
89
  printf '%s' "${dir}"
64
90
  return 0
65
91
  fi
66
- [ -e "${dir}/.git" ] && return 1
67
- dir="$(dirname "${dir}")"
68
- depth=$((depth + 1))
92
+ if [ -n "${repo_root}" ]; then
93
+ [ "${dir}" = "${repo_root}" ] && return 1
94
+ elif [ -e "${dir}/.git" ]; then
95
+ return 1
96
+ fi
97
+ parent="$(dirname "${dir}")"
98
+ [ "${parent}" = "${dir}" ] && return 1
99
+ dir="${parent}"
69
100
  done
70
101
  return 1
71
102
  }
@@ -0,0 +1,39 @@
1
+ # Control Reachability — A Prescribed Control Must Reach the Changed Path (load-bearing)
2
+
3
+ A work item may hand the implementer a falsifiable stopping rule: *"make the change; `<named existing test>` must go red. If it still passes, the fix did nothing."* That is good practice — it replaces eyeballing with an observation. It is also **only valid if that test's fixture exercises the path the change touches.**
4
+
5
+ When it does not, the test stays green **for a reason unrelated to the change**: execution never reached the changed code. The stopping rule then instructs the implementer to revert a correct fix, and the more faithfully the implementer follows the item, the more reliably it happens. Measured once in the portfolio: a pinned test's fixture omitted the field the fix read, execution fell through to a default branch, the test stayed green, and the fix had worked.
6
+
7
+ This is the specification-side sibling of `falsifiable-checks`, which governs a check *you authored*. Here the check is **prescribed by the work item** and the defect is in the prose, not the code.
8
+
9
+ ## Authoring obligation — checkable, not advisory
10
+
11
+ **A work item that names an existing test as a red-before-green control must say what makes that test reach the code the change touches.** Naming the code is not enough; the item names the *input*.
12
+
13
+ Declare it in `## Validation Journey`, one marker per named control:
14
+
15
+ ```text
16
+ [CONTROL: <test-identifier> | reaches: <input-or-field>]
17
+ ```
18
+
19
+ - `<test-identifier>` — non-empty: the test file path, the test name, or both.
20
+ - `reaches:` — the literal key, then the **input, field, fixture key, argument, or state** that carries execution into the changed code. `reaches: the fix` and `reaches: the changed code` restate the assertion and do not satisfy it.
21
+
22
+ Enforced by gate **S20** in `lisa-jira-validate-ticket`, `lisa-github-validate-issue`, and `lisa-linear-validate-issue`; rendered by `lisa-jira-write-ticket`, `lisa-github-write-issue`, and `lisa-linear-write-issue`; read at claim time by `lisa-ticket-triage` and `lisa-implement`.
23
+
24
+ **An item that prescribes no existing-test control is exempt.** Introducing a *new* test carries no reachability obligation — the gate is `N/A` and the item is never reported as incomplete on this basis.
25
+
26
+ ## Implementer obligation — a green control is two facts, not one
27
+
28
+ `lisa-tdd-implementation` and `lisa-reproduce-bug` bind this arm: **when a prescribed control does not change state as the item predicted, stop. Establish which cause holds before acting on the stopping rule.**
29
+
30
+ | Cause | Correct action |
31
+ |---|---|
32
+ | The change had no effect on behaviour | Revisit the change |
33
+ | The fixture never reaches the changed code path | Fix or extend the control — do **not** revert |
34
+
35
+ They demand opposite actions, and the cheaper reading ("the fix did nothing") wins by default when nothing forces the distinction. So force it: **prove reachability by execution, never by reading the fixture.** Put a temporary throw, log line, or counter in the changed code and run that one test; or run coverage scoped to it and read whether the changed lines were hit. Reasoning that the fixture *looks like* it reaches the path is the same move `falsifiable-checks` rejects as "mentally reverting".
36
+
37
+ **Never revert on an unexplained green.** An unexplained control is a blocked observation, not a verdict — and a reachable-but-still-green control is the only form of it that licenses revisiting the change.
38
+
39
+ Full prose, the worked instance, and the reporting template: [reference/control-reachability.md](../reference/control-reachability.md).
@@ -6,6 +6,8 @@ A passing test, a clean lint run, a zero-hit sweep, a green ratchet: each is evi
6
6
 
7
7
  This is the instrument-validity counterpart to the `verification` rule (which proves the *software* behaves) and `empirical-inquiry` (which proves a *fact*). All three reject "it looks correct." This one rejects "the check said so."
8
8
 
9
+ When the check was **prescribed by a work item** rather than authored here — "that named test must go red, or the fix did nothing" — `control-reachability` governs instead, and it inverts the cost: a control whose fixture never reaches the changed path stays green for an unrelated reason, and the item's own stopping rule then reverts a correct fix.
10
+
9
11
  ## The five ways a check silently measures nothing
10
12
 
11
13
  Each has been observed in real runs; each reported success while asserting nothing:
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Shared core (every leaf, enforced by `*-validate-*` S1–S16)
6
6
 
7
- Three-audience description · Gherkin acceptance criteria (except Spike) · Out of Scope · single-repo scope · relationship search · typed evidence manifest on runtime changes · Source Requirement traceability when PRD-sourced · provable read access to every named external surface.
7
+ Three-audience description · Gherkin acceptance criteria (except Spike) · Out of Scope · single-repo scope · relationship search · typed evidence manifest on runtime changes · Source Requirement traceability when PRD-sourced · provable read access to every named external surface · declared reachability for any **existing** test the item pins as a red-before-green control (`control-reachability`, gate S20 — `N/A` when the item introduces a new test instead).
8
8
 
9
9
  ## Per-type additions
10
10