@lannguyensi/harness 0.41.0 → 0.43.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 (137) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +9 -0
  3. package/dist/cli/adopt/derive.d.ts +15 -0
  4. package/dist/cli/adopt/derive.js +34 -7
  5. package/dist/cli/adopt/derive.js.map +1 -1
  6. package/dist/cli/adopt/index.d.ts +30 -0
  7. package/dist/cli/adopt/index.js +23 -3
  8. package/dist/cli/adopt/index.js.map +1 -1
  9. package/dist/cli/apply/generate-settings.d.ts +14 -1
  10. package/dist/cli/apply/generate-settings.js +7 -3
  11. package/dist/cli/apply/generate-settings.js.map +1 -1
  12. package/dist/cli/approve/branch-protection.js +1 -1
  13. package/dist/cli/approve/branch-protection.js.map +1 -1
  14. package/dist/cli/approve/stdin-report.js +2 -0
  15. package/dist/cli/approve/stdin-report.js.map +1 -1
  16. package/dist/cli/doctor/claude-mcp.d.ts +50 -0
  17. package/dist/cli/doctor/claude-mcp.js +170 -0
  18. package/dist/cli/doctor/claude-mcp.js.map +1 -0
  19. package/dist/cli/doctor/format.js +37 -0
  20. package/dist/cli/doctor/format.js.map +1 -1
  21. package/dist/cli/doctor/index.d.ts +10 -0
  22. package/dist/cli/doctor/index.js +24 -0
  23. package/dist/cli/doctor/index.js.map +1 -1
  24. package/dist/cli/doctor/npm-bin-path.js +42 -0
  25. package/dist/cli/doctor/npm-bin-path.js.map +1 -1
  26. package/dist/cli/doctor/types.d.ts +11 -0
  27. package/dist/cli/doctor/types.js.map +1 -1
  28. package/dist/cli/dry-run.js +13 -1
  29. package/dist/cli/dry-run.js.map +1 -1
  30. package/dist/cli/index.js +253 -5
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/cli/init/agent-tasks-auth.js +32 -0
  33. package/dist/cli/init/agent-tasks-auth.js.map +1 -1
  34. package/dist/cli/init/composer.js +9 -15
  35. package/dist/cli/init/composer.js.map +1 -1
  36. package/dist/cli/init/dependencies.js +21 -0
  37. package/dist/cli/init/dependencies.js.map +1 -1
  38. package/dist/cli/init/detect.d.ts +20 -3
  39. package/dist/cli/init/detect.js +34 -36
  40. package/dist/cli/init/detect.js.map +1 -1
  41. package/dist/cli/init/interactive.d.ts +33 -0
  42. package/dist/cli/init/interactive.js +399 -5
  43. package/dist/cli/init/interactive.js.map +1 -1
  44. package/dist/cli/init/profiles.d.ts +1 -1
  45. package/dist/cli/init/profiles.js +2 -2
  46. package/dist/cli/init/templates.d.ts +1 -1
  47. package/dist/cli/init/templates.js +111 -32
  48. package/dist/cli/init/templates.js.map +1 -1
  49. package/dist/cli/main.js +10 -0
  50. package/dist/cli/main.js.map +1 -1
  51. package/dist/cli/pack/hook-branch-protection.js +1 -1
  52. package/dist/cli/pack/hook-branch-protection.js.map +1 -1
  53. package/dist/cli/pack/hook-post-merge-gate-record.d.ts +30 -0
  54. package/dist/cli/pack/hook-post-merge-gate-record.js +153 -0
  55. package/dist/cli/pack/hook-post-merge-gate-record.js.map +1 -0
  56. package/dist/cli/pack/hook-post-merge-gate.d.ts +28 -0
  57. package/dist/cli/pack/hook-post-merge-gate.js +244 -0
  58. package/dist/cli/pack/hook-post-merge-gate.js.map +1 -0
  59. package/dist/cli/pack/hook-solution-acceptance-writeguard.js +149 -3
  60. package/dist/cli/pack/hook-solution-acceptance-writeguard.js.map +1 -1
  61. package/dist/cli/pack/hook-solution-acceptance.js +1 -1
  62. package/dist/cli/pack/hook-solution-acceptance.js.map +1 -1
  63. package/dist/cli/pack/understanding-report-schema-hint.js +1 -1
  64. package/dist/cli/pack/understanding-report-schema-hint.js.map +1 -1
  65. package/dist/cli/policy/intercept.js +77 -10
  66. package/dist/cli/policy/intercept.js.map +1 -1
  67. package/dist/cli/record/index.d.ts +64 -0
  68. package/dist/cli/record/index.js +211 -0
  69. package/dist/cli/record/index.js.map +1 -0
  70. package/dist/cli/session-start/index.d.ts +7 -0
  71. package/dist/cli/session-start/index.js +133 -38
  72. package/dist/cli/session-start/index.js.map +1 -1
  73. package/dist/cli/session-start/toolchain-parity.d.ts +149 -0
  74. package/dist/cli/session-start/toolchain-parity.js +618 -0
  75. package/dist/cli/session-start/toolchain-parity.js.map +1 -0
  76. package/dist/cli/uninstall/index.d.ts +73 -1
  77. package/dist/cli/uninstall/index.js +134 -2
  78. package/dist/cli/uninstall/index.js.map +1 -1
  79. package/dist/cli/validate/checks.js +2 -2
  80. package/dist/cli/validate/checks.js.map +1 -1
  81. package/dist/io/claude-mcp.d.ts +258 -0
  82. package/dist/io/claude-mcp.js +544 -0
  83. package/dist/io/claude-mcp.js.map +1 -0
  84. package/dist/policies/ledger-client.d.ts +34 -1
  85. package/dist/policies/ledger-client.js +21 -16
  86. package/dist/policies/ledger-client.js.map +1 -1
  87. package/dist/policy-packs/builtin/branch-protection-runtime.d.ts +1 -1
  88. package/dist/policy-packs/builtin/branch-protection-runtime.js +1 -1
  89. package/dist/policy-packs/builtin/branch-protection.js +2 -2
  90. package/dist/policy-packs/builtin/branch-protection.js.map +1 -1
  91. package/dist/policy-packs/builtin/post-merge-gate-runtime.d.ts +195 -0
  92. package/dist/policy-packs/builtin/post-merge-gate-runtime.js +290 -0
  93. package/dist/policy-packs/builtin/post-merge-gate-runtime.js.map +1 -0
  94. package/dist/policy-packs/builtin/post-merge-gate.d.ts +56 -0
  95. package/dist/policy-packs/builtin/post-merge-gate.js +291 -0
  96. package/dist/policy-packs/builtin/post-merge-gate.js.map +1 -0
  97. package/dist/policy-packs/builtin/solution-acceptance-runtime.js +29 -14
  98. package/dist/policy-packs/builtin/solution-acceptance-runtime.js.map +1 -1
  99. package/dist/policy-packs/builtin/solution-acceptance.js +1 -1
  100. package/dist/policy-packs/registry.d.ts +1 -1
  101. package/dist/policy-packs/registry.js +12 -0
  102. package/dist/policy-packs/registry.js.map +1 -1
  103. package/dist/runtime/bash-match-facts.d.ts +58 -0
  104. package/dist/runtime/bash-match-facts.js +194 -0
  105. package/dist/runtime/bash-match-facts.js.map +1 -0
  106. package/dist/runtime/bash-match-registry.d.ts +75 -0
  107. package/dist/runtime/bash-match-registry.js +359 -0
  108. package/dist/runtime/bash-match-registry.js.map +1 -0
  109. package/dist/runtime/bash-prefix-parse.js +23 -0
  110. package/dist/runtime/bash-prefix-parse.js.map +1 -1
  111. package/dist/runtime/command-normalize.d.ts +92 -0
  112. package/dist/runtime/command-normalize.js +1182 -0
  113. package/dist/runtime/command-normalize.js.map +1 -0
  114. package/dist/runtime/git-context.d.ts +50 -0
  115. package/dist/runtime/git-context.js +108 -1
  116. package/dist/runtime/git-context.js.map +1 -1
  117. package/dist/runtime/hermetic-spawn-guard.d.ts +25 -0
  118. package/dist/runtime/hermetic-spawn-guard.js +136 -0
  119. package/dist/runtime/hermetic-spawn-guard.js.map +1 -0
  120. package/dist/runtime/intercept.d.ts +39 -1
  121. package/dist/runtime/intercept.js +33 -4
  122. package/dist/runtime/intercept.js.map +1 -1
  123. package/dist/runtime/ledger-writer.d.ts +27 -0
  124. package/dist/runtime/ledger-writer.js +46 -0
  125. package/dist/runtime/ledger-writer.js.map +1 -0
  126. package/dist/runtime/read-only-bash.d.ts +43 -0
  127. package/dist/runtime/read-only-bash.js +133 -3
  128. package/dist/runtime/read-only-bash.js.map +1 -1
  129. package/dist/schema/index.d.ts +41 -0
  130. package/dist/schema/index.js +5 -0
  131. package/dist/schema/index.js.map +1 -1
  132. package/dist/schema/policies.js +1 -1
  133. package/dist/schema/policies.js.map +1 -1
  134. package/dist/schema/toolchain-parity.d.ts +18 -0
  135. package/dist/schema/toolchain-parity.js +35 -0
  136. package/dist/schema/toolchain-parity.js.map +1 -0
  137. package/package.json +5 -2
