@dzhechkov/harness-core 0.5.0 → 0.5.2
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.
- package/.dz-manifest.json +339 -75
- package/README.md +47 -4
- package/dist/codex-hooks-assets.d.ts.map +1 -1
- package/dist/codex-hooks-assets.js +39 -2
- package/dist/codex-hooks-assets.js.map +1 -1
- package/dist/codex-hooks-verify.d.ts +23 -2
- package/dist/codex-hooks-verify.d.ts.map +1 -1
- package/dist/codex-hooks-verify.js +29 -0
- package/dist/codex-hooks-verify.js.map +1 -1
- package/dist/codex-hooks.d.ts +90 -7
- package/dist/codex-hooks.d.ts.map +1 -1
- package/dist/codex-hooks.js +171 -21
- package/dist/codex-hooks.js.map +1 -1
- package/dist/feature-adr-routing.d.ts +22 -0
- package/dist/feature-adr-routing.d.ts.map +1 -1
- package/dist/feature-adr-routing.js +45 -0
- package/dist/feature-adr-routing.js.map +1 -1
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/loop-blobs.generated.d.ts +1 -1
- package/dist/loop-blobs.generated.d.ts.map +1 -1
- package/dist/loop-blobs.generated.js +12 -3
- package/dist/loop-blobs.generated.js.map +1 -1
- package/dist/loop-plan.d.ts +70 -0
- package/dist/loop-plan.d.ts.map +1 -1
- package/dist/loop-plan.js +103 -0
- package/dist/loop-plan.js.map +1 -1
- package/dist/loop-render.d.ts.map +1 -1
- package/dist/loop-render.js +38 -130
- package/dist/loop-render.js.map +1 -1
- package/dist/loop-run-semantics.d.ts +130 -0
- package/dist/loop-run-semantics.d.ts.map +1 -0
- package/dist/loop-run-semantics.js +257 -0
- package/dist/loop-run-semantics.js.map +1 -0
- package/dist/loop-trace.d.ts +106 -5
- package/dist/loop-trace.d.ts.map +1 -1
- package/dist/loop-trace.js +151 -18
- package/dist/loop-trace.js.map +1 -1
- package/dist/managed-hooks.d.ts +10 -0
- package/dist/managed-hooks.d.ts.map +1 -1
- package/dist/managed-hooks.js +17 -5
- package/dist/managed-hooks.js.map +1 -1
- package/dist/named-lock.d.ts +57 -0
- package/dist/named-lock.d.ts.map +1 -0
- package/dist/named-lock.js +247 -0
- package/dist/named-lock.js.map +1 -0
- package/dist/operations.d.ts +81 -5
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +356 -38
- package/dist/operations.js.map +1 -1
- package/dist/parity.d.ts +70 -2
- package/dist/parity.d.ts.map +1 -1
- package/dist/parity.js +133 -2
- package/dist/parity.js.map +1 -1
- package/dist/qe-bridge.d.ts +291 -0
- package/dist/qe-bridge.d.ts.map +1 -0
- package/dist/qe-bridge.js +538 -0
- package/dist/qe-bridge.js.map +1 -0
- package/dist/score.d.ts.map +1 -1
- package/dist/score.js +43 -9
- package/dist/score.js.map +1 -1
- package/dist/trace-corroborate.d.ts +48 -0
- package/dist/trace-corroborate.d.ts.map +1 -0
- package/dist/trace-corroborate.js +172 -0
- package/dist/trace-corroborate.js.map +1 -0
- package/dist/workflow-run-dispatch.d.ts +230 -0
- package/dist/workflow-run-dispatch.d.ts.map +1 -0
- package/dist/workflow-run-dispatch.js +363 -0
- package/dist/workflow-run-dispatch.js.map +1 -0
- package/dist/workflow-run.d.ts +513 -0
- package/dist/workflow-run.d.ts.map +1 -0
- package/dist/workflow-run.js +1377 -0
- package/dist/workflow-run.js.map +1 -0
- package/package.json +2 -2
- package/sbom.json +740 -80
- package/src/codex-hooks-assets.ts +39 -2
- package/src/codex-hooks-verify.ts +55 -2
- package/src/codex-hooks.ts +172 -20
- package/src/feature-adr-routing.ts +55 -0
- package/src/index.ts +46 -1
- package/src/loop-blobs.generated.ts +12 -3
- package/src/loop-plan.ts +185 -0
- package/src/loop-render.ts +38 -128
- package/src/loop-run-semantics.ts +278 -0
- package/src/loop-trace.ts +207 -16
- package/src/managed-hooks.ts +26 -5
- package/src/named-lock.ts +277 -0
- package/src/operations.ts +441 -40
- package/src/parity.ts +177 -2
- package/src/qe-bridge.ts +737 -0
- package/src/score.ts +50 -9
- package/src/trace-corroborate.ts +205 -0
- package/src/workflow-run-dispatch.ts +459 -0
- package/src/workflow-run.ts +1773 -0
package/src/parity.ts
CHANGED
|
@@ -99,6 +99,18 @@ export interface CapabilityEvidence {
|
|
|
99
99
|
readonly kind: 'transcript' | 'reproducer';
|
|
100
100
|
/** ISO date the evidence was produced. Stale evidence is still evidence — silence is not. */
|
|
101
101
|
readonly at: string;
|
|
102
|
+
/**
|
|
103
|
+
* The RUNTIME VERSION the evidence was measured on, verbatim from the runtime's own `--version`
|
|
104
|
+
* (e.g. `codex-cli 0.147.0`).
|
|
105
|
+
*
|
|
106
|
+
* ADR-006 pins capability grants to a runtime version, and the gate enforced only that a record
|
|
107
|
+
* existed and its file was on disk — so evidence recorded on codex 0.147 kept a grant alive on
|
|
108
|
+
* the installed 0.148 (independent review, finding 3). A hook registry is exactly the kind of
|
|
109
|
+
* surface a minor version moves. When this is set and the caller supplies a PROBED version that
|
|
110
|
+
* differs, the grant is reported `stale-runtime-version` — INCONCLUSIVE, not revoked: the fix is
|
|
111
|
+
* a fresh probe, and an absent probe flips nothing.
|
|
112
|
+
*/
|
|
113
|
+
readonly runtimeVersion?: string;
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
export const CAPABILITY_EVIDENCE: Record<TargetName, Partial<Record<RuntimeCapability, CapabilityEvidence>>> = {
|
|
@@ -119,15 +131,28 @@ export const CAPABILITY_EVIDENCE: Record<TargetName, Partial<Record<RuntimeCapab
|
|
|
119
131
|
mcp: { evidence: 'codex mcp list', kind: 'reproducer', at: '2026-07-19' },
|
|
120
132
|
// The two-sided live block: our marker in the transcript AND the sentinel side effect absent,
|
|
121
133
|
// in a NON-bypassed run, with the entry reported `trusted` by codex's own `hooks/list`.
|
|
134
|
+
// RE-PROBED on the installed runtime at the independent-QE fix round (review finding 3): the
|
|
135
|
+
// 0.147.0 evidence was stale on a machine running 0.148.0, and re-running it was NOT a
|
|
136
|
+
// formality — the arming path had silently stopped working, because `hooks/list` spells its
|
|
137
|
+
// `eventName` field `preToolUse` on 0.148 while the trust KEY still says `pre_tool_use`.
|
|
138
|
+
// All four legs pass on 0.148.0 with the trust axis PARSED from `hooks/list` rather than
|
|
139
|
+
// hard-coded (the pre-fix grader asserted `trustStatus: 'trusted'` as a constant).
|
|
122
140
|
'hooks-shell': {
|
|
123
|
-
evidence: 'features/crossrt-2-codex-hooks/07_code_changes/probe-results/veto-armed.txt',
|
|
141
|
+
evidence: 'features/crossrt-2-codex-hooks/07_code_changes/probe-results/fixround/veto-armed.txt',
|
|
124
142
|
kind: 'transcript',
|
|
125
143
|
at: '2026-08-19',
|
|
144
|
+
runtimeVersion: 'codex-cli 0.148.0',
|
|
126
145
|
},
|
|
146
|
+
// NOT re-probed on 0.148: the recall canary is a separate live run (forced hit + removed twin)
|
|
147
|
+
// and this round did not execute it. The version it was measured on is recorded HONESTLY, so
|
|
148
|
+
// `findUnbackedCapabilities` reports it `stale-runtime-version` — INCONCLUSIVE — the moment a
|
|
149
|
+
// caller supplies the probed 0.148.0. That is the correct state: the grant is not withdrawn,
|
|
150
|
+
// it is awaiting its re-probe.
|
|
127
151
|
'hooks-prompt': {
|
|
128
152
|
evidence: 'features/crossrt-2-codex-hooks/07_code_changes/probe-results/recall-canary.md',
|
|
129
153
|
kind: 'transcript',
|
|
130
154
|
at: '2026-08-19',
|
|
155
|
+
runtimeVersion: 'codex-cli 0.147.0',
|
|
131
156
|
},
|
|
132
157
|
},
|
|
133
158
|
opencode: { shell: { evidence: 'adapter emit', kind: 'reproducer', at: '2026-07-19' }, skills: { evidence: 'dz compose --target opencode', kind: 'reproducer', at: '2026-07-19' } },
|
|
@@ -143,10 +168,23 @@ export const CAPABILITY_EVIDENCE: Record<TargetName, Partial<Record<RuntimeCapab
|
|
|
143
168
|
export interface UnbackedCapability {
|
|
144
169
|
readonly target: TargetName;
|
|
145
170
|
readonly capability: RuntimeCapability;
|
|
146
|
-
|
|
171
|
+
/**
|
|
172
|
+
* `stale-runtime-version` is INCONCLUSIVE, not a refutation: the grant was proven, on a runtime
|
|
173
|
+
* that is no longer the installed one. It clears when a current-version probe is recorded.
|
|
174
|
+
*/
|
|
175
|
+
readonly reason: 'no-evidence-record' | 'dangling-transcript' | 'stale-runtime-version';
|
|
147
176
|
readonly evidence?: string;
|
|
177
|
+
/** `recorded → probed`, for the stale case. */
|
|
178
|
+
readonly recordedVersion?: string;
|
|
179
|
+
readonly probedVersion?: string;
|
|
148
180
|
}
|
|
149
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Runtime versions observed NOW, keyed by target — injected, never probed here (this module is
|
|
184
|
+
* pure). An absent entry means "not probed", which flips nothing.
|
|
185
|
+
*/
|
|
186
|
+
export type ProbedRuntimeVersions = Partial<Record<TargetName, string>>;
|
|
187
|
+
|
|
150
188
|
/**
|
|
151
189
|
* Every capability grant that is NOT backed by usable evidence.
|
|
152
190
|
*
|
|
@@ -159,6 +197,7 @@ export function findUnbackedCapabilities(
|
|
|
159
197
|
transcriptExists: (path: string) => boolean,
|
|
160
198
|
capabilities: Record<TargetName, readonly RuntimeCapability[]> = TARGET_CAPABILITIES,
|
|
161
199
|
evidence: Record<TargetName, Partial<Record<RuntimeCapability, CapabilityEvidence>>> = CAPABILITY_EVIDENCE,
|
|
200
|
+
probedVersions: ProbedRuntimeVersions = {},
|
|
162
201
|
): UnbackedCapability[] {
|
|
163
202
|
const out: UnbackedCapability[] = [];
|
|
164
203
|
for (const target of Object.keys(capabilities) as TargetName[]) {
|
|
@@ -170,12 +209,112 @@ export function findUnbackedCapabilities(
|
|
|
170
209
|
}
|
|
171
210
|
if (record.kind === 'transcript' && !transcriptExists(record.evidence)) {
|
|
172
211
|
out.push({ target, capability, reason: 'dangling-transcript', evidence: record.evidence });
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Version pin (ADR-006, finding 3). Only fires when BOTH sides are known: an unprobed target
|
|
215
|
+
// stays silent rather than reporting every grant stale on a machine without the runtime.
|
|
216
|
+
const probed = probedVersions[target];
|
|
217
|
+
if (record.runtimeVersion !== undefined && probed !== undefined && probed !== record.runtimeVersion) {
|
|
218
|
+
out.push({
|
|
219
|
+
target,
|
|
220
|
+
capability,
|
|
221
|
+
reason: 'stale-runtime-version',
|
|
222
|
+
evidence: record.evidence,
|
|
223
|
+
recordedVersion: record.runtimeVersion,
|
|
224
|
+
probedVersion: probed,
|
|
225
|
+
});
|
|
173
226
|
}
|
|
174
227
|
}
|
|
175
228
|
}
|
|
176
229
|
return out;
|
|
177
230
|
}
|
|
178
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Compare two recorded runtime versions the way a version string means it.
|
|
234
|
+
*
|
|
235
|
+
* `null` when the two are not comparable (different product, unparseable) — which the caller reads
|
|
236
|
+
* as "not the newest", the conservative direction.
|
|
237
|
+
*/
|
|
238
|
+
function compareRuntimeVersions(a: string, b: string): number | null {
|
|
239
|
+
const parse = (v: string): { product: string; parts: number[] } | null => {
|
|
240
|
+
const m = /^(.*?)\s*(\d+(?:\.\d+)*)\s*$/.exec(v.trim());
|
|
241
|
+
if (m === null) return null;
|
|
242
|
+
return { product: (m[1] ?? '').trim(), parts: (m[2] ?? '').split('.').map((n) => Number(n)) };
|
|
243
|
+
};
|
|
244
|
+
const pa = parse(a);
|
|
245
|
+
const pb = parse(b);
|
|
246
|
+
if (pa === null || pb === null || pa.product !== pb.product) return null;
|
|
247
|
+
const len = Math.max(pa.parts.length, pb.parts.length);
|
|
248
|
+
for (let i = 0; i < len; i += 1) {
|
|
249
|
+
const x = pa.parts[i] ?? 0;
|
|
250
|
+
const y = pb.parts[i] ?? 0;
|
|
251
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
252
|
+
}
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The newest runtime version recorded among a target's TRANSCRIPT evidence, or `null`.
|
|
258
|
+
*
|
|
259
|
+
* This is what makes staleness detectable WITHOUT running anything: the records date themselves
|
|
260
|
+
* against each other. `dz parity` computes a matrix and must keep doing so deterministically — a
|
|
261
|
+
* `codex --version` subprocess inside it would make a pure report depend on the machine it prints
|
|
262
|
+
* on. Re-probing one capability is what dates the others (fix round 2, R2-3).
|
|
263
|
+
*/
|
|
264
|
+
export function newestRecordedRuntimeVersion(
|
|
265
|
+
target: TargetName,
|
|
266
|
+
evidence: Record<TargetName, Partial<Record<RuntimeCapability, CapabilityEvidence>>> = CAPABILITY_EVIDENCE,
|
|
267
|
+
): string | null {
|
|
268
|
+
let newest: string | null = null;
|
|
269
|
+
for (const record of Object.values(evidence[target] ?? {})) {
|
|
270
|
+
if (record === undefined || record.kind !== 'transcript' || record.runtimeVersion === undefined) continue;
|
|
271
|
+
if (newest === null) {
|
|
272
|
+
newest = record.runtimeVersion;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const cmp = compareRuntimeVersions(newest, record.runtimeVersion);
|
|
276
|
+
if (cmp !== null && cmp < 0) newest = record.runtimeVersion;
|
|
277
|
+
}
|
|
278
|
+
return newest;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Transcript evidence that is out of date with the newest record for its own target.
|
|
283
|
+
*
|
|
284
|
+
* SCOPE, stated because it is a judgement and not a derivation: the version rule applies to
|
|
285
|
+
* `kind: 'transcript'` records only. A transcript FREEZES one runtime moment, so it can go stale; a
|
|
286
|
+
* `reproducer` is a command anyone can re-run, and calling it stale would report `dz --version` as
|
|
287
|
+
* expired. A transcript with NO `runtimeVersion` is stale too — an undated observation cannot be
|
|
288
|
+
* shown to be current.
|
|
289
|
+
*
|
|
290
|
+
* INCONCLUSIVE, never a refutation: the grant is not withdrawn, it is awaiting its re-probe.
|
|
291
|
+
*/
|
|
292
|
+
export function findStaleTranscriptEvidence(
|
|
293
|
+
capabilities: Record<TargetName, readonly RuntimeCapability[]> = TARGET_CAPABILITIES,
|
|
294
|
+
evidence: Record<TargetName, Partial<Record<RuntimeCapability, CapabilityEvidence>>> = CAPABILITY_EVIDENCE,
|
|
295
|
+
): UnbackedCapability[] {
|
|
296
|
+
const out: UnbackedCapability[] = [];
|
|
297
|
+
for (const target of Object.keys(capabilities) as TargetName[]) {
|
|
298
|
+
const newest = newestRecordedRuntimeVersion(target, evidence);
|
|
299
|
+
if (newest === null) continue; // nothing version-pinned on this target ⇒ nothing to be stale against
|
|
300
|
+
for (const capability of capabilities[target]) {
|
|
301
|
+
const record = evidence[target]?.[capability];
|
|
302
|
+
if (record === undefined || record.kind !== 'transcript') continue;
|
|
303
|
+
const recorded = record.runtimeVersion;
|
|
304
|
+
if (recorded !== undefined && compareRuntimeVersions(recorded, newest) === 0) continue;
|
|
305
|
+
out.push({
|
|
306
|
+
target,
|
|
307
|
+
capability,
|
|
308
|
+
reason: 'stale-runtime-version',
|
|
309
|
+
evidence: record.evidence,
|
|
310
|
+
...(recorded !== undefined ? { recordedVersion: recorded } : {}),
|
|
311
|
+
probedVersion: newest,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
179
318
|
/** How a feature manifests on a platform: a concrete FORM with its runtime requirements. */
|
|
180
319
|
export interface FeatureForm {
|
|
181
320
|
/** Human-readable name of the form, shown as the `via` of a parity cell (AM-2). */
|
|
@@ -329,6 +468,42 @@ export function computeParity(feature: ParityFeature, capabilities: readonly Run
|
|
|
329
468
|
return { level: 'none' };
|
|
330
469
|
}
|
|
331
470
|
|
|
471
|
+
/**
|
|
472
|
+
* A parity cell AS REPORTED — the computed level, plus the one thing the computation cannot know:
|
|
473
|
+
* whether the evidence behind the deciding capability is still current.
|
|
474
|
+
*
|
|
475
|
+
* `inconclusive` is a REPORTING level, not a model level: `computeParity` keeps answering the
|
|
476
|
+
* capability question, and this layer answers the evidence question. Keeping them apart is what
|
|
477
|
+
* lets the matrix stay a pure function of the capability model.
|
|
478
|
+
*/
|
|
479
|
+
export interface ParityReportCell {
|
|
480
|
+
readonly level: 'full' | 'manual' | 'none' | 'inconclusive';
|
|
481
|
+
readonly via?: string | undefined;
|
|
482
|
+
/** The stale capabilities the deciding form depends on (present only when `inconclusive`). */
|
|
483
|
+
readonly staleEvidence?: readonly RuntimeCapability[];
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Downgrade a cell whose DECIDING form rests on stale evidence to `inconclusive`.
|
|
488
|
+
*
|
|
489
|
+
* The round-1 gate could tell that `hooks-prompt` evidence was stale and nothing a user runs ever
|
|
490
|
+
* asked it (fix round 2, R2-3): `dz parity` printed `full` for the auto-recall leg on codex off a
|
|
491
|
+
* transcript recorded on a runtime that is no longer installed. A cell that says `full` on evidence
|
|
492
|
+
* nobody has re-confirmed is the same class of claim this whole feature exists to refuse.
|
|
493
|
+
*/
|
|
494
|
+
export function downgradeForStaleEvidence(
|
|
495
|
+
feature: ParityFeature,
|
|
496
|
+
cell: ParityCell,
|
|
497
|
+
staleCapabilities: readonly RuntimeCapability[],
|
|
498
|
+
): ParityReportCell {
|
|
499
|
+
if (cell.level === 'none' || cell.via === undefined || staleCapabilities.length === 0) return cell;
|
|
500
|
+
const form = feature.forms.find((f) => f.form === cell.via);
|
|
501
|
+
if (form === undefined) return cell;
|
|
502
|
+
const hit = form.requires.filter((r) => staleCapabilities.includes(r));
|
|
503
|
+
if (hit.length === 0) return cell;
|
|
504
|
+
return { level: 'inconclusive', via: cell.via, staleEvidence: hit };
|
|
505
|
+
}
|
|
506
|
+
|
|
332
507
|
export interface ParityMatrixRow {
|
|
333
508
|
readonly feature: ParityFeature;
|
|
334
509
|
readonly cells: Readonly<Record<TargetName, ParityCell>>;
|