@dzhechkov/harness-cli 0.3.256 → 0.3.257

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 CHANGED
@@ -9,7 +9,7 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "5f73e2c6d9c0ea38d5324f058cbd921bdb28dab60aa97ded97979ed994426731"
12
+ "sha256": "7333dd44bcbeb6536e8ee0f0490a57fefca211e706535da7f44cea7012388213"
13
13
  },
14
14
  {
15
15
  "path": "coverage/coverage-final.json",
@@ -41,15 +41,15 @@
41
41
  },
42
42
  {
43
43
  "path": "dist/cli.d.ts.map",
44
- "sha256": "18d5f55ed3c56718e470fef8bd677c55de08f4505e188b1dd29a0f0bbec09d4b"
44
+ "sha256": "818293a250cdf1bd10b5d69267b714d9a8ab90017c1fa18de29fc24e28ad8a55"
45
45
  },
46
46
  {
47
47
  "path": "dist/cli.js",
48
- "sha256": "c47d11fd3ed7e6049b1080cd9292ed3498e113d8bc74a9af65a027d70fa96435"
48
+ "sha256": "9c37499ed55b571232b5718c863b83b31f7a79faab46a9dcbe3283b993b1862a"
49
49
  },
50
50
  {
51
51
  "path": "dist/cli.js.map",
52
- "sha256": "684ea0c6fa4edbbbea1ffcd943167cd374166eb196100433d4e8d02d70560794"
52
+ "sha256": "6cf537925ccb4b1ffff84c8fd255b487438b78b56a46f9d91d33b25500ae520d"
53
53
  },
54
54
  {
55
55
  "path": "dist/index.d.ts",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  {
75
75
  "path": "package.json",
76
- "sha256": "154755702a4652597bf8ca1410f9a3303b76c6a8dae8dacd867921a88c7b3626"
76
+ "sha256": "7abb07cdda09bea2b8e948cecdbaed4579371e8d0e8cee4436e7fcb20f430cdd"
77
77
  },
78
78
  {
79
79
  "path": "src/bin.ts",
@@ -81,7 +81,7 @@
81
81
  },
82
82
  {
83
83
  "path": "src/cli.ts",
84
- "sha256": "0dce827134ab5f7aaf55bd76325b5e1e17881770310e64e0d66bafbedc91687e"
84
+ "sha256": "d64af623372238bb5f2ead34e733130d7b013c49058a26f4795f09586b2b5dda"
85
85
  },
86
86
  {
87
87
  "path": "src/index.ts",
@@ -89,7 +89,7 @@
89
89
  },
90
90
  {
91
91
  "path": "test/cli.test.ts",
92
- "sha256": "7d6ba26ec1188db3b3ac89f50b015ba1f0c6117b5c30d99b22da41da10c96c12"
92
+ "sha256": "555aed692218a73f23a343df2f2b38ce028e694dd9d4aff37e727096f88eb50e"
93
93
  },
94
94
  {
95
95
  "path": "test/epoch-replay-cli.test.ts",
@@ -109,5 +109,5 @@
109
109
  }
110
110
  ]
111
111
  },
112
- "signature": "5nXiD5ii68CSKw5EWFT2WXxbELjZYvHBup3O5E4oLDcTszEC2kyuHXBy7VBzcrkgzNtICsxp8+hJJMcGXa2+Cw=="
112
+ "signature": "SGosjJne1+J5lgK/bAptsnGAciI95Bi4z2zHjB1QC8yWic0SGvKE3rj2mZqvjBfwmwzxvpmYBv/laFmWIQEtAw=="
113
113
  }
package/README.md CHANGED
@@ -456,6 +456,8 @@ $ dz compounding
456
456
  ↓ readme-first: 49 → 4
457
457
  COLD-VS-WARM REPLAY: 2 unique prompt event(s); 5 needed — queries are recorded as of 2026-07-28
458
458
  INSTRUMENTATION: last apply-leg record … — live