@@ -0,0 +1,359 @@
1
+ // Registration point for every engine-side constant set that mirrors
2
+ // FULL_TEMPLATE's `bash_match` facts (task `074acf5d`, run
3
+ // `2026-07-28-manifest-facts-drift-guard`).
4
+ //
5
+ // WHY A REGISTRY, NOT ONE HARD-CODED COMPARISON: the guard this replaces
6
+ // (`tests/runtime/bash-match-head-token-drift.test.ts`, pre-migration)
7
+ // checked exactly one named set (`NON_GIT_HEAD_TOKENS`) against the
8
+ // manifest. That guard could not see run `dbc6d303`'s `INERT_CONSUMER_HEADS`
9
+ // (a NEW module, CRITICAL: contained `harness`, the kill-switch's own
10
+ // gated head token) at all — it simply never looked at any set but the
11
+ // one it was told about. `REGISTERED_HEAD_TOKEN_SETS` below is the list
12
+ // every such set must join; `checkRegisteredSets` walks the WHOLE list
13
+ // against the live facts, so a set registered tomorrow is checked
14
+ // automatically, and (paired with the unregistered-set scan in
15
+ // `tests/runtime/bash-match-head-token-drift.test.ts`) a set that is
16
+ // NOT registered is itself a finding.
17
+ //
18
+ // TWO INTENTS (minimum required — see the two historical incidents this
19
+ // run exists to close):
20
+ // - "covers-gated-head-tokens": the set exists so the engine recognises
21
+ // these head tokens (`NON_GIT_HEAD_TOKENS`'s shape). Checked for
22
+ // COMPLETENESS: every head token the manifest actually gates (barring
23
+ // a consciously `documented-uncovered` token) must be covered by AT
24
+ // LEAST ONE registered set with this intent — the run `432db3d3` HIGH
25
+ // (a covers-set spanning only 4 of 8 gated tokens) as a standing
26
+ // regression class.
27
+ // - "must-not-contain-gated-head-token": the set exists for some OTHER
28
+ // purpose entirely and must NEVER contain a token the manifest itself
29
+ // gates — the run `dbc6d303` CRITICAL's shape (an "inert consumer"
30
+ // allowlist that accidentally absorbed the kill-switch's own gated
31
+ // head token). Checked by testing every LIVE gated head token against
32
+ // every such registered set's `has()`.
33
+ //
34
+ // FIX ROUND 2 (task `074acf5d`, second fix round, findings S1/S2/S4 —
35
+ // see `.ai/runs/2026-07-28-manifest-facts-drift-guard/03-decisions.md`
36
+ // D-002/D-004): review round 2 MEASURED that the round-1 guard's central
37
+ // defence — a registration's self-declared `intent` — was still
38
+ // escapable two ways, and that the guard reproduced its own defect class
39
+ // inside itself. Three changes close what can be made STRUCTURALLY
40
+ // impossible (D-002/D-003 deliberately draws the line here, not at every
41
+ // enumerable escape — see the decisions file):
42
+ // (S1) `RegisteredHeadTokenSet` is now a DISCRIMINATED UNION on `intent`:
43
+ // the "covers-gated-head-tokens" arm's `members` is NON-OPTIONAL.
44
+ // Omitting `members` on a covers registration — the round-1 escape,
45
+ // "declare no members and check (c) below never runs" — is now a
46
+ // COMPILE ERROR, not a test failure. The "must-not-contain" arm keeps
47
+ // `members` optional (no purity semantics apply to it).
48
+ // (S2) check (d), COVERS-REDUNDANCY, below: NARROWS — does not close —
49
+ // the escape where a laundered set's declared `members` are all
50
+ // HONESTLY gated (so check (c) is satisfied) but the set's true
51
+ // purpose is something else. It works only for a gated token that some
52
+ // covers-set legitimately covers, and is STRUCTURALLY SILENT for the
53
+ // `documented-uncovered` subset. MEASURED (review round 3): a
54
+ // registration declaring `members: ["tee"]` — or any subset of
55
+ // `env`/`unset`/`tee`/`cp`, four of the eight live gated head tokens —
56
+ // passes checks (a) through (e) with an entirely honest, live-coupled
57
+ // `has()` and `members`, end to end on the real tree. That is the
58
+ // `dbc6d303` shape (an inert allowlist absorbing a gated head token)
59
+ // and this registry does not see it. Redesign filed as a follow-up;
60
+ // see the LIMITS paragraph at the end of this header.
61
+ // (S4) check (e), COVERS-PREDICATE-PURITY, below: closes the escape
62
+ // where a registration's `has()` PREDICATE accepts more than its
63
+ // declared `members` claim (measured: widening `GIT_TOKEN_RE` to also
64
+ // match "docker" while leaving `members: ["git"]` untouched left the
65
+ // full suite green) — this guard reproducing, inside itself, the exact
66
+ // "engine-side constant mirrors manifest content with no coupling"
67
+ // defect class it exists to eliminate elsewhere.
68
+ // Each violation now carries a `check` field (see `RegistrationViolation`)
69
+ // naming exactly which of the five checks fired, so a fixture test can
70
+ // assert on ONE check's output without an unrelated check's finding on the
71
+ // same (setId, token) pair silently changing what the array contains.
72
+ //
73
+ // COVERS-PURITY (fix round, F1+F2): a THIRD check, over a "covers"
74
+ // registration's OWN declared `members` (see `RegisteredHeadTokenSet.members`
75
+ // below), closes two escapes with one rule — every declared member must
76
+ // itself be a live gated head token:
77
+ // (F1) the REVERSE direction, ENGINE->MANIFEST: the pre-migration guard
78
+ // asserted this (every member of `NON_GIT_HEAD_TOKENS` must be a token
79
+ // some shipped policy gates) and the migration to this registry
80
+ // silently dropped it — completeness-checking (b) only ever walks
81
+ // `facts.gatedHeadTokens` outward to registered sets, never a
82
+ // registered set's own members inward to the facts. An engine set
83
+ // gaining an ungated member (e.g. "docker" added to
84
+ // `NON_GIT_HEAD_TOKENS`) is real DEAD CODE at best and a sign the
85
+ // manifest and the engine have silently diverged at worst; either way
86
+ // it is a regression this registry exists to catch.
87
+ // (F2) MIS-DECLARED INTENT: a `must-not-contain-gated-head-token` set
88
+ // relabeled `covers-gated-head-tokens` to dodge check (a) — cheap,
89
+ // because `intent` is self-declared and was, before this check,
90
+ // cross-checked against nothing. A set that exists for any purpose
91
+ // OTHER than recognising gated head tokens (an inert-consumer
92
+ // allowlist, say) necessarily contains members that are not
93
+ // themselves gated head tokens, so it fails THIS check the moment it
94
+ // declares `members` — the decision rule is: pick the intent that
95
+ // describes the set's PURPOSE; a set whose purpose is anything other
96
+ // than recognising gated head tokens is
97
+ // "must-not-contain-gated-head-token", never "covers-gated-head-tokens".
98
+ //
99
+ // AXIS SCOPE (F6, fix round): this registry models the HEAD-TOKEN axis
100
+ // only — which literal head token a `bash_match` anchors on. A shipped
101
+ // policy's `bash_match` can equally anchor on a VERB with no head-token
102
+ // significance of its own (`pause` in `deny-kill-switch-bypass`), checked
103
+ // today only by `HARNESS_READ_ONLY_SUBS` in `read-only-bash.ts`, a set this
104
+ // registry does not see and does not check. The VERB axis is explicitly
105
+ // OUT OF SCOPE for this run: the exact same defect shape (an engine-side
106
+ // allowlist silently absorbing a gated token) is unmodellable here today,
107
+ // one axis over. Do not assume verb coverage exists because head-token
108
+ // coverage does.
109
+ //
110
+ // WHY src/runtime/, NOT tests/ (F8, fix round): see `bash-match-facts.ts`'s
111
+ // module header — the same reasoning applies here.
112
+ //
113
+ // LIMITS — READ THIS BEFORE TRUSTING A REGISTRATION. Three independent
114
+ // adversarial review passes each found a way to register a set that this
115
+ // registry accepts but should not. Two rounds of added checks narrowed the
116
+ // family without closing it, so the checks were frozen here deliberately
117
+ // and the registration SURFACE is being redesigned instead (follow-up
118
+ // task; the shape under consideration is that a registration hands the
119
+ // registry the constant itself rather than a self-description, so a stale
120
+ // or lying mirror cannot be expressed at all). What this registry does NOT
121
+ // catch today, each measured end to end, not theorised:
122
+ // 1. A covers registration whose declared `members` are all
123
+ // `documented-uncovered` gated tokens (`env`, `unset`, `tee`, `cp` —
124
+ // four of eight). Passes every check with an honest, live-coupled
125
+ // registration. This is the `dbc6d303` CRITICAL shape.
126
+ // 2. A `has()` predicate that does not reflect the constant its
127
+ // `id`/`module` names: a stale hand-copied snapshot, or `() => false`.
128
+ // Nothing couples the two. The `must-not-contain` arm has no
129
+ // predicate check at all, and that is the arm the CRITICAL shape
130
+ // lives in.
131
+ // 3. `members` declaring coverage the predicate rejects (the two are
132
+ // each walked in one direction, never against each other), and an
133
+ // empty `members: []`, which typechecks.
134
+ // 4. A registration naming a `module`/`id` that no exported constant
135
+ // matches.
136
+ // What it DOES catch is the bar it was built for and still meets: both
137
+ // historical incidents (`432db3d3`'s covers-completeness gap and
138
+ // `dbc6d303`'s `harness` in an inert allowlist), plus an unregistered set
139
+ // in the runtime layer. Treat it as a tripwire for the accidental case,
140
+ // never as a boundary against a determined one.
141
+ //
142
+ // TODAY'S REAL REGISTRATIONS are both "covers" sets — `GIT_TOKEN_RE` and
143
+ // `NON_GIT_HEAD_TOKENS`, both from `command-normalize.ts`, the two sets
144
+ // the pre-migration guard already knew about. No real
145
+ // "must-not-contain-gated-head-token" consumer exists in this tree today
146
+ // (run `dbc6d303`'s own module is explicit non-goal / future work for
147
+ // THIS run) — the intent is fully implemented and covered by the fixture
148
+ // regression tests in `tests/runtime/bash-match-registry.test.ts`
149
+ // (reproducing the CRITICAL's exact shape) and by a live mutation probe
150
+ // against this file's own `REGISTERED_HEAD_TOKEN_SETS` array, not against
151
+ // a second production consumer: temporarily registering a fixture
152
+ // (`intent: "must-not-contain-gated-head-token"`, `has` containing
153
+ // "harness") reddens exactly "every registered engine-side set is clean
154
+ // against the live facts ..." and the F5 defaults test in
155
+ // `tests/runtime/bash-match-head-token-drift.test.ts`, with the violation
156
+ // naming the fixture's own `setId` and the offending token — the probe was
157
+ // reverted immediately after.
158
+ //
159
+ // `has()` rather than an enumerable `ReadonlySet<string>` uniformly for
160
+ // checks (a) and (b): the live gated-head-token vocabulary is small and
161
+ // fully known from `BashMatchFacts.gatedHeadTokens` (never more than ~8
162
+ // entries), so both checks only ever need to ask "does this registered set
163
+ // answer YES for token X", which a `RegExp.test` (`GIT_TOKEN_RE`) and a
164
+ // `Set.has` (`NON_GIT_HEAD_TOKENS`) can equally answer. Check (c)
165
+ // (COVERS-PURITY, above) is different in kind — it asks "does this
166
+ // registered set contain something the facts DON'T know about", which no
167
+ // predicate over the ~8 known tokens can answer; that is why `members` is
168
+ // a separate, enumerable field rather than folded into `has()` — NON-
169
+ // OPTIONAL on the "covers-gated-head-tokens" arm as of fix round 2 (S1;
170
+ // see `CoversHeadTokenSet`'s doc), optional on "must-not-contain", which
171
+ // this field's checks never apply to.
172
+ import { GIT_TOKEN_RE, NON_GIT_HEAD_TOKENS } from "./command-normalize.js";
173
+ import { DOCUMENTED_UNCOVERED_HEAD_TOKENS, isHeadTokenGated } from "./bash-match-facts.js";
174
+ export const REGISTERED_HEAD_TOKEN_SETS = [
175
+ {
176
+ id: "GIT_TOKEN_RE",
177
+ module: "src/runtime/command-normalize.ts",
178
+ intent: "covers-gated-head-tokens",
179
+ has: (token) => GIT_TOKEN_RE.test(token),
180
+ members: ["git"],
181
+ description: "canonicalizeSegment's git-basename recognizer (matches `git`, and any `\\S*/git` path-qualified spelling, by basename)",
182
+ },
183
+ {
184
+ id: "NON_GIT_HEAD_TOKENS",
185
+ module: "src/runtime/command-normalize.ts",
186
+ intent: "covers-gated-head-tokens",
187
+ has: (token) => NON_GIT_HEAD_TOKENS.has(token),
188
+ members: [...NON_GIT_HEAD_TOKENS],
189
+ description: "canonicalizeSegment's closed non-git head-token set (today: gh, npm, harness)",
190
+ },
191
+ ];
192
+ /**
193
+ * Fixed, deliberately small vocabulary of plausible NON-gated head tokens
194
+ * (fix round 2, S4/D-004) — commands a reasonable engine-side set might
195
+ * plausibly mirror or accidentally absorb, but that no shipped
196
+ * `bash_match` policy gates today. Combined with the live
197
+ * `facts.gatedHeadTokens` (every token that IS gated, verified fresh —
198
+ * see `bash-match-facts.ts`) this gives check (e) below a concrete probe
199
+ * domain to test every registered "covers" predicate against, instead of
200
+ * trusting a registration's declared `members` to be an honest reflection
201
+ * of what its `has()` actually accepts.
202
+ */
203
+ const PLAUSIBLE_NON_GATED_HEAD_TOKENS = [
204
+ "docker",
205
+ "kubectl",
206
+ "ls",
207
+ "cat",
208
+ "node",
209
+ "python",
210
+ "make",
211
+ "ssh",
212
+ ];
213
+ /**
214
+ * Check every registered set against the live facts. Returns an EMPTY
215
+ * array when clean. `registrations` and `documentedUncovered` default to
216
+ * the real, shipped values (F5 fix-round correction: the
217
+ * `documentedUncovered` default previously silently fell back to an empty
218
+ * set, which raised 4 FALSE violations — tee, cp, env, unset — when called
219
+ * with only `facts`; pinned by
220
+ * `tests/runtime/bash-match-head-token-drift.test.ts`'s
221
+ * "checkRegisteredSets(liveFacts())" defaults test) but accept fixtures —
222
+ * see `tests/runtime/bash-match-registry.test.ts`'s regression tests for
223
+ * the historical incidents, which pass deliberately-broken fixtures to
224
+ * prove this function catches their exact shape.
225
+ */
226
+ export function checkRegisteredSets(facts, registrations = REGISTERED_HEAD_TOKEN_SETS, documentedUncovered = DOCUMENTED_UNCOVERED_HEAD_TOKENS) {
227
+ const violations = [];
228
+ // (a) must-not-contain: no registered set with this intent may answer
229
+ // `true` for ANY live gated head token — the dbc6d303 CRITICAL shape.
230
+ for (const reg of registrations) {
231
+ if (reg.intent !== "must-not-contain-gated-head-token")
232
+ continue;
233
+ for (const token of facts.gatedHeadTokens) {
234
+ if (reg.has(token)) {
235
+ violations.push({
236
+ setId: reg.id,
237
+ intent: reg.intent,
238
+ token,
239
+ check: "must-not-contain",
240
+ reason: `"${reg.id}" (${reg.module} — ${reg.description}) declares intent "must-not-contain-gated-head-token" but contains "${token}", which IS gated by a shipped bash_match policy`,
241
+ });
242
+ }
243
+ }
244
+ }
245
+ // (b) covers-completeness: every gated head token NOT consciously
246
+ // documented as uncovered must be answered `true` by at least one
247
+ // registered "covers" set — the 432db3d3 HIGH shape.
248
+ for (const token of facts.gatedHeadTokens) {
249
+ if (documentedUncovered.has(token))
250
+ continue;
251
+ const covered = registrations.some((reg) => reg.intent === "covers-gated-head-tokens" && reg.has(token));
252
+ if (!covered) {
253
+ const cls = facts.classify(token);
254
+ violations.push({
255
+ setId: "(none)",
256
+ intent: "covers-gated-head-tokens",
257
+ token,
258
+ check: "covers-completeness",
259
+ reason: `gated head token "${token}" (class: ${cls ?? "unknown"}) is not covered by ANY registered "covers-gated-head-tokens" set, and is not in the documented-uncovered list either`,
260
+ });
261
+ }
262
+ }
263
+ // (c) covers-purity (fix round, F1+F2): every member a "covers" set
264
+ // DECLARES (see RegisteredHeadTokenSet.members) must itself be a live
265
+ // gated head token. Closes two escapes with one check: (1) the reverse
266
+ // ENGINE->MANIFEST direction the pre-migration guard checked and the
267
+ // migration dropped (a covers set gaining a member — e.g. "docker" —
268
+ // that no shipped policy gates at all); (2) the mis-declared-intent
269
+ // laundering escape, where a `must-not-contain-gated-head-token` set is
270
+ // relabeled `covers-gated-head-tokens` to dodge check (a) — a set that
271
+ // exists for any purpose OTHER than recognising gated head tokens
272
+ // necessarily contains a member that fails this check. `members` is
273
+ // non-optional on this arm as of fix round 2 (S1) — every registration
274
+ // that reaches here has one to walk.
275
+ for (const reg of registrations) {
276
+ if (reg.intent !== "covers-gated-head-tokens")
277
+ continue;
278
+ for (const member of reg.members) {
279
+ if (!isHeadTokenGated(facts, member)) {
280
+ violations.push({
281
+ setId: reg.id,
282
+ intent: reg.intent,
283
+ token: member,
284
+ check: "covers-purity",
285
+ reason: `"${reg.id}" (${reg.module} — ${reg.description}) declares intent "covers-gated-head-tokens" and lists "${member}" among its members, but "${member}" is not a live gated head token (no shipped bash_match policy gates it) — pick the intent that describes the set's PURPOSE: a set that exists for any purpose OTHER than recognising gated head tokens is "must-not-contain-gated-head-token", never "covers-gated-head-tokens"`,
286
+ });
287
+ }
288
+ }
289
+ }
290
+ // (d) covers-redundancy (fix round 2, S2): a gated head token answered
291
+ // `true` by MORE THAN ONE registered "covers-gated-head-tokens" set is a
292
+ // violation. Closes the escape check (c) cannot: a laundered set (its
293
+ // TRUE purpose is something other than recognising gated head tokens,
294
+ // relabeled "covers-gated-head-tokens" to dodge check (a)) whose
295
+ // declared `members` are HONESTLY all gated — e.g. `new
296
+ // Set(["harness"])` or `new Set(["harness", "gh"])` — passes check (c)
297
+ // cleanly (every declared member IS a live gated head token). The tell
298
+ // is redundancy: a gated head token legitimately has exactly ONE
299
+ // registered "covers" set responsible for it.
300
+ //
301
+ // SCOPE OF THIS CHECK, measured, not assumed (review round 3): the tell
302
+ // only exists for a token some covers-set ALREADY covers. For the four
303
+ // `documented-uncovered` tokens (`env`, `unset`, `tee`, `cp`) no
304
+ // covers-set covers them by definition, so `coveringSets.length` is 1
305
+ // and this check is structurally silent — while check (b) skips them and
306
+ // checks (c)/(e) pass because they ARE gated. A laundered set declaring
307
+ // only those tokens therefore passes everything. Do not read this check
308
+ // as a completeness proof; an earlier version of this comment did, and
309
+ // it was false for half the live vocabulary.
310
+ for (const token of facts.gatedHeadTokens) {
311
+ const coveringSets = registrations.filter((reg) => reg.intent === "covers-gated-head-tokens" && reg.has(token));
312
+ if (coveringSets.length > 1) {
313
+ for (const reg of coveringSets) {
314
+ const others = coveringSets.filter((r) => r.id !== reg.id).map((r) => r.id);
315
+ violations.push({
316
+ setId: reg.id,
317
+ intent: reg.intent,
318
+ token,
319
+ check: "covers-redundancy",
320
+ reason: `"${reg.id}" (${reg.module} — ${reg.description}) declares intent "covers-gated-head-tokens" and answers true for "${token}", but "${token}" is ALSO covered by ${others.length} other registered covers-set(s) (${others.join(", ")}) — a gated head token must be covered by exactly one registered covers-set; redundant coverage is the signature of an intent-laundered set whose declared members happen to already be genuinely gated elsewhere`,
321
+ });
322
+ }
323
+ }
324
+ }
325
+ // (e) covers-predicate-purity (fix round 2, S4/D-004): unlike check (c),
326
+ // which only inspects a registration's DECLARED `members`, this probes
327
+ // the registration's ACTUAL `has()` predicate directly against a fixed
328
+ // vocabulary — every live gated head token plus
329
+ // `PLAUSIBLE_NON_GATED_HEAD_TOKENS` — and demands `has(token)` answer
330
+ // `true` ONLY for tokens that ARE live gated head tokens. Closes the
331
+ // exact defect class this run exists to eliminate, reproduced INSIDE
332
+ // this guard: a predicate-based registration (`GIT_TOKEN_RE.test`)
333
+ // paired with a hand-written `members` list that check (c) verifies but
334
+ // the predicate ITSELF is never re-checked against — measured (D-004):
335
+ // widening `GIT_TOKEN_RE` to also match "docker" while leaving
336
+ // `members: ["git"]` untouched left the full suite green before this
337
+ // check existed.
338
+ const probeVocabulary = new Set([
339
+ ...facts.gatedHeadTokens,
340
+ ...PLAUSIBLE_NON_GATED_HEAD_TOKENS,
341
+ ]);
342
+ for (const reg of registrations) {
343
+ if (reg.intent !== "covers-gated-head-tokens")
344
+ continue;
345
+ for (const token of probeVocabulary) {
346
+ if (reg.has(token) && !isHeadTokenGated(facts, token)) {
347
+ violations.push({
348
+ setId: reg.id,
349
+ intent: reg.intent,
350
+ token,
351
+ check: "covers-predicate-purity",
352
+ reason: `"${reg.id}" (${reg.module} — ${reg.description}) declares intent "covers-gated-head-tokens" and its has() predicate answers true for "${token}", but "${token}" is not a live gated head token — the predicate accepts more than its declared members claim, reproducing this run's own defect class (an engine-side predicate mirroring manifest content with no coupling) inside the guard itself`,
353
+ });
354
+ }
355
+ }
356
+ }
357
+ return violations;
358
+ }
359
+ //# sourceMappingURL=bash-match-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-match-registry.js","sourceRoot":"","sources":["../../src/runtime/bash-match-registry.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,16 @@
30
30
  // after `cd <path>`) the parser falls through cleanly: the malformed
