@dzhechkov/harness-core 0.4.4 → 0.5.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 (97) hide show
  1. package/.dz-manifest.json +283 -103
  2. package/LICENSE +21 -0
  3. package/README.md +73 -5
  4. package/dist/agents-policy.d.ts +67 -0
  5. package/dist/agents-policy.d.ts.map +1 -0
  6. package/dist/agents-policy.js +258 -0
  7. package/dist/agents-policy.js.map +1 -0
  8. package/dist/codex-hooks-assets.d.ts +47 -0
  9. package/dist/codex-hooks-assets.d.ts.map +1 -0
  10. package/dist/codex-hooks-assets.js +287 -0
  11. package/dist/codex-hooks-assets.js.map +1 -0
  12. package/dist/codex-hooks-verify.d.ts +74 -0
  13. package/dist/codex-hooks-verify.d.ts.map +1 -0
  14. package/dist/codex-hooks-verify.js +140 -0
  15. package/dist/codex-hooks-verify.js.map +1 -0
  16. package/dist/codex-hooks.d.ts +258 -0
  17. package/dist/codex-hooks.d.ts.map +1 -0
  18. package/dist/codex-hooks.js +391 -0
  19. package/dist/codex-hooks.js.map +1 -0
  20. package/dist/discrimination-gate.d.ts +88 -15
  21. package/dist/discrimination-gate.d.ts.map +1 -1
  22. package/dist/discrimination-gate.js +343 -51
  23. package/dist/discrimination-gate.js.map +1 -1
  24. package/dist/feature-adr-checkpoints.d.ts +22 -0
  25. package/dist/feature-adr-checkpoints.d.ts.map +1 -1
  26. package/dist/feature-adr-checkpoints.js +42 -0
  27. package/dist/feature-adr-checkpoints.js.map +1 -1
  28. package/dist/feature-adr-routing.d.ts +196 -5
  29. package/dist/feature-adr-routing.d.ts.map +1 -1
  30. package/dist/feature-adr-routing.js +538 -54
  31. package/dist/feature-adr-routing.js.map +1 -1
  32. package/dist/guard.d.ts +13 -0
  33. package/dist/guard.d.ts.map +1 -1
  34. package/dist/guard.js +25 -1
  35. package/dist/guard.js.map +1 -1
  36. package/dist/index.d.ts +17 -7
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +23 -4
  39. package/dist/index.js.map +1 -1
  40. package/dist/loop-blobs.generated.js +2 -2
  41. package/dist/loop-blobs.generated.js.map +1 -1
  42. package/dist/managed-hooks.d.ts +76 -0
  43. package/dist/managed-hooks.d.ts.map +1 -0
  44. package/dist/managed-hooks.js +89 -0
  45. package/dist/managed-hooks.js.map +1 -0
  46. package/dist/mutation-gate.d.ts +14 -0
  47. package/dist/mutation-gate.d.ts.map +1 -1
  48. package/dist/mutation-gate.js +25 -2
  49. package/dist/mutation-gate.js.map +1 -1
  50. package/dist/operations.d.ts +153 -0
  51. package/dist/operations.d.ts.map +1 -1
  52. package/dist/operations.js +560 -24
  53. package/dist/operations.js.map +1 -1
  54. package/dist/parity.d.ts +38 -1
  55. package/dist/parity.d.ts.map +1 -1
  56. package/dist/parity.js +78 -5
  57. package/dist/parity.js.map +1 -1
  58. package/dist/recall-usage.d.ts +53 -0
  59. package/dist/recall-usage.d.ts.map +1 -1
  60. package/dist/recall-usage.js +125 -2
  61. package/dist/recall-usage.js.map +1 -1
  62. package/dist/setup.d.ts.map +1 -1
  63. package/dist/setup.js +14 -26
  64. package/dist/setup.js.map +1 -1
  65. package/dist/shell-veto-policy.d.ts +53 -0
  66. package/dist/shell-veto-policy.d.ts.map +1 -0
  67. package/dist/shell-veto-policy.js +103 -0
  68. package/dist/shell-veto-policy.js.map +1 -0
  69. package/dist/skills.d.ts +86 -1
  70. package/dist/skills.d.ts.map +1 -1
  71. package/dist/skills.js +116 -1
  72. package/dist/skills.js.map +1 -1
  73. package/dist/targets.d.ts +75 -0
  74. package/dist/targets.d.ts.map +1 -1
  75. package/dist/targets.js +160 -0
  76. package/dist/targets.js.map +1 -1
  77. package/package.json +20 -19
  78. package/sbom.json +552 -102
  79. package/src/agents-policy.ts +338 -0
  80. package/src/codex-hooks-assets.ts +291 -0
  81. package/src/codex-hooks-verify.ts +184 -0
  82. package/src/codex-hooks.ts +571 -0
  83. package/src/discrimination-gate.ts +456 -58
  84. package/src/feature-adr-checkpoints.ts +38 -0
  85. package/src/feature-adr-routing.ts +642 -75
  86. package/src/guard.ts +36 -1
  87. package/src/index.ts +118 -2
  88. package/src/loop-blobs.generated.ts +2 -2
  89. package/src/managed-hooks.ts +129 -0
  90. package/src/mutation-gate.ts +24 -2
  91. package/src/operations.ts +719 -28
  92. package/src/parity.ts +120 -6
  93. package/src/recall-usage.ts +184 -1
  94. package/src/setup.ts +26 -27
  95. package/src/shell-veto-policy.ts +119 -0
  96. package/src/skills.ts +174 -1
  97. package/src/targets.ts +189 -0
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The fail-closed veto-probe classifier (`crossrt-2-codex-hooks`, ADR-002).
3
+ *
4
+ * Turns the evidence of ONE live probe run into a two-axis verdict. Pure: it never reads a file,
5
+ * never runs a command, and never consults its own emitter's intent — the whole point is that
6
+ * "we wrote the hook" is not evidence that the hook fired.
7
+ *
8
+ * ## The two axes (AM-16)
9
+ *
10
+ * `verdict` answers *did OUR hook block the forbidden command?* and comes from the TRANSCRIPT plus
11
+ * the sentinel side effect. `trust` answers *is the entry armed in the runtime's own view?* and
12
+ * comes from `hooks/list`'s `trustStatus` (MEASURED available headlessly — M0 spike, probe 1).
13
+ *
14
+ * ## No path defaults to a pass
15
+ *
16
+ * Every branch states its own verdict; there is no `??` fallback and no `else` that guesses. The
17
+ * four ways a probe can LOOK like a pass without being one, each closed here:
18
+ *
19
+ * 1. **A bypassed run.** `--dangerously-bypass-hook-trust` makes an UNTRUSTED hook fire, so a block
20
+ * observed under it says nothing about the installed state (AM-1). Decided FIRST.
21
+ * 2. **Somebody else's block.** MEASURED (M0 §5): the runtime prefixes our stderr with its own
22
+ * `Command blocked by PreToolUse hook: `. That phrase WITHOUT `DZ-VETO:` means *a* hook blocked,
23
+ * not that ours did.
24
+ * 3. **A command that never ran.** "the model declined" and "the command was blocked" both show an
25
+ * absent sentinel; only the transcript separates them (AM-36).
26
+ * 4. **A stale transcript.** Without this run's nonce the text is evidence about some other run.
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+
31
+ export type VetoVerdict = 'armed' | 'not-armed' | 'inconclusive';
32
+ export type VetoTrust = 'trusted' | 'trust-pending' | 'unknown';
33
+
34
+ /** The runtime's own `HookTrustStatus` vocabulary (v2 `HooksListResponse.json`, codex 0.147.0). */
35
+ export type CodexHookTrustStatus = 'managed' | 'untrusted' | 'trusted' | 'modified';
36
+
37
+ /** Our block marker. Must appear verbatim for a block to be attributed to dz. */
38
+ export const DZ_VETO_MARKER = 'DZ-VETO:';
39
+ /** The default-mode (warn) marker — a hit that did NOT block (G-J). */
40
+ export const DZ_VETO_WARN_MARKER = 'DZ-VETO-WARN:';
41
+ /** The runtime's generic block phrase. Present WITHOUT our marker ⇒ not our block. */
42
+ export const RUNTIME_BLOCK_PHRASE = 'Command blocked by PreToolUse hook';
43
+
44
+ export interface VetoProbeEvidence {
45
+ /** stdout+stderr of the probe run, verbatim. */
46
+ readonly transcript: string;
47
+ /** Per-run nonce embedded in the probe command; guards against grading a stale transcript. */
48
+ readonly nonce: string;
49
+ /** Did the sentinel side effect land? `true` ⇒ the command RAN. */
50
+ readonly sentinelPresent: boolean;
51
+ /** Did the transcript show the model actually attempting the shell command? */
52
+ readonly shellAttempted: boolean;
53
+ /** Was `--dangerously-bypass-hook-trust` in play? `true` can never yield a pass. */
54
+ readonly bypassedTrust: boolean;
55
+ /** From `hooks/list`. Absent ⇒ trust could not be established, which is not the same as bad. */
56
+ readonly trustStatus?: CodexHookTrustStatus | undefined;
57
+ /** The codex version the manifest was written against. */
58
+ readonly recordedCodexVersion?: string | undefined;
59
+ /** The codex version observed now. A mismatch re-asserts stale facts, so it is inconclusive. */
60
+ readonly probedCodexVersion?: string | undefined;
61
+ }
62
+
63
+ export interface VetoProbeResult {
64
+ readonly verdict: VetoVerdict;
65
+ readonly trust: VetoTrust;
66
+ readonly reason: string;
67
+ }
68
+
69
+ /** Trust axis. Derived only from the runtime's own report, never from our emitter. */
70
+ export function classifyTrust(status: VetoProbeEvidence['trustStatus']): VetoTrust {
71
+ switch (status) {
72
+ case 'trusted':
73
+ case 'managed':
74
+ return 'trusted';
75
+ case 'untrusted':
76
+ case 'modified':
77
+ return 'trust-pending';
78
+ default:
79
+ return 'unknown';
80
+ }
81
+ }
82
+
83
+ /** Classify one probe run. Exhaustive; no branch falls through to a pass. */
84
+ export function classifyVetoProbe(e: VetoProbeEvidence): VetoProbeResult {
85
+ const transcript = typeof e.transcript === 'string' ? e.transcript : '';
86
+
87
+ // 1. A bypassed run is a DIAGNOSTIC, never a verdict (AM-1). Decided before any other evidence.
88
+ if (e.bypassedTrust === true) {
89
+ return {
90
+ verdict: 'inconclusive',
91
+ trust: 'unknown',
92
+ reason:
93
+ 'run used --dangerously-bypass-hook-trust: it proves the helper body works, not that the installed entry is armed',
94
+ };
95
+ }
96
+
97
+ const trust = classifyTrust(e.trustStatus);
98
+
99
+ // 2. Stale facts are not facts (AM-10).
100
+ const recorded = e.recordedCodexVersion;
101
+ const probed = e.probedCodexVersion;
102
+ if (typeof recorded === 'string' && typeof probed === 'string' && recorded !== probed) {
103
+ return {
104
+ verdict: 'inconclusive',
105
+ trust,
106
+ reason: `codex version mismatch: manifest recorded ${recorded}, runtime reports ${probed} — re-probe before trusting the verdict`,
107
+ };
108
+ }
109
+
110
+ // 3. A stale or foreign transcript cannot grade THIS run.
111
+ if (typeof e.nonce !== 'string' || e.nonce === '' || !transcript.includes(e.nonce)) {
112
+ return {
113
+ verdict: 'inconclusive',
114
+ trust,
115
+ reason: "transcript does not carry this run's nonce — it is not evidence about this run",
116
+ };
117
+ }
118
+
119
+ // 4. "the command never ran" and "the command was blocked" both show an absent sentinel (AM-36).
120
+ if (e.shellAttempted !== true) {
121
+ return {
122
+ verdict: 'inconclusive',
123
+ trust,
124
+ reason: 'transcript shows no shell attempt: the model declined or rewrote the command — re-drive the probe',
125
+ };
126
+ }
127
+
128
+ const ourMarker = transcript.includes(DZ_VETO_MARKER);
129
+
130
+ // 5. Somebody ELSE's block is not ours (MEASURED: the runtime prefixes our stderr, M0 §5).
131
+ if (!ourMarker && transcript.includes(RUNTIME_BLOCK_PHRASE)) {
132
+ return {
133
+ verdict: 'inconclusive',
134
+ trust,
135
+ reason: `a PreToolUse hook blocked the command but the transcript carries no ${DZ_VETO_MARKER} marker — the block is not attributable to dz`,
136
+ };
137
+ }
138
+
139
+ if (ourMarker) {
140
+ if (e.sentinelPresent === true) {
141
+ return {
142
+ verdict: 'inconclusive',
143
+ trust,
144
+ reason: 'contradictory evidence: our block marker is present AND the sentinel side effect landed',
145
+ };
146
+ }
147
+ return {
148
+ verdict: 'armed',
149
+ trust,
150
+ reason: 'our marker present and the sentinel absent: the forbidden command was blocked by the dz hook',
151
+ };
152
+ }
153
+
154
+ // 6. No marker, and the command demonstrably ran.
155
+ if (e.sentinelPresent === true) {
156
+ return {
157
+ verdict: 'not-armed',
158
+ trust,
159
+ reason: 'the forbidden command ran to completion (sentinel present) with no dz block marker',
160
+ };
161
+ }
162
+
163
+ // 7. Attempted, no marker, no sentinel: something swallowed it. Missing evidence is not clean evidence.
164
+ return {
165
+ verdict: 'inconclusive',
166
+ trust,
167
+ reason: 'shell was attempted but neither a dz block marker nor the sentinel is present — evidence is missing, not clean',
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Exit map (ADR-002 §5). `0` demands BOTH axes: an `armed` run whose entry is only `trust-pending`
173
+ * exits non-zero, because the next session may not fire it.
174
+ */
175
+ export function verifyExitCode(r: VetoProbeResult): 0 | 1 | 3 {
176
+ if (r.verdict === 'armed' && r.trust === 'trusted') return 0;
177
+ if (r.verdict === 'inconclusive') return 3;
178
+ return 1;
179
+ }
180
+
181
+ /** True only for the one outcome that may print a success word (AM-17 / G-G). */
182
+ export function isReadyVerdict(r: VetoProbeResult): boolean {
183
+ return r.verdict === 'armed' && r.trust === 'trusted';
184
+ }