459
+ EVIDENCE CHAIN .dz/recall-usage.jsonl: verified · 3 chained · 133 pre-chain (uncovered)
460
+ EVIDENCE CHAIN .dz/guard-audit.jsonl: verified · 3 chained · 82 pre-chain (uncovered)
459
461
  ```
460
462
 
461
463
  Three honesty rules are load-bearing:
@@ -469,6 +471,47 @@ The prompt queries that make replay possible stay on your machine: `.dz/recall-u
469
471
  git-ignored, entries are truncated at 200 chars and flagged when truncated (a prefix is not a prompt,
470
472
  so flagged rows never count as replayable).
471
473
 
474
+ #### Is the evidence itself intact? — the event chain
475
+
476
+ Every number above is computed from two JSONL files, and a compaction bug already inflated one of
477
+ them once (read totals grew `2 → 4 → 6` across three compactions, fixed 2026-07-28). So each record
478
+ appended to `.dz/recall-usage.jsonl` and `.dz/guard-audit.jsonl` now carries a sequence number and a
479
+ hash of the line before it, and both `dz compounding` and `dz doctor` verify the chain:
480
+
481
+ ```
482
+ $ dz compounding | grep 'EVIDENCE CHAIN'
483
+ EVIDENCE CHAIN .dz/recall-usage.jsonl: verified · 3 chained · 133 pre-chain (uncovered)
484
+
485
+ $ dz doctor # silent while clean; on damage:
486
+ [XX] evidence chain (.dz/guard-audit.jsonl) - 1 defect(s): BrokenLink@L3 —
487
+ learning verdicts computed from this log are unsafe
488
+ ```
489
+
490
+ Defects are named, not lumped: `BrokenLink` (an edited or lost record), `DuplicateSeq` (a duplicated
491
+ record, or two writers racing), `NonMonotonicSeq` (an unrecorded restart), `TornTail` (a partial
492
+ write), `DoubleCounted` (a rewrite that emits more events than it read — the `2 → 4 → 6` class),
493
+ plus three that keep a rewrite from certifying itself: `LedgerImbalance`, `MalformedLedger` and
494
+ `ClaimInterrupted`.
495
+
496
+ Two rules make the check hard to fool, both learned from cross-model review:
497
+
498
+ - **compaction refuses to launder.** A rewrite that re-chains a damaged file would turn corruption
499
+ into a clean chain — so compaction verifies its input first and REFUSES a defective one. The log
500
+ then grows past its size cap, on purpose: the cap is a convenience, the evidence is the product.
501
+ - **a rewrite cannot silently eat a concurrent append.** The whole-file rewrite takes an exclusive
502
+ lock and re-reads the live file immediately before the rename; an append that landed in between
503
+ aborts the attempt and is folded into the retry instead of being overwritten.
504
+
505
+ **Honest scope, stated everywhere it appears:** this is corruption detection for *our own* bugs —
506
+ compaction, torn writes, races. FNV-1a is not cryptography and the threat model has no adversary:
507
+ anyone who can edit the log can recompute the chain. **When to use:** read the line before you quote
508
+ any `dz compounding` number, and after any crash that interrupted a write.
509
+
510
+ Three properties make it safe to run on a per-prompt hook: chain fields come from the **last line
511
+ only** (no full-file scan per append); records written before chaining existed stay valid and are
512
+ reported as an uncovered `pre-chain` prefix rather than flagged; and a tail that cannot be read
513
+ **never blocks the write** — the writer starts a fresh, explicitly marked segment.
514
+
472
515
  ### Did the lessons actually change outcomes? — `dz epoch-replay`
473
516
 
474
517
  `dz compounding` says a cold-vs-warm replay **can** be run. `dz epoch-replay` **runs** it and reports
@@ -880,7 +923,7 @@ dz bto-optimize --split|--plan|--select|--scope-check|--diff [--json] # determ
880
923
  dz discrimination-check --test <f[,f]> [--base <ref>] [--name <filter>] [--runner <cmd>] [--json] # §42 test-discrimination gate for feature-adr Step-8: run the ADR's property test in an isolated git worktree at pre-feature base — it MUST go red without the fix; a green is a false green (HIGH finding, advisory, never auto-aborts)
881
924
  dz delivery-check --slug <slug> [--context-only] [--findings <f.json>] [--strict] [--author <model>] [--json] # portable Step-10 Delivery Gate: the `manual` form that travels to every shell target — prints the 4-plane review brief (regressions ‖ security ‖ code-quality ‖ product-honesty) + artifact probes; --findings classifies a fed-back review into a fail-closed ready|blocked hand-off (only cross-validated BLOCKER/HIGH count) and writes features/<slug>/10_delivery_review.md; --strict exits 1 on blocked