31
31
  // prefix is not consumed, the resolver-side fallback to process env /
32
32
  // hook cwd holds. There are no thrown errors from this module.
33
+ //
34
+ // MEASUREMENT RULE (task 47297478): any claim about this parser's
35
+ // CD-TARGET extraction versus another build (lost or gained `cdTarget`
36
+ // values) must come from scripts/measure-bash-prefix-parse.mjs, the
37
+ // per-arm-gated corpus with real bash as referee. Three consecutive
38
+ // ad-hoc corpora in the b093911d run reported "0 lost" while being
39
+ // structurally unable to report a loss; that tool's self-test rebuilds
40
+ // exactly that failure and demands the gate catch it. The tool does NOT
41
+ // measure `inlineEnv` extraction — claims about inline-env behavior
42
+ // have no instrument yet and need their own measurement.
33
43
  /**
34
44
  * Parse leading inline-env and `cd <path>` idioms from a Bash command
35
45
  * string. Returns an empty `inlineEnv` and `cdTarget:null` when neither
@@ -77,6 +87,19 @@ function skipWs(s, i) {
77
87
  * On a syntactically broken token (e.g. unterminated quote) the broken
78
88
  * token is NOT consumed and the function returns the cursor at the
79
89
  * start of that token, preserving the rest of the command for fallback.
90
+ *
91
+ * QUOTE-MODEL DIVERGENCE, recorded so the next change here starts from
92
+ * the known state instead of rediscovering it (task 13e55484, review
93
+ * round 1): `command-normalize.ts`'s `consumeAssignment` is a SECOND
94
+ * quote model for the same leading-`VAR=value` construction, with
95
+ * different deliberate coverage — it handles backslash escapes (outside
96
+ * single quotes) and chained quote runs (`'a b'"c d"`), which this
97
+ * function does not, while this function extracts the VALUE (which the
98
+ * normaliser never needs). Neither model handles ANSI-C `$'...'`
99
+ * escapes; the normaliser side carries a one-directional guard so that
100
+ * divergence can only fall back to its pre-continuation behaviour,
101
+ * never swallow a gated head token. This function's own escape gaps are
102
+ * task `b093911d`.
80
103
  */