882
925
  dz skills-verify [--dir <project>] [--expect a,b] [--static] [--strict] [--timeout <s>] [--json] # does .claude/skills/ actually REGISTER? --static = instant layout scan (CI-safe, no session): flags dirs that can never register; default also starts a real session and reads the authoritative system/init listing. exit 0 pass / 1 fail / 2 inconclusive — an unobservable registration is NEVER a pass
883
- dz compounding [--project <dir>] [--json] # honest learning-loop payoff report: pool write-only ratio, guard repeat-violation trajectory (rate per audit-half), cold-vs-warm replay readiness over unique untruncated prompt events, apply-leg staleness — a gate without enough data says INSUFFICIENT_DATA, never a verdict
926
+ dz compounding [--project <dir>] [--json] # honest learning-loop payoff report: pool write-only ratio, guard repeat-violation trajectory (rate per audit-half), cold-vs-warm replay readiness over unique untruncated prompt events, apply-leg staleness, and EVENT-CHAIN health of the two evidence logs (verified / defects / uncovered pre-chain prefix) — a gate without enough data says INSUFFICIENT_DATA, never a verdict
884
927
  dz epoch-replay --mock [--n <N>] [--effect <-1..1>] [--tie-rate <0..1>] [--seed <N>] [--slice <name>] [--margin <0..0.5>] [--json] # $0 synthetic run through the REAL verdict math; labelled SYNTHETIC, same seed = byte-identical
885
928
  dz epoch-replay --emit [--project <dir>] [--limit <N>] [--seed <N>] [--margin <0..0.5>] [--out <file>] [--json] # cold-vs-warm work order: replayable instances + PRE-REGISTERED blind A/B assignment + the PRE-REGISTERED non-superiority margin + a sha256 integrity digest + emittedAt/corpus fingerprint (raw prompts — defaults into the git-ignored .dz/epoch-replay/)
886
929
  dz epoch-replay --judge <filled-work-order.json> [--out <file>] [--json] # blind judge prompts from the filled plans — the file carries {id, prompt} ONLY (no assignment, no arm names); refuses an order whose digest or seed-derived assignment does not check out; half-pairs skipped WITH A REASON, on stdout
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAuWH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA4gO9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA4J5E"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA6WH,2EAA2E;AAC3E,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CAC5C;AAED,yFAAyF;AACzF,MAAM,MAAM,iBAAiB,GAAG,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAAE,KACvD;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAyjO9E,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,KAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CA4J5E"}
package/dist/cli.js CHANGED
@@ -3,13 +3,13 @@
3
3
  *
4
4
  * @packageDocumentation
5
5
  */
6
- import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
6
+ import { chmodSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
7
7
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { execFileSync, execSync, spawn } from 'node:child_process';
10
10
  import { homedir, tmpdir } from 'node:os';
11
11
  import { createRequire } from 'node:module';
12
- import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters,
12
+ import { createSkill, getSkillInfo, getWorkflow, isTargetName, listSkills, runDoctor, runInit, benchmarkSkill, benchmarkSkills, scanMcp, reconcileCapabilities, RECONCILE_BANNER, buildRegistry, discoverSkillPackDirs, checkUpstream, compareSkills, checkAllUpstream, sweepSkillDrift, syncCanonicalSkill, checkUpgrades, discoverPackages, discoverSourcePackages, fetchAllDownloads, filterByCategory, pretrain, recommend, generatePlugin, publishPackages, runSetup, runMigrate, searchRegistry, runSync, runVerify, runInitAgentsMd, runInitGeminiMd, TARGET_NAMES, buildParityMatrix, TARGET_CAPABILITIES, TARGET_SHORT_LABELS, WORKFLOW_NAMES, importEcc, recordPattern, resolveLearningBackend, storeStats, consolidateSessions, pruneNoisePatterns, lessonDeltaReport, removePatternsByIds, snapshotStore, recallHybrid, teachGuard, mirrorPatternsToVector, mirrorEntriesToVector, patternVectorEntry, readMemoryLearningConfig, promotePatterns, quarantineExpiryCandidates, pruneQuarantinePatterns, clearAgentdbQuarantine, vectorMirrorEnabled, vectorTierStatus, resolveVectorEngine, reindexVectorStore, harmonizeVectorStore, importRvfCheckpoint, statuslineData, writeFeatureAdrState, computeUsage, deriveUsageCalibration, normalizeClaudeUsageModelKey, readUsageLimits, claimCheck, summarize, queryBookKnowledge, loadStorePatternsSync, patternRecordId, loadStoreRecords, recordToPattern, bundleSkills, brainHome, listBrain, promoteProjectToBrain, updateBrainSource, queryBrain, groundPrompt, expandKu, reindexBrainVectors, buildPrimer, exportBrainSlice, importBrainSlice, registerKusToBrain, RECALL_USAGE_LOG_RELATIVE, RECALL_USAGE_LOG_MAX_BYTES, parseRecallUsageLog, buildRecallUsageReport, EVENT_CHAIN_TAIL_BYTES, EMPTY_LOG_TAIL, readTailInfo, appendChainedLines, verifyEventChainText, buildManifest, buildSbom, resolveTrustRoot, decideVerifyPolicy, generateSigningKeypair, evaluateGuard, resolveRules, auditRecord, guardExitCode, DEFAULT_RULES, parsePnpmLockImporters,
13
13
  // guard-promotion (feature guard-promotion, scout idea #1)
14
14
  assembleCandidates, renderPromotionReport, renderPromotionAdr, normalizePromotionState, nextPromotionState, globMatch, promotionAdrRelPath, DEFAULT_WINDOW_DAYS, DEFAULT_PERIODS, MAX_CONTENT_FETCHES, BUILTIN_COVERAGE, decideProvenance, isInsideTree, signManifest, verifyManifest, listSignablePackFiles, assertKeyOutsideTree, decidePublishGate, collectPackageFacts, planReleaseGates, selectAffectedPackages, classifyGateExecutions, buildFailureIssue, buildReleaseNotes, releaseTagName, firstOutputLine, formatPublishError, MANIFEST_NAME, SBOM_NAME, buildArchitectureMap, renderMapHuman, findArchitectureDrift, renderDriftReport, scanWorkspacePackages, loadSubsystemManifest, loadProductVision, checkFeatureAgainstArchitecture, renderArchCheck, planProjectSkills, guidanceForStage, renderInjectionReport, analyzeCorpus, renderRakeReport, renderCriticSection, rakeAsLesson, rakeReward, DEFAULT_RAKE_THRESHOLDS, streamSessionEvents, findLatestTranscript, detectProcessRakes, buildRetro, renderRetro, retroLessonText, PROCESS_SIGNATURES, RETRO_DOMAIN, scanForSetup, buildSetupPlan, scaffoldFromSpec, renderScaffoldPreview, readExistingForScaffold, assembleChallengeContext, buildChallengeBrief, planDiscriminationCheck, classifyDiscrimination, pickAdversaryModel, CHALLENGE_QUESTIONS, loadOutcomes, renderOutcomes, statsForKey, selectAutoCost, recordProvisional, finalizeOutcome, COST_LADDER, splitScenarios, budgetPlan, selectWinner, proseScopeOk, renderProseDiff, readScenarioIds, DEFAULT_MAX_JUDGE_RUNS, collectDeliveryFacts, planDeliveryCheck, renderDeliveryBrief, classifyDelivery, isUsablePlaneResult, renderDeliveryReview, scanSkillsLayout, parseInitFacts, verifyRegistration, buildContentProbePrompt, classifyContentProbe, renderContentProbe, findNonRegistrableSkillDirs, assembleCompoundingReport,
15
15
  // Cold-vs-warm EPOCH RUNNER (feature epoch-replay) — orchestrates + scores, never calls a model.
@@ -4686,11 +4686,50 @@ function runGuardEvaluation(root, op, text, overrideReason) {
4686
4686
  try {
4687
4687
  const rec = auditRecord(result, new Date().toISOString(), overrideReason !== undefined ? { reason: overrideReason } : undefined);
4688
4688
  mkdirSync(join(root, '.dz'), { recursive: true });
4689
- writeFileSync(join(root, '.dz', 'guard-audit.jsonl'), JSON.stringify(rec) + '\n', { flag: 'a' });
4689
+ const auditPath = join(root, '.dz', 'guard-audit.jsonl');
4690
+ // event-chain (ADR-001): seq + prevHash derived from the LAST LINE ONLY — this file is the
4691
+ // evidence base `dz guard promote` decides on, and a rewrite that loses or duplicates a record
4692
+ // must not be able to look intact. A tail that cannot be read starts a MARKED segment rather
4693
+ // than blocking the audit: the verdict is never held hostage to a broken log.
4694
+ writeFileSync(auditPath, appendChainedLines([rec], readLogTail(auditPath)), { flag: 'a' });
4690
4695
  }
4691
4696
  catch { /* audit is best-effort, never blocks the verdict */ }
4692
4697
  return result;
4693
4698
  }
4699
+ /**
4700
+ * The tail facts of an append-only log, read from its END — O(1) in the file size, which is what
4701
+ * lets the chain be extended on every append without a full-file scan (FR-2). Anything unreadable
4702
+ * yields {@link EMPTY_LOG_TAIL}; the caller then starts a marked segment rather than blocking.
4703
+ */
4704
+ function readLogTail(path) {
4705
+ let fd;
4706
+ try {
4707
+ if (!existsSync(path))
4708
+ return EMPTY_LOG_TAIL;
4709
+ fd = openSync(path, 'r');
4710
+ const size = fstatSync(fd).size;
4711
+ if (!Number.isFinite(size) || size <= 0)
4712
+ return EMPTY_LOG_TAIL;
4713
+ const want = Math.min(size, EVENT_CHAIN_TAIL_BYTES);
4714
+ const buf = Buffer.alloc(want);
4715
+ readSync(fd, buf, 0, want, size - want);
4716
+ return readTailInfo(buf.toString('utf-8'), { partial: want < size });
4717
+ }
4718
+ catch {
4719
+ // A read FAILURE is not an empty file (Codex re-QE LOW): EMPTY_LOG_TAIL means "there is
4720
+ // nothing", which lets the writer open an UNMARKED genesis on a file we merely failed to
4721
+ // read. An unreadable tail must force a marked reset, per the AM-6 contract.
4722
+ return { ...EMPTY_LOG_TAIL, unreadable: true };
4723
+ }
4724
+ finally {
4725
+ if (fd !== undefined) {
4726
+ try {
4727
+ closeSync(fd);
4728
+ }
4729
+ catch { /* nothing to do */ }
4730
+ }
4731
+ }
4732
+ }
4694
4733
  // ── `dz guard promote` (feature guard-promotion, scout idea #1) ─────────────────────────────────
4695
4734
  const PROMOTIONS_DIR = join('features', 'guard-promotion', 'promotions');
4696
4735
  const PROMOTION_STATE_FILE = join('.dz', 'promotion-state.json');
@@ -6019,7 +6058,19 @@ function cmdCompounding(options, flags, cwd, write) {
6019
6058
  catch {
6020
6059
  /* no audit yet */
6021
6060
  }
6022
- const report = assembleCompoundingReport({ lessons, usage, guard, nowTs: new Date().toISOString() });
6061
+ // The evidence logs themselves, verbatim: the report verifies their hash chains (feature
6062
+ // event-chain). Handing over the TEXT rather than a pre-computed verdict keeps one definition of
6063
+ // "the chain is intact" — a second copy here is how a gate and its report start disagreeing.
6064
+ const evidenceLogs = [];
6065
+ for (const rel of ['.dz/recall-usage.jsonl', '.dz/guard-audit.jsonl']) {
6066
+ try {
6067
+ evidenceLogs.push({ log: rel, text: readFileSync(join(root, ...rel.split('/')), 'utf-8') });
6068
+ }
6069
+ catch {
6070
+ /* absent log — reported by its own gate above, not invented here */
6071
+ }
6072
+ }
6073
+ const report = assembleCompoundingReport({ lessons, usage, guard, nowTs: new Date().toISOString(), evidenceLogs });
6023
6074
  if (json)
6024
6075
  write(JSON.stringify({ ...report, exitCode: 0 }, null, 2));
6025
6076
  else