81
104
  function consumeInlineEnv(s, start, into) {
82
105
  let i = skipWs(s, start);
@@ -1 +1 @@
1
- {"version":3,"file":"bash-prefix-parse.js","sourceRoot":"","sources":["../../src/runtime/bash-prefix-parse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"bash-prefix-parse.js","sourceRoot":"","sources":["../../src/runtime/bash-prefix-parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ export interface NormalizedCommand {
2
+ /**
3
+ * The command with wrapper prefixes peeled and git invocations
4
+ * canonicalised to `git <subcommand>`. Shell boundary characters
5
+ * (`\n ; | && ( )`) are preserved verbatim so a boundary-anchored
6
+ * regex still matches. Byte-identical to the input when nothing
7
+ * recognisable was found anywhere in the command.
8
+ */
9
+ normalized: string;
10
+ /**
11
+ * The effective target directory of the command's git invocation, or
12
+ * `null` when the command names none, the command names invocations
13
+ * that disagree, or the only named value is `~`-prefixed (see the
14
+ * module header's target-directory-extraction rules).
15
+ *
16
+ * NOT WIRED TO ANY GATE (see the module header's STATUS paragraph):
17
+ * extracted and fully tested, pending a redesign that attributes a
18
+ * target per-policy instead of per-event — follow-up task
19
+ * `98ad072f`.
20
+ */
21
+ targetDir: string | null;
22
+ /**
23
+ * When `targetDir` is a RELATIVE path, the directory it should be
24
+ * resolved against instead of the caller's own cwd — the wrapping
25
+ * `env -C` on the same invocation, or a leading `cd` prefix (see the
26
+ * module header). `null` when `targetDir` is absolute, `null` itself,
27
+ * or no more specific base was found.
28
+ *
29
+ * Same unconsumed status as `targetDir` above — see the module
30
+ * header's STATUS paragraph.
31
+ */
32
+ targetBase: string | null;
33
+ /**
34
+ * `true` when the input exceeded `MAX_NORMALIZE_LENGTH` and
35
+ * normalisation was skipped entirely (G4 fix, review round 2,
36
+ * 2026-07-27): the caller previously had no way to tell "nothing
37
+ * recognisable was found" apart from "normalisation never ran at all",
38
+ * so the skip carried no audit signal. `false` whenever the input WAS
39
+ * run through `normalizeCommandInner` (including when nothing
40
+ * recognisable was found there either — that case is still `false`,
41
+ * only the length-bound short-circuit sets this).
42
+ */
43
+ truncated: boolean;
44
+ }
45
+ /**
46
+ * The `git` token itself, matched by BASENAME so a path-qualified
47
+ * invocation (`/usr/bin/git`, `./git`) is recognised too (F4 fix).
48
+ * Anchored to the WHOLE token (not a substring) so `mygit` / `git-foo`
49
+ * still correctly fail to match — `\S*` only ever contributes characters
50
+ * immediately before a literal `/`, never before `git` directly.
51
+ * Exported (fix round 2, finding F3) so
52
+ * `tests/runtime/bash-match-head-token-drift.test.ts` can couple this
53
+ * module's covered set to what FULL_TEMPLATE's shipped `bash_match`
54
+ * policies actually key on today, instead of a test-owned duplicate.
55
+ */
56
+ export declare const GIT_TOKEN_RE: RegExp;
57
+ /**
58
+ * The three OTHER head tokens this module covers (D-001, run
59
+ * 2026-07-28-nongit-trigger-wrappers) — `gh` (`gh pr merge` / `gh pr
60
+ * create`), `npm` (`npm publish`), `harness` (the kill-switch verbs). A
61
+ * closed set, deliberately NOT a general "any head token" rule (see the
62
+ * module header): matched by EXACT literal equality, unlike `GIT_TOKEN_RE`'s
63
+ * basename match, so a path-qualified spelling of any of these three stays
64
+ * out of scope (named residual, module header NOT-SUPPORTED list). This is
65
+ * NOT the complete set of head tokens a shipped `bash_match` trigger keys
66
+ * on — see the module header's "SHIPPED BUT NOT COVERED" paragraph (fix
67
+ * round 2, finding F1) for the four that are NOT in this set (`env`,
68
+ * `unset`, `tee`, `cp`) and why. Exported (fix round 2, finding F3) for the
69
+ * same drift-guard reason as `GIT_TOKEN_RE` above.
70
+ */
71
+ export declare const NON_GIT_HEAD_TOKENS: ReadonlySet<string>;
72
+ /**
73
+ * Above this length, normalisation is skipped entirely and the command
74
+ * is returned unchanged (F3 fix — see the module header for why command
75
+ * SIZE must be bounded even after `findNextBoundary` was made linear).
76
+ * 100k characters comfortably covers any command a human or agent would
77
+ * plausibly type, while keeping the worst case a small constant instead
78
+ * of scaling with whatever ends up pasted into a single Bash call.
79
+ * Exported (G4 fix, review round 2) so a caller reporting the skip (see
80
+ * `NormalizedCommand.truncated`) can reference the same number instead of
81
+ * hand-copying it into a diagnostic message.
82
+ */
83
+ export declare const MAX_NORMALIZE_LENGTH = 100000;
84
+ /**
85
+ * Given a Bash command string, return its normalised form and the target
86
+ * directory (and base, if relative) of its git invocation. Never throws:
87
+ * any internal failure (there should be none — this is a defensive
88
+ * backstop matching the fail-safe shape used elsewhere in this codebase,
89
+ * e.g. `policyMatchesEvent`'s `try/catch` around `new RegExp`) falls back
90
+ * to the input unchanged.
91
+ */
92
+ export declare function normalizeCommand(command: string): NormalizedCommand;