@tangle-network/agent-eval 0.145.10 → 0.145.12

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 (52) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/analyst/index.d.ts +2 -2
  3. package/dist/analyst/index.js +3 -3
  4. package/dist/{benchmark-command-BDHW2AUg.js → benchmark-command-JSjVoAxT.js} +7 -7
  5. package/dist/{benchmark-command-BDHW2AUg.js.map → benchmark-command-JSjVoAxT.js.map} +1 -1
  6. package/dist/benchmarks/index.js +3 -3
  7. package/dist/campaign/index.js +5 -5
  8. package/dist/{campaign-B3GJpJ4h.js → campaign-Ct6r9oNe.js} +7 -7
  9. package/dist/{campaign-B3GJpJ4h.js.map → campaign-Ct6r9oNe.js.map} +1 -1
  10. package/dist/cli.js +1 -1
  11. package/dist/contract/index.d.ts.map +1 -1
  12. package/dist/contract/index.js +121 -22
  13. package/dist/contract/index.js.map +1 -1
  14. package/dist/{define-agent-eval-cSMEFzVu.js → define-agent-eval-BXusiVTQ.js} +3 -3
  15. package/dist/{define-agent-eval-cSMEFzVu.js.map → define-agent-eval-BXusiVTQ.js.map} +1 -1
  16. package/dist/{dspy-rlm-engine-BZ2aMZEd.js → dspy-rlm-engine-CqwhQQBw.js} +2 -2
  17. package/dist/{dspy-rlm-engine-BZ2aMZEd.js.map → dspy-rlm-engine-CqwhQQBw.js.map} +1 -1
  18. package/dist/exec-y-DCLqK7.js +204 -0
  19. package/dist/exec-y-DCLqK7.js.map +1 -0
  20. package/dist/{external-optimizer-process-BRE56woM.js → external-optimizer-process-D-9iX64j.js} +3 -3
  21. package/dist/{external-optimizer-process-BRE56woM.js.map → external-optimizer-process-D-9iX64j.js.map} +1 -1
  22. package/dist/{external-optimizer-subprocess-BhKYK0Jv.js → external-optimizer-subprocess-x5AbhAYX.js} +2 -2
  23. package/dist/{external-optimizer-subprocess-BhKYK0Jv.js.map → external-optimizer-subprocess-x5AbhAYX.js.map} +1 -1
  24. package/dist/index.js +5 -5
  25. package/dist/ledger-core/index.js +1 -1
  26. package/dist/{ledger-core-BmZt19oQ.js → ledger-core-DTae9rv_.js} +3 -2
  27. package/dist/ledger-core-DTae9rv_.js.map +1 -0
  28. package/dist/{llm-judge-DhiJqSjB.js → llm-judge-DqAkIQA1.js} +6 -5
  29. package/dist/{llm-judge-DhiJqSjB.js.map → llm-judge-DqAkIQA1.js.map} +1 -1
  30. package/dist/openapi.json +1 -1
  31. package/dist/{produced-state-BNyyud4g.js → produced-state-B9skdU8u.js} +2 -2
  32. package/dist/{produced-state-BNyyud4g.js.map → produced-state-B9skdU8u.js.map} +1 -1
  33. package/dist/{semantic-concept-judge-BiJxScqe.js → semantic-concept-judge-C6M-qOeb.js} +2 -2
  34. package/dist/{semantic-concept-judge-BiJxScqe.js.map → semantic-concept-judge-C6M-qOeb.js.map} +1 -1
  35. package/dist/{skillopt-optimization-method-C6JSfYxT.js → skillopt-optimization-method-CcWZBaSz.js} +4 -4
  36. package/dist/{skillopt-optimization-method-C6JSfYxT.js.map → skillopt-optimization-method-CcWZBaSz.js.map} +1 -1
  37. package/dist/steps-AmkT-GIM.d.ts +216 -0
  38. package/dist/steps-AmkT-GIM.d.ts.map +1 -0
  39. package/dist/trace-repair/index.d.ts +1 -21
  40. package/dist/trace-repair/index.d.ts.map +1 -1
  41. package/dist/trace-repair/index.js +10 -19
  42. package/dist/trace-repair/index.js.map +1 -1
  43. package/dist/trajectory-replay/index.d.ts +2 -2
  44. package/dist/trajectory-replay/index.js +2 -2
  45. package/docs/trace-repair-admission.md +134 -0
  46. package/docs/trajectory-replay.md +21 -1
  47. package/package.json +3 -3
  48. package/dist/exec-BLtYZdWo.js +0 -49
  49. package/dist/exec-BLtYZdWo.js.map +0 -1
  50. package/dist/ledger-core-BmZt19oQ.js.map +0 -1
  51. package/dist/steps-BArUxhna.d.ts +0 -51
  52. package/dist/steps-BArUxhna.d.ts.map +0 -1
@@ -2,7 +2,7 @@ import { t as DefaultVerdict } from "../verdict-E4eRNf7-.js";
2
2
  import { s as TraceStore } from "../store-CT9YIIve.js";
3
3
  import { i as TraceEmitter } from "../emitter-DGQGoLyj.js";
4
4
  import { i as CounterfactualRunner, t as CounterfactualContext } from "../counterfactual--bpysZF0.js";
5
- import { a as parseObservationOutput, i as isSubmitAction, n as SUBMIT_ACTION_SIGNATURE, o as parseRecordedReturncode, r as deriveFailureSignature, t as RecordedTrajectoryStep } from "../steps-BArUxhna.js";
5
+ import { _ as isSubmitAction, a as RecordedObservationKind, b as parseRecordedReturncode, c as SUBMIT_ACTION_SIGNATURE, d as classifyObservation, f as decodeRecordedTurns, g as isRecordedTimeout, h as isElidedField, i as RecordedFinalOutcome, l as TIMEOUT_OBSERVATION_MARKER, m as finalRecordedOutcome, n as FORMAT_ERROR_OBSERVATION_PREFIX, o as RecordedTrajectoryStep, p as deriveFailureSignature, r as RECORDED_ELISION_PATTERN, s as RecordedTrajectoryTurn, t as DecodedTrajectory, u as assertReplayableTrajectory, v as isSubmitOnlyAction, x as unreadableExitCount, y as parseObservationOutput } from "../steps-AmkT-GIM.js";
6
6
  //#region src/trajectory-replay/corpus.d.ts
7
7
  interface CorpusSpec {
8
8
  readonly name: string;
@@ -790,5 +790,5 @@ interface ReplayFindingResult {
790
790
  */
791
791
  declare function replayVerifyFinding(finding: AnalystReplayFinding, options: ReplayFindingOptions): Promise<ReplayFindingResult>;
792
792
  //#endregion
793
- export { type AnalystReplayFinding, type ArmExecutionResult, type CaseResources, type ChatCompletionCaller, type ChatOutcome, type ChatUsage, type CorpusSpec, type CwdSource, type DockerImagePreparerOptions, type EnumerationResult, type ExcludedCase, type FailedFixAttempt, type FindingReplaySource, type FindingReplayability, type FindingVerification, type FindingVerificationStatus, type FixArmExecution, type FixArmExecutor, type FixGenerationOutcome, type FixLoopAttemptRecord, type FixLoopOptions, type FixLoopResult, type FixPromptInput, type ImagePreparation, type ImagePreparer, type IncorrectStepsSubject, type IngestedTrajectory, PREFIX_DIVERGENCE_TOLERANCE_PCT, type PrefixDivergence, type PrefixDivergenceKind, type PrefixReplayResult, type RecordedTrajectoryStep, type ReplayArmVerdict, type ReplayBatchCaseRow, type ReplayBatchFixResult, type ReplayBatchOptions, type ReplayBatchReport, type ReplayExclusionReason, type ReplayExecBackend, type ReplayExecBackendFactory, type ReplayExecResult, type ReplayExecSession, type ReplayFindingOptions, type ReplayFindingResult, type ReplayVerdict, type ReplayVerifyOptions, type ReplayableCase, type ResolvedFindingReplay, type ResolvedReplayInvocation, type ResourceResolution, SUBMIT_ACTION_SIGNATURE, SandboxCounterfactualRunner, type SandboxCounterfactualRunnerOptions, type VerifiableFinding, type VerifyFindingsOptions, type VerifyFindingsRun, buildFixPrompt, buildRetryFixPrompt, classifyPrefixStep, classifyVerdict, clipText, countScriptCommands, deriveFailureSignature, derivedImageTag, dockerImagePreparer, enumerateReplayableCases, extractFixCommand, findingReplayStep, findingTrajectoryId, generateFixCommand, goldIncorrectSteps, ingestRecordedTrajectory, isSubmitAction, parseCorpusFlag, parseIncorrectStepsSubject, parseObservationOutput, parseRecordedReturncode, readFindingsFile, readLabelEntries, renderBatchReport, renderVerifiedFindingsSection, replayVerify, replayVerifyFinding, resolveCaseResources, resolveFindingInvocation, resolveFindingReplayability, runFixLoop, runReplayBatch, seededSample, summarizePrefixReplay, verifyFindings, wrapActionForExec };
793
+ export { type AnalystReplayFinding, type ArmExecutionResult, type CaseResources, type ChatCompletionCaller, type ChatOutcome, type ChatUsage, type CorpusSpec, type CwdSource, type DecodedTrajectory, type DockerImagePreparerOptions, type EnumerationResult, type ExcludedCase, FORMAT_ERROR_OBSERVATION_PREFIX, type FailedFixAttempt, type FindingReplaySource, type FindingReplayability, type FindingVerification, type FindingVerificationStatus, type FixArmExecution, type FixArmExecutor, type FixGenerationOutcome, type FixLoopAttemptRecord, type FixLoopOptions, type FixLoopResult, type FixPromptInput, type ImagePreparation, type ImagePreparer, type IncorrectStepsSubject, type IngestedTrajectory, PREFIX_DIVERGENCE_TOLERANCE_PCT, type PrefixDivergence, type PrefixDivergenceKind, type PrefixReplayResult, RECORDED_ELISION_PATTERN, type RecordedFinalOutcome, type RecordedObservationKind, type RecordedTrajectoryStep, type RecordedTrajectoryTurn, type ReplayArmVerdict, type ReplayBatchCaseRow, type ReplayBatchFixResult, type ReplayBatchOptions, type ReplayBatchReport, type ReplayExclusionReason, type ReplayExecBackend, type ReplayExecBackendFactory, type ReplayExecResult, type ReplayExecSession, type ReplayFindingOptions, type ReplayFindingResult, type ReplayVerdict, type ReplayVerifyOptions, type ReplayableCase, type ResolvedFindingReplay, type ResolvedReplayInvocation, type ResourceResolution, SUBMIT_ACTION_SIGNATURE, SandboxCounterfactualRunner, type SandboxCounterfactualRunnerOptions, TIMEOUT_OBSERVATION_MARKER, type VerifiableFinding, type VerifyFindingsOptions, type VerifyFindingsRun, assertReplayableTrajectory, buildFixPrompt, buildRetryFixPrompt, classifyObservation, classifyPrefixStep, classifyVerdict, clipText, countScriptCommands, decodeRecordedTurns, deriveFailureSignature, derivedImageTag, dockerImagePreparer, enumerateReplayableCases, extractFixCommand, finalRecordedOutcome, findingReplayStep, findingTrajectoryId, generateFixCommand, goldIncorrectSteps, ingestRecordedTrajectory, isElidedField, isRecordedTimeout, isSubmitAction, isSubmitOnlyAction, parseCorpusFlag, parseIncorrectStepsSubject, parseObservationOutput, parseRecordedReturncode, readFindingsFile, readLabelEntries, renderBatchReport, renderVerifiedFindingsSection, replayVerify, replayVerifyFinding, resolveCaseResources, resolveFindingInvocation, resolveFindingReplayability, runFixLoop, runReplayBatch, seededSample, summarizePrefixReplay, unreadableExitCount, verifyFindings, wrapActionForExec };
794
794
  //# sourceMappingURL=index.d.ts.map
@@ -3,7 +3,7 @@ import { t as TraceEmitter } from "../emitter-CPBAhxum.js";
3
3
  import { t as runCounterfactual } from "../counterfactual-lDfCx0Uz.js";
4
4
  import { n as InMemoryTraceStore } from "../store-DNe_Uv1Q.js";
5
5
  import { t as packageVersion } from "../package-version-D7lQHt_-.js";
6
- import { a as parseObservationOutput, i as isSubmitAction, n as SUBMIT_ACTION_SIGNATURE, o as parseRecordedReturncode, r as deriveFailureSignature, t as wrapActionForExec } from "../exec-BLtYZdWo.js";
6
+ import { _ as unreadableExitCount, a as TIMEOUT_OBSERVATION_MARKER, c as decodeRecordedTurns, d as isElidedField, f as isRecordedTimeout, g as parseRecordedReturncode, h as parseObservationOutput, i as SUBMIT_ACTION_SIGNATURE, l as deriveFailureSignature, m as isSubmitOnlyAction, n as FORMAT_ERROR_OBSERVATION_PREFIX, o as assertReplayableTrajectory, p as isSubmitAction, r as RECORDED_ELISION_PATTERN, s as classifyObservation, t as wrapActionForExec, u as finalRecordedOutcome } from "../exec-y-DCLqK7.js";
7
7
  import { createHash } from "node:crypto";
8
8
  import { appendFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
9
9
  import { join } from "node:path";
@@ -2145,6 +2145,6 @@ function readFindingsFile(path) {
2145
2145
  return array;
2146
2146
  }
2147
2147
  //#endregion
2148
- export { PREFIX_DIVERGENCE_TOLERANCE_PCT, SUBMIT_ACTION_SIGNATURE, SandboxCounterfactualRunner, buildFixPrompt, buildRetryFixPrompt, classifyPrefixStep, classifyVerdict, clipText, countScriptCommands, deriveFailureSignature, derivedImageTag, dockerImagePreparer, enumerateReplayableCases, extractFixCommand, findingReplayStep, findingTrajectoryId, generateFixCommand, goldIncorrectSteps, ingestRecordedTrajectory, isSubmitAction, parseCorpusFlag, parseIncorrectStepsSubject, parseObservationOutput, parseRecordedReturncode, readFindingsFile, readLabelEntries, renderBatchReport, renderVerifiedFindingsSection, replayVerify, replayVerifyFinding, resolveCaseResources, resolveFindingInvocation, resolveFindingReplayability, runFixLoop, runReplayBatch, seededSample, summarizePrefixReplay, verifyFindings, wrapActionForExec };
2148
+ export { FORMAT_ERROR_OBSERVATION_PREFIX, PREFIX_DIVERGENCE_TOLERANCE_PCT, RECORDED_ELISION_PATTERN, SUBMIT_ACTION_SIGNATURE, SandboxCounterfactualRunner, TIMEOUT_OBSERVATION_MARKER, assertReplayableTrajectory, buildFixPrompt, buildRetryFixPrompt, classifyObservation, classifyPrefixStep, classifyVerdict, clipText, countScriptCommands, decodeRecordedTurns, deriveFailureSignature, derivedImageTag, dockerImagePreparer, enumerateReplayableCases, extractFixCommand, finalRecordedOutcome, findingReplayStep, findingTrajectoryId, generateFixCommand, goldIncorrectSteps, ingestRecordedTrajectory, isElidedField, isRecordedTimeout, isSubmitAction, isSubmitOnlyAction, parseCorpusFlag, parseIncorrectStepsSubject, parseObservationOutput, parseRecordedReturncode, readFindingsFile, readLabelEntries, renderBatchReport, renderVerifiedFindingsSection, replayVerify, replayVerifyFinding, resolveCaseResources, resolveFindingInvocation, resolveFindingReplayability, runFixLoop, runReplayBatch, seededSample, summarizePrefixReplay, unreadableExitCount, verifyFindings, wrapActionForExec };
2149
2149
 
2150
2150
  //# sourceMappingURL=index.js.map
@@ -58,6 +58,140 @@ It is on the rejected decisions too, so a reader of an artifact can tell which c
58
58
  Divergence is `divergences / prefixExecuted`, and the threshold admits a row sitting exactly on it.
59
59
  A replay that executed fewer steps than the recording holds is excluded as `prefix-replay-truncated` before that ratio is read, because a truncated run computes divergence over the steps it did reach and a short replay would look perfect.
60
60
 
61
+ ## Reading the recording
62
+
63
+ Every condition above is asked of a recording, so a row is admitted or not by what a decoder can read out of it.
64
+ [`src/trajectory-replay/steps.ts`](../src/trajectory-replay/steps.ts) is that decoder, and it is the only one.
65
+
66
+ The published Terminal-Bench-2 dump holds **turns**, not steps.
67
+ A turn carries an observation in one of four recorded shapes, plus two absences — no observation at all, and a shape this grammar does not know.
68
+ Only the first shape carries an exit status.
69
+
70
+ | observation shape | what it means | carries an exit status |
71
+ | --- | --- | --- |
72
+ | `<returncode>N</returncode><output>…</output>` | a command ran | yes |
73
+ | `The last command <command>…</command> timed out and has been killed.` | the environment killed the command at its bound | no — the outcome is `killed` |
74
+ | `Please always provide EXACTLY ONE action in triple backticks, found N actions.` | the scaffold rejected the turn | no — nothing ran |
75
+ | `$3a` | the dump dropped the string | no — and nothing recovers it |
76
+
77
+ Three rules follow, and each one costs rows when it is missing.
78
+
79
+ **A command keeps the observation of its own turn.**
80
+ Collecting commands and observations into two lists and zipping them looks right and is not.
81
+ A rejected turn carries an observation and usually no command, so from the first rejected turn onward every observation is read against a different command than the one that produced it.
82
+ 1,654 turns in the certified population are rejected turns, and they shift 643 of 2,727 rows.
83
+
84
+ **A rejected turn is never a step, even when the dump kept a command for it.**
85
+ The scaffold rejects a turn holding several bash blocks and runs none of them, while the dump keeps one block in the command field.
86
+ 494 turns carry that pair.
87
+ Replaying the field would execute a command the recorded run did not, which is a worse corpus than a smaller one.
88
+
89
+ A rejected turn is recognised by its observation, so a rejected turn whose observation the dump elided reads as an executed command.
90
+ Nothing in the dump separates the two, and the defence is per row rather than per turn: the unknown-returncode ratio bounds how much of a row may be unreadable, and a row with no elided observation cannot hold the case at all.
91
+
92
+ **A trailing step that echoes the sentinel and nothing else is the end of the transcript, not a gap in it.**
93
+ The scaffold records an observation only when it hands one back to the model, and `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT` ends the run.
94
+ 2,312 of 2,727 rows end that way.
95
+ Reading that turn as the trajectory's last step reports no exit status for 85% of the corpus.
96
+ Echoing the sentinel changes no state, so the recorded end state is the state the step before it left, and that step's exit is the row's final return code.
97
+
98
+ The test is the whole action, never a substring.
99
+ 131 of those 2,312 runs end on a command that writes files or edits them and then echoes the sentinel.
100
+ Dropping such a step would remove the run's last state change from the replay; keeping it leaves its exit unknown, which is what the row reports.
101
+
102
+ The elision marker is a **hexadecimal** counter over the row's strings in serialization order.
103
+ A decimal-only pattern (`^\$\d+$`) reads `$3a` as command text: 19,266 of 107,989 recorded commands are elided, and the decimal pattern sees 11,651 of them.
104
+
105
+ The counter is a position, not a key.
106
+ Across all 2,292 rows that hold a marker, the markers are strictly ascending and never repeat, and the first is always `$32` or `$33`.
107
+ A shell command that happened to look like a marker would repeat one or arrive out of order; none does, so the pattern has no false positive in this corpus and no marker maps back to text.
108
+ An elided command is unrecoverable and the row holding it is rejected.
109
+
110
+ ## What the decoder recovers, and what it refuses
111
+
112
+ Measured over the 2,578 rows the earlier decoder rejected at the replayable stage, on the 46 certified tasks.
113
+
114
+ | class | rows | share | recovery |
115
+ | --- | --- | --- | --- |
116
+ | commands elided by the dump | 1,327 | 51.5% | refused — no dictionary maps the marker back |
117
+ | run ended on the submit sentinel | 581 | 22.5% | exact — the last executed command's own returncode |
118
+ | ended on the sentinel AND shifted by a rejected turn | 308 | 11.9% | exact |
119
+ | no executed command after decoding | 119 | 4.6% | refused — the run held only rejected turns or the sentinel |
120
+ | last executed command recorded no observation | 94 | 3.6% | refused — it echoed the sentinel after doing work, or its own text was elided |
121
+ | final observation elided by the dump | 85 | 3.3% | refused |
122
+ | shifted by a rejected turn | 47 | 1.8% | exact |
123
+ | last command killed at its timeout | 14 | 0.5% | read exactly as `killed`, then excluded as `signal-kill` |
124
+ | observation shape the grammar cannot read | 2 | 0.1% | refused |
125
+ | phantom command from a rejected turn | 1 | 0.0% | exact |
126
+
127
+ 937 rows are recovered exactly — 36.3% of the 2,578 — and every refused class stays refused.
128
+ The 14 killed rows read their outcome exactly too, and are excluded at the stratum stage rather than counted here.
129
+
130
+ The population is one scaffold: `mini-swe-agent`.
131
+ No class is another scaffold's transcript format, because no other scaffold's rows enter this funnel.
132
+ `terminus-2`, `openhands`, `codex` and `claude-code` are pinned for later and are 0% of these 2,578.
133
+
134
+ The correction runs in both directions.
135
+ Six of the 142 rows the earlier decoder admitted carry a command the dump elided with a marker its pattern did not match, and one of those six is in the sealed 16.
136
+ Replaying it would have run the literal string `$3a` as a shell command.
137
+
138
+ ## The measured funnel
139
+
140
+ Certified tasks, `mini-swe-agent`, recorded reward 0.
141
+ The other gates are unchanged: the end-state screen, the image-digest pin, and the oracle-determinism refusal all keep their thresholds.
142
+
143
+ The funnel opens at 2,601 rather than the 2,727 rows the dump holds for these tasks.
144
+ 126 rows executed no command at all — every turn was rejected, or the run's only command was the sentinel — so they carry no prefix to replay.
145
+ [`tb-corpus-decode.json`](../benchmarks/trace-repair/tb-corpus-decode.json) records that count with the shard list and the duckdb version that produced it.
146
+
147
+ | stage | entering | excluded | remaining |
148
+ | --- | --- | --- | --- |
149
+ | `certified-deterministic-oracle` | 2,601 | 0 | 2,601 |
150
+ | `replayable-commands-and-final-returncode` | 2,601 | 1,528 | 1,073 |
151
+ | `stratum-carries-a-repairable-failure` | 1,073 | 3 | 1,070 |
152
+ | `unknown-returncode-ratio-at-most-25pct` | 1,070 | 126 | 944 |
153
+ | `recorded-commands-at-most-25` | 944 | 164 | 780 |
154
+ | `image-pinned-by-digest` | 780 | 0 | 780 |
155
+ | `one-row-per-recorded-trial` | 780 | 172 | 608 |
156
+ | `recorded-end-state-fails-its-own-suite` | 608 | 324 | 284 |
157
+ | `prefix-divergence-at-most-10pct` | 284 | 66 | 218 |
158
+ | `not-exposed-by-the-mechanism-pilot` | 218 | 0 | 218 |
159
+ | `task-carries-at-least-two-rows` | 218 | 2 | 216 |
160
+
161
+ 216 rows in 22 task clusters, against 16 rows in 8 clusters before.
162
+ Simulated power rises from 0.19 to 0.92 at a per-row effect of 0.10, and the registered 0.80 floor is cleared.
163
+
164
+ Three of these stages are new and every one of them removes rows.
165
+
166
+ `one-row-per-recorded-trial`: 885 `mini-swe-agent` trials appear in the dump twice, under an empty and a populated trial id, and the two copies are the same recorded run.
167
+ A cluster holding both reads one trajectory as two independent rows.
168
+
169
+ `stratum-carries-a-repairable-failure`: a row ending on a command the environment stopped cannot be repaired by substituting a command, which is why `ADMISSION_CONFIG_DEFAULTS.admitStrata` omits `signal-kill`.
170
+
171
+ `image-pinned-by-digest` reads the checked-in lock, not one machine's docker store.
172
+ A gate on local presence reports a different denominator after an image eviction, and the same funnel run twice on the same corpus gave 388 rows and then 75.
173
+ Pulling is a prerequisite of execution: a row nothing ran on carries no end-state verdict and leaves at the next stage as unscreened, which is where the 324 exclusions there come from.
174
+
175
+ `prefix-divergence-at-most-10pct`: condition 1, which the earlier design waived and measured without acting on.
176
+ The measurement says the waiver was wrong. 303 rows were screened by replaying the recorded prefix and grading the state it left; 69.2% replay with no divergence at all and 76.9% at or under the threshold.
177
+ One row's end-state verdict is not reproducible — `qemu-startup__giTPHxJ` graded pass under the pilot and fail under two later screens — and it carries 1 divergence over 8 replayed steps, which is above the threshold.
178
+ The gate that was waived is the one that removes it.
179
+
180
+ ## When the corpus reward and the task's own suite disagree
181
+
182
+ A row recorded with reward 0 can still pass the task's held-out suite on its replayed end state, and such a row cannot measure a repair because every arm wins it for free.
183
+
184
+ Over 303 screened rows the disagreement rate is **1 row, 0.33%**, and it is `qemu-startup__PnXK6EH`: measured passing twice, in two independent screens 2 hours apart.
185
+ Every task other than `qemu-startup` is 0 of 288. `qemu-startup` alone is 1 of 17, 5.9%.
186
+
187
+ The screen catches that row and does not catch the second `qemu-startup` case.
188
+ `qemu-startup__giTPHxJ` graded pass once and fail twice over three gradings of the same replayed prefix, so one screening grade of it is a draw rather than a measurement.
189
+ The task's suite certified stable on byte-identical state; a replay builds a fresh container, and this task boots a virtual machine, so identical bytes is not what a replay reproduces.
190
+ The divergence gate removes that row, which is the reason it is registered rather than waived.
191
+
192
+ A screen that hits a boundary failure produces no verdict at all.
193
+ 20 of the 323 rows the screen opened a container for ended that way, and recording their default `false` would have admitted every one on a check that never ran.
194
+
61
195
  ## The population split
62
196
 
63
197
  The corpus assay measured the final recorded return code of every admitted row.
@@ -88,7 +88,7 @@ A high divergence rate is a finding about replay fidelity, not a harness error.
88
88
 
89
89
  | Module | Role |
90
90
  |---|---|
91
- | `steps` | The recorded step and its `<returncode>` / `<output>` grammar. |
91
+ | `steps` | The recorded grammar: turns to steps, the four observation shapes, the final outcome. |
92
92
  | `exec` | The execution boundary and mini-SWE `/bin/sh` command wrapping. |
93
93
  | `verify` | One case: prefix replay, arm A, optional arm B, `ReplayVerdict`. |
94
94
  | `corpus` | Labeled corpora to replayable cases, with a reason for every exclusion. |
@@ -97,6 +97,26 @@ A high divergence rate is a finding about replay fidelity, not a harness error.
97
97
  | `batch` | Every replayable case to replayability and fix-flip rates. |
98
98
  | `wire` / `findings` | One analyst finding to an executed, receipted proof. |
99
99
 
100
+ ## The recorded grammar
101
+
102
+ `steps` decodes turns into steps, and it is the only place that grammar is read.
103
+
104
+ A turn is not a step.
105
+ The system prompt, the task statement and every turn the scaffold rejected are turns that executed nothing.
106
+ `decodeRecordedTurns` keeps the turns that ran a command and pairs each one with the observation of its own turn.
107
+ A turn the scaffold rejected is dropped even when the published dump kept a command for it, because the scaffold ran none of the blocks that turn held.
108
+
109
+ `classifyObservation` names four recorded shapes plus two absences: a command result carrying `<returncode>`, a timeout notice, a format-error notice, an elision marker `$<hex>`, no observation at all, and a shape this grammar does not know.
110
+ Only a command result carries an exit status.
111
+
112
+ `finalRecordedOutcome` reads the last executed command.
113
+ It separates `killed` — the environment stopped the command and wrote a notice instead of an exit status — from `unreadable`, which names the shape that blocked the read.
114
+ A run that ended on the submit sentinel has no observation for that turn, because the scaffold records one only when it hands one back to the model; `decodeRecordedTurns` reports the sentinel rather than reading it as the trajectory's last step.
115
+
116
+ An elided field is unrecoverable.
117
+ The marker is a counter, not a key: the same marker carries different text in different rows, and the dump ships no dictionary.
118
+ A row whose commands cannot be reconstructed exactly as recorded is rejected, because a replay that guesses is worse than a smaller corpus.
119
+
100
120
  ## Honest limits
101
121
 
102
122
  Only trajectories that record their image are replayable.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-eval",
3
- "version": "0.145.10",
3
+ "version": "0.145.12",
4
4
  "description": "Evaluate and improve AI agents from runs, traces, judges, and feedback. Compare candidates, cluster failures, measure lift, and gate releases.",
5
5
  "homepage": "https://github.com/tangle-network/agent-eval#readme",
6
6
  "repository": {
@@ -180,8 +180,8 @@
180
180
  "dependencies": {
181
181
  "@asteasolutions/zod-to-openapi": "^9.1.0",
182
182
  "@hono/node-server": "^2.0.12",
183
- "@tangle-network/agent-core": "0.7.1",
184
- "@tangle-network/agent-interface": "0.49.0",
183
+ "@tangle-network/agent-core": "0.8.0",
184
+ "@tangle-network/agent-interface": "0.52.0",
185
185
  "@tangle-network/agent-trace-contract": "^1.0.2",
186
186
  "hono": "^4.12.32",
187
187
  "linear-sum-assignment": "1.0.9",
@@ -1,49 +0,0 @@
1
- //#region src/trajectory-replay/steps.ts
2
- /** Recorded returncode of a step, or null when the observation carries none. */
3
- function parseRecordedReturncode(observation) {
4
- if (!observation) return null;
5
- const m = /<returncode>(-?\d+)<\/returncode>/.exec(observation);
6
- return m ? Number(m[1]) : null;
7
- }
8
- /** Text between the observation's <output> tags, or the raw observation when
9
- * the tags are absent. */
10
- function parseObservationOutput(observation) {
11
- if (!observation) return "";
12
- const m = /<output>\n?([\s\S]*?)\n?<\/output>/.exec(observation);
13
- return m ? m[1] : observation;
14
- }
15
- /**
16
- * Stable failure-signature candidate: the first line of the recorded output
17
- * that contains the word "error". Null when no such line exists — a verdict
18
- * then falls back to returncode-only matching and says so.
19
- * Pass an explicit signature to override (compiler quote glyphs vary with
20
- * locale, so a hand-picked ASCII substring is often more robust).
21
- */
22
- function deriveFailureSignature(observation) {
23
- const line = parseObservationOutput(observation).split("\n").find((l) => /\berror\b/i.test(l));
24
- return line ? line.trim().slice(0, 200) : null;
25
- }
26
- /** mini-SWE's end-of-run submit convention: the agent echoes this sentinel
27
- * and dumps the diff. A label on this step marks a bad SUBMIT DECISION, not a
28
- * failed command — there is no executable failure to reproduce, so it is
29
- * never a counterfactual replay target. */
30
- const SUBMIT_ACTION_SIGNATURE = "COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT";
31
- function isSubmitAction(action) {
32
- return action.includes(SUBMIT_ACTION_SIGNATURE);
33
- }
34
- //#endregion
35
- //#region src/trajectory-replay/exec.ts
36
- /**
37
- * mini-SWE runs every action as a fresh /bin/sh subshell from a fixed
38
- * workdir. Reproduce that exactly — and stay quote-proof for arbitrary
39
- * recorded actions — by piping the base64 of the action into `sh` after
40
- * cd-ing to the workdir. Exit code is sh's, i.e. the action's.
41
- */
42
- function wrapActionForExec(action, cwd) {
43
- const b64 = Buffer.from(action, "utf8").toString("base64");
44
- return `cd ${`'${cwd.replaceAll("'", `'\\''`)}'`} && printf %s ${b64} | base64 -d | sh`;
45
- }
46
- //#endregion
47
- export { parseObservationOutput as a, isSubmitAction as i, SUBMIT_ACTION_SIGNATURE as n, parseRecordedReturncode as o, deriveFailureSignature as r, wrapActionForExec as t };
48
-
49
- //# sourceMappingURL=exec-BLtYZdWo.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"exec-BLtYZdWo.js","names":[],"sources":["../src/trajectory-replay/steps.ts","../src/trajectory-replay/exec.ts"],"sourcesContent":["/**\n * Recorded shell-trajectory steps and the observation grammar they carry.\n *\n * A recorded trajectory is the action/observation sequence an agent actually\n * ran. Scaffolds that execute one shell command per step (mini-SWE and the\n * CodeTracer-normalized corpora built from it) tag each observation with the\n * command's returncode and its combined output:\n *\n * <returncode>2</returncode>\n * <output>\n * …command output…\n * </output>\n *\n * The parsers here are the only place that grammar is decoded. Everything\n * downstream — replay verdicts, corpus enumeration, fix prompts — reads the\n * returncode, the output, and the failure signature through these functions.\n */\n\n/**\n * One step of a recorded shell trajectory. Structural: any richer step record\n * (file refs, thinking text, tool type) satisfies it.\n */\nexport interface RecordedTrajectoryStep {\n /** 1-based position in the trajectory. */\n readonly step_id: number\n readonly action: string\n /** Null when the step recorded no observation (terminal submit steps). */\n readonly observation: string | null\n}\n\n/** Recorded returncode of a step, or null when the observation carries none. */\nexport function parseRecordedReturncode(observation: string | null): number | null {\n if (!observation) return null\n const m = /<returncode>(-?\\d+)<\\/returncode>/.exec(observation)\n return m ? Number(m[1]) : null\n}\n\n/** Text between the observation's <output> tags, or the raw observation when\n * the tags are absent. */\nexport function parseObservationOutput(observation: string | null): string {\n if (!observation) return ''\n const m = /<output>\\n?([\\s\\S]*?)\\n?<\\/output>/.exec(observation)\n return m ? m[1]! : observation\n}\n\n/**\n * Stable failure-signature candidate: the first line of the recorded output\n * that contains the word \"error\". Null when no such line exists — a verdict\n * then falls back to returncode-only matching and says so.\n * Pass an explicit signature to override (compiler quote glyphs vary with\n * locale, so a hand-picked ASCII substring is often more robust).\n */\nexport function deriveFailureSignature(observation: string | null): string | null {\n const line = parseObservationOutput(observation)\n .split('\\n')\n .find((l) => /\\berror\\b/i.test(l))\n return line ? line.trim().slice(0, 200) : null\n}\n\n/** mini-SWE's end-of-run submit convention: the agent echoes this sentinel\n * and dumps the diff. A label on this step marks a bad SUBMIT DECISION, not a\n * failed command — there is no executable failure to reproduce, so it is\n * never a counterfactual replay target. */\nexport const SUBMIT_ACTION_SIGNATURE = 'COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT'\n\nexport function isSubmitAction(action: string): boolean {\n return action.includes(SUBMIT_ACTION_SIGNATURE)\n}\n","/**\n * The execution boundary replay runs across.\n *\n * A replay needs one thing from its environment: a session that runs a shell\n * command inside the trajectory's own image and reports the exit code and\n * output. That is the whole contract. Concrete backends — a sandbox platform\n * client, a docker exec, an SSH shell — live with the consumer that owns the\n * infrastructure, so this package depends on no sandbox client.\n */\n\nexport interface ReplayExecResult {\n exitCode: number\n stdout: string\n stderr: string\n}\n\nexport interface ReplayExecSession {\n exec(command: string, timeoutMs: number): Promise<ReplayExecResult>\n close(): Promise<void>\n}\n\nexport interface ReplayExecBackend {\n /** One fresh execution environment per call; the caller closes it. */\n open(): Promise<ReplayExecSession>\n}\n\n/** Builds a backend pinned to one image. Callers that resolve images\n * internally (batch, corpus wire, finding verification) take this instead of\n * a backend, so every case runs against its own image. */\nexport type ReplayExecBackendFactory = (image: string) => ReplayExecBackend\n\n/**\n * mini-SWE runs every action as a fresh /bin/sh subshell from a fixed\n * workdir. Reproduce that exactly — and stay quote-proof for arbitrary\n * recorded actions — by piping the base64 of the action into `sh` after\n * cd-ing to the workdir. Exit code is sh's, i.e. the action's.\n */\nexport function wrapActionForExec(action: string, cwd: string): string {\n const b64 = Buffer.from(action, 'utf8').toString('base64')\n const quotedCwd = `'${cwd.replaceAll(\"'\", `'\\\\''`)}'`\n return `cd ${quotedCwd} && printf %s ${b64} | base64 -d | sh`\n}\n"],"mappings":";;AA+BA,SAAgB,wBAAwB,aAA2C;CACjF,IAAI,CAAC,aAAa,OAAO;CACzB,MAAM,IAAI,oCAAoC,KAAK,WAAW;CAC9D,OAAO,IAAI,OAAO,EAAE,EAAE,IAAI;AAC5B;;;AAIA,SAAgB,uBAAuB,aAAoC;CACzE,IAAI,CAAC,aAAa,OAAO;CACzB,MAAM,IAAI,qCAAqC,KAAK,WAAW;CAC/D,OAAO,IAAI,EAAE,KAAM;AACrB;;;;;;;;AASA,SAAgB,uBAAuB,aAA2C;CAChF,MAAM,OAAO,uBAAuB,WAAW,CAAC,CAC7C,MAAM,IAAI,CAAC,CACX,MAAM,MAAM,aAAa,KAAK,CAAC,CAAC;CACnC,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,GAAG,IAAI;AAC5C;;;;;AAMA,MAAa,0BAA0B;AAEvC,SAAgB,eAAe,QAAyB;CACtD,OAAO,OAAO,SAAS,uBAAuB;AAChD;;;;;;;;;AC9BA,SAAgB,kBAAkB,QAAgB,KAAqB;CACrE,MAAM,MAAM,OAAO,KAAK,QAAQ,MAAM,CAAC,CAAC,SAAS,QAAQ;CAEzD,OAAO,MAAM,IADS,IAAI,WAAW,KAAK,OAAO,EAAE,GAC5B,gBAAgB,IAAI;AAC7C"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ledger-core-BmZt19oQ.js","names":[],"sources":["../src/ledger-core/atomic-file-lock.ts","../src/concurrency.ts","../src/ledger-core/journal-file.ts","../src/ledger-core/trusted-head.ts","../src/ledger-core/journal.ts"],"sourcesContent":["/** Crash-safe filesystem lock shared by campaign persistence and run exclusion. */\n\nimport { randomUUID } from 'node:crypto'\nimport {\n closeSync,\n constants,\n existsSync,\n fsyncSync,\n linkSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs'\nimport { hostname } from 'node:os'\n\nexport interface AtomicFileLockOwner {\n readonly pid: number\n readonly host: string\n readonly nonce: string\n}\n\nexport interface AtomicFileLock {\n readonly owner: AtomicFileLockOwner\n release(): void\n}\n\nexport type AtomicFileLockUnavailable =\n | { readonly acquired: false; readonly reason: 'held'; readonly holder: AtomicFileLockOwner }\n | { readonly acquired: false; readonly reason: 'recovery' }\n\nexport type AtomicFileLockAcquisition =\n | { readonly acquired: true; readonly lock: AtomicFileLock }\n | AtomicFileLockUnavailable\n\nexport interface AtomicFileLockOptions {\n readonly lockPath: string\n readonly pid?: number\n}\n\nexport class AtomicFileLockError extends Error {\n override readonly name = 'AtomicFileLockError'\n}\n\ntype OwnerState =\n | { readonly state: 'missing' }\n | { readonly state: 'stale'; readonly owner: AtomicFileLockOwner }\n | { readonly state: 'held'; readonly owner: AtomicFileLockOwner }\n\n/** Report whether another lock path prevents acquisition without modifying it. */\nexport function probeAtomicFileLock(\n options: AtomicFileLockOptions,\n): AtomicFileLockUnavailable | null {\n if (existsSync(recoveryPath(options.lockPath))) return { acquired: false, reason: 'recovery' }\n const state = ownerState(options.lockPath)\n if (state.state === 'held') {\n return { acquired: false, reason: 'held', holder: state.owner }\n }\n return null\n}\n\n/**\n * Try to acquire a complete, uniquely-owned lock inode.\n *\n * A hard link publishes fully-written owner metadata atomically. Stale-owner\n * removal is serialized so one reclaimer cannot delete a new owner's lock.\n */\nexport function tryAcquireAtomicFileLock(\n options: AtomicFileLockOptions,\n): AtomicFileLockAcquisition {\n const pid = options.pid ?? process.pid\n if (!Number.isSafeInteger(pid) || pid <= 0) {\n throw new AtomicFileLockError('atomic file lock pid must be a positive integer')\n }\n const owner: AtomicFileLockOwner = { pid, host: hostname(), nonce: randomUUID() }\n for (let attempt = 0; attempt < 8; attempt += 1) {\n if (existsSync(recoveryPath(options.lockPath))) {\n const recovery = tryAcquireRecoveryLease(options.lockPath, owner, attempt)\n if (!recovery) return { acquired: false, reason: 'recovery' }\n try {\n return recoverLock(options.lockPath, owner, attempt)\n } finally {\n recovery.release()\n }\n }\n if (tryLinkOwner(options.lockPath, owner, `acquire.${attempt}`)) {\n return acquired(options.lockPath, owner)\n }\n\n const holder = ownerState(options.lockPath)\n if (holder.state === 'missing') continue\n if (holder.state === 'held') {\n return { acquired: false, reason: 'held', holder: holder.owner }\n }\n\n const recovery = tryAcquireRecoveryLease(options.lockPath, owner, attempt)\n if (!recovery) return { acquired: false, reason: 'recovery' }\n try {\n return recoverLock(options.lockPath, owner, attempt)\n } finally {\n recovery.release()\n }\n }\n\n throw new AtomicFileLockError(`could not acquire atomic file lock ${options.lockPath}`)\n}\n\ninterface RecoveryLease {\n release(): void\n}\n\nfunction tryAcquireRecoveryLease(\n lockPath: string,\n owner: AtomicFileLockOwner,\n attempt: number,\n): RecoveryLease | null {\n let leasePath = recoveryPath(lockPath)\n const staleOwners: Array<{ path: string; owner: AtomicFileLockOwner }> = []\n\n for (let depth = 0; depth < 64; depth += 1) {\n const state = ownerState(leasePath)\n if (state.state === 'missing') {\n if (!tryLinkOwner(leasePath, owner, `reclaim.${attempt}.${depth}`)) continue\n return {\n release: () => {\n for (const stale of staleOwners) releaseOwnedPath(stale.path, stale.owner)\n releaseOwnedPath(leasePath, owner)\n },\n }\n }\n if (state.state === 'held') return null\n staleOwners.push({ path: leasePath, owner: state.owner })\n leasePath = `${leasePath}.next.${state.owner.nonce}`\n }\n\n throw new AtomicFileLockError(`atomic file lock recovery chain is too deep (${lockPath})`)\n}\n\nfunction recoverLock(\n lockPath: string,\n owner: AtomicFileLockOwner,\n attempt: number,\n): AtomicFileLockAcquisition {\n const current = ownerState(lockPath)\n if (current.state === 'held') {\n return { acquired: false, reason: 'held', holder: current.owner }\n }\n if (current.state === 'stale') {\n const tombstone = `${lockPath}.stale.${owner.nonce}.${attempt}`\n try {\n renameSync(lockPath, tombstone)\n unlinkSync(tombstone)\n } catch (error) {\n if (!isMissing(error)) throw error\n }\n }\n if (tryLinkOwner(lockPath, owner, `recovered.${attempt}`)) {\n return acquired(lockPath, owner)\n }\n const holder = ownerState(lockPath)\n if (holder.state === 'held') {\n return { acquired: false, reason: 'held', holder: holder.owner }\n }\n return { acquired: false, reason: 'recovery' }\n}\n\nfunction acquired(lockPath: string, owner: AtomicFileLockOwner): AtomicFileLockAcquisition {\n return {\n acquired: true,\n lock: {\n owner,\n release: () => releaseOwnedPath(lockPath, owner),\n },\n }\n}\n\nfunction tryLinkOwner(lockPath: string, owner: AtomicFileLockOwner, suffix: string): boolean {\n const ownerPath = `${lockPath}.${owner.pid}.${owner.nonce}.${suffix}.owner`\n const descriptor = openSync(\n ownerPath,\n constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,\n 0o600,\n )\n try {\n writeFileSync(descriptor, `${canonicalOwner(owner)}\\n`, 'utf8')\n fsyncSync(descriptor)\n } catch (error) {\n closeSync(descriptor)\n unlinkIfExists(ownerPath)\n throw error\n }\n closeSync(descriptor)\n\n try {\n linkSync(ownerPath, lockPath)\n return true\n } catch (error) {\n if (!isAlreadyExists(error)) throw error\n return false\n } finally {\n unlinkIfExists(ownerPath)\n }\n}\n\nfunction ownerState(lockPath: string): OwnerState {\n const owner = readOwner(lockPath)\n if (!owner) return { state: 'missing' }\n if (owner.host !== hostname()) return { state: 'held', owner }\n try {\n process.kill(owner.pid, 0)\n return { state: 'held', owner }\n } catch (error) {\n if (isNoSuchProcess(error)) return { state: 'stale', owner }\n return { state: 'held', owner }\n }\n}\n\nfunction readOwner(lockPath: string): AtomicFileLockOwner | undefined {\n let contents: string\n try {\n contents = readFileSync(lockPath, 'utf8').trim()\n } catch (error) {\n if (isMissing(error)) return undefined\n throw error\n }\n\n let value: unknown\n try {\n value = JSON.parse(contents)\n } catch (error) {\n throw invalidOwner(lockPath, error)\n }\n if (!isOwner(value)) throw invalidOwner(lockPath)\n return value\n}\n\nfunction isOwner(value: unknown): value is AtomicFileLockOwner {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false\n const record = value as Record<string, unknown>\n return (\n Object.keys(record).length === 3 &&\n Number.isSafeInteger(record.pid) &&\n Number(record.pid) > 0 &&\n typeof record.host === 'string' &&\n record.host.trim().length > 0 &&\n typeof record.nonce === 'string' &&\n record.nonce.trim().length > 0\n )\n}\n\nfunction invalidOwner(lockPath: string, cause?: unknown): AtomicFileLockError {\n const error = new AtomicFileLockError(\n `atomic file lock has an invalid owner (${lockPath}); refusing unsafe recovery`,\n )\n if (cause !== undefined) Object.defineProperty(error, 'cause', { value: cause })\n return error\n}\n\nfunction releaseOwnedPath(lockPath: string, owner: AtomicFileLockOwner): void {\n const current = readOwner(lockPath)\n if (!current) return\n if (canonicalOwner(current) !== canonicalOwner(owner)) {\n throw new AtomicFileLockError(`atomic file lock owner changed before release (${lockPath})`)\n }\n try {\n unlinkSync(lockPath)\n } catch (error) {\n if (!isMissing(error)) throw error\n }\n}\n\nfunction canonicalOwner(owner: AtomicFileLockOwner): string {\n return JSON.stringify({ host: owner.host, nonce: owner.nonce, pid: owner.pid })\n}\n\nfunction recoveryPath(lockPath: string): string {\n return `${lockPath}.reclaim`\n}\n\nfunction isAlreadyExists(error: unknown): boolean {\n return error instanceof Error && 'code' in error && error.code === 'EEXIST'\n}\n\nfunction isMissing(error: unknown): boolean {\n return error instanceof Error && 'code' in error && error.code === 'ENOENT'\n}\n\nfunction isNoSuchProcess(error: unknown): boolean {\n return error instanceof Error && 'code' in error && error.code === 'ESRCH'\n}\n\nfunction unlinkIfExists(path: string): void {\n try {\n unlinkSync(path)\n } catch (error) {\n if (!isMissing(error)) throw error\n }\n}\n","/**\n * concurrency — small primitives the evolution loop needs.\n *\n * `Mutex` is a zero-dep async lock with FIFO fairness. The evolution loop\n * uses it to serialise checkout/build/commit sequences inside a single\n * pool slot, and to gate concurrent JSONL writers (see\n * `lockedJsonlReferenceReplayStore`).\n *\n * Deliberately minimal — no priority queue, no timeouts. If you need\n * those, swap to `async-mutex` at the call site.\n */\n\nexport class Mutex {\n private locked = false\n private readonly waiters: Array<() => void> = []\n\n async acquire(): Promise<() => void> {\n if (!this.locked) {\n this.locked = true\n return () => this.release()\n }\n return new Promise<() => void>((resolve) => {\n this.waiters.push(() => {\n resolve(() => this.release())\n })\n })\n }\n\n private release(): void {\n const next = this.waiters.shift()\n if (next) {\n next()\n } else {\n this.locked = false\n }\n }\n\n async runExclusive<T>(fn: () => Promise<T> | T): Promise<T> {\n const release = await this.acquire()\n try {\n return await fn()\n } finally {\n release()\n }\n }\n\n /** True iff someone holds the lock right now. Diagnostics only. */\n get isLocked(): boolean {\n return this.locked\n }\n\n /** Pending waiter count. Diagnostics only. */\n get pending(): number {\n return this.waiters.length\n }\n}\n\nexport interface MapConcurrentRangeOptions<R> {\n count: number\n maxConcurrency: number\n label: string\n signal?: AbortSignal\n map(index: number, signal: AbortSignal): Promise<R>\n}\n\n/** Map an integer range with bounded work, cancellation, and first-error cleanup. */\nexport async function mapConcurrentRange<R>(options: MapConcurrentRangeOptions<R>): Promise<R[]> {\n if (!Number.isSafeInteger(options.count) || options.count < 0) {\n throw new Error(`${options.label} count must be a non-negative integer`)\n }\n if (!Number.isSafeInteger(options.maxConcurrency) || options.maxConcurrency < 1) {\n throw new Error(`${options.label} maxConcurrency must be a positive integer`)\n }\n\n const controller = new AbortController()\n const abortFromCaller = () => controller.abort(options.signal?.reason)\n if (options.signal?.aborted) abortFromCaller()\n else options.signal?.addEventListener('abort', abortFromCaller, { once: true })\n\n const results = new Array<R>(options.count)\n let nextIndex = 0\n let failed = false\n let firstFailure: unknown\n const workers = Array.from(\n { length: Math.min(options.maxConcurrency, options.count) },\n async () => {\n while (!controller.signal.aborted) {\n const index = nextIndex\n nextIndex += 1\n if (index >= options.count) return\n try {\n results[index] = await options.map(index, controller.signal)\n } catch (error) {\n if (!failed) {\n failed = true\n firstFailure = error\n controller.abort(error)\n }\n return\n }\n }\n },\n )\n\n try {\n await Promise.all(workers)\n if (options.signal?.aborted) throw abortError(options.signal, options.label)\n if (failed) throw firstFailure\n if (controller.signal.aborted) throw abortError(controller.signal, options.label)\n return results\n } finally {\n options.signal?.removeEventListener('abort', abortFromCaller)\n }\n}\n\n/**\n * Map independent work with a fixed worker count while preserving input order.\n * After the first rejection, no new items start; already-running work is allowed\n * to settle before the returned promise rejects. Partial results are discarded.\n */\nexport async function mapConcurrent<T, R>(\n items: readonly T[],\n concurrency: number,\n map: (item: T, index: number, signal: AbortSignal) => Promise<R>,\n): Promise<R[]> {\n return mapConcurrentRange({\n count: items.length,\n maxConcurrency: concurrency,\n label: 'mapConcurrent',\n map(index, signal) {\n return map(items[index]!, index, signal)\n },\n })\n}\n\nfunction abortError(signal: AbortSignal, label: string): Error {\n return signal.reason instanceof Error ? signal.reason : new Error(`${label} aborted`)\n}\n","/** Filesystem durability and cross-process exclusion for append-only journal files. */\n\nimport {\n closeSync,\n constants,\n fstatSync,\n fsyncSync,\n mkdirSync,\n openSync,\n readSync,\n renameSync,\n rmSync,\n writeSync,\n} from 'node:fs'\nimport { dirname } from 'node:path'\nimport { AtomicFileLockError, tryAcquireAtomicFileLock } from './atomic-file-lock'\n\n/** How a journal binding reports file-layer faults in its own error taxonomy. */\nexport interface LedgerFileContext {\n /** Noun used in error messages, e.g. 'search ledger'. */\n subject: string\n integrityError(message: string, options?: { cause?: unknown }): Error\n}\n\n/** Append one already-serialized line; the write is fsynced (file and directory)\n * before returning so an acknowledged append survives a crash. */\nexport function appendLedgerLine(path: string, line: string, context: LedgerFileContext): void {\n mkdirSync(dirname(path), { recursive: true })\n recoverTornFinalWrite(path, context)\n const fd = openSync(path, constants.O_CREAT | constants.O_WRONLY | constants.O_APPEND, 0o600)\n try {\n writeAll(fd, Buffer.from(line, 'utf8'), context)\n fsyncSync(fd)\n } finally {\n closeSync(fd)\n }\n fsyncDirectory(dirname(path))\n}\n\nfunction recoverTornFinalWrite(path: string, context: LedgerFileContext): void {\n let fd: number\n try {\n fd = openSync(path, constants.O_RDWR)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return\n throw context.integrityError(`${context.subject} ${path} could not be opened for recovery`, {\n cause: error,\n })\n }\n\n try {\n const size = fstatSync(fd).size\n if (size === 0 || readByte(fd, size - 1) === 0x0a) return\n const tailStart = findFinalLineStart(fd, size)\n const replacement = Buffer.alloc(size - tailStart, 0x20)\n replacement[replacement.length - 1] = 0x0a\n writeAllAt(fd, replacement, tailStart, context)\n fsyncSync(fd)\n } catch (error) {\n throw context.integrityError(\n `${context.subject} ${path} has an unrecoverable final journal write`,\n { cause: error },\n )\n } finally {\n closeSync(fd)\n }\n}\n\n/** Replace a small sidecar file whole: write a temporary sibling, fsync it,\n * then rename over the target. A reader therefore sees the previous contents\n * or the new ones, never a partial file. */\nexport function writeLedgerFileAtomically(\n path: string,\n contents: string,\n context: LedgerFileContext,\n): void {\n const directory = dirname(path)\n mkdirSync(directory, { recursive: true })\n const temporaryPath = `${path}.tmp`\n const fd = openSync(\n temporaryPath,\n constants.O_CREAT | constants.O_WRONLY | constants.O_TRUNC,\n 0o600,\n )\n try {\n writeAll(fd, Buffer.from(contents, 'utf8'), context)\n fsyncSync(fd)\n } finally {\n closeSync(fd)\n }\n renameSync(temporaryPath, path)\n fsyncDirectory(directory)\n}\n\n/** Remove whatever occupies a sidecar path and fsync its directory, so the\n * removal is as durable as the write that created it. False when the path was\n * already empty. Recursive because a sidecar path holding a directory is one of\n * the states this has to be able to clear, not one it can refuse. */\nexport function removeLedgerFile(path: string, context: LedgerFileContext): boolean {\n try {\n rmSync(path, { recursive: true })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false\n throw context.integrityError(`${context.subject} ${path} could not be removed`, {\n cause: error,\n })\n }\n fsyncDirectory(dirname(path))\n return true\n}\n\nexport function withLedgerFileLock<T>(path: string, context: LedgerFileContext, run: () => T): T {\n const result = tryWithLedgerFileLock(path, context, run)\n if (!result.acquired) {\n throw context.integrityError(`${context.subject} lock is held (${path})`)\n }\n return result.value\n}\n\nexport type FileLockResult<T> = { acquired: true; value: T } | { acquired: false }\n\nexport function tryWithLedgerFileLock<T>(\n path: string,\n context: LedgerFileContext,\n run: () => T,\n): FileLockResult<T> {\n mkdirSync(dirname(path), { recursive: true })\n const lockPath = `${path}.lock`\n try {\n const acquisition = tryAcquireAtomicFileLock({ lockPath })\n if (!acquisition.acquired) return { acquired: false }\n try {\n return { acquired: true, value: run() }\n } finally {\n acquisition.lock.release()\n }\n } catch (error) {\n if (error instanceof AtomicFileLockError) {\n throw context.integrityError(error.message, { cause: error })\n }\n throw error\n }\n}\n\nfunction writeAll(fd: number, bytes: Buffer, context: LedgerFileContext): void {\n let offset = 0\n while (offset < bytes.byteLength) {\n const written = writeSync(fd, bytes, offset, bytes.byteLength - offset)\n if (written <= 0) throw context.integrityError('filesystem wrote zero bytes')\n offset += written\n }\n}\n\nfunction writeAllAt(fd: number, bytes: Buffer, position: number, context: LedgerFileContext): void {\n let offset = 0\n while (offset < bytes.byteLength) {\n const written = writeSync(fd, bytes, offset, bytes.byteLength - offset, position + offset)\n if (written <= 0) throw context.integrityError('filesystem wrote zero bytes')\n offset += written\n }\n}\n\nfunction findFinalLineStart(fd: number, size: number): number {\n const chunk = Buffer.alloc(Math.min(4_096, size))\n let cursor = size\n while (cursor > 0) {\n const length = Math.min(chunk.byteLength, cursor)\n cursor -= length\n const read = readSync(fd, chunk, 0, length, cursor)\n for (let index = read - 1; index >= 0; index -= 1) {\n if (chunk[index] === 0x0a) return cursor + index + 1\n }\n }\n return 0\n}\n\nfunction readByte(fd: number, position: number): number {\n const byte = Buffer.allocUnsafe(1)\n const read = readSync(fd, byte, 0, 1, position)\n return read === 1 ? byte[0]! : -1\n}\n\nfunction fsyncDirectory(path: string): void {\n const fd = openSync(path, constants.O_RDONLY)\n try {\n fsyncSync(fd)\n } finally {\n closeSync(fd)\n }\n}\n","/**\n * Trusted head — the pin that makes deletion detectable.\n *\n * ## What the chain binds\n *\n * Every entry hashes the journal header, its sequence, the previous entry's\n * hash, and its event, so editing, reordering, or removing an entry in the\n * middle of a journal invalidates every hash after it. What a hash chain\n * cannot bind is its own length. Dropping entries off the END leaves a shorter\n * journal that is internally perfect, and rebuilding a journal from sequence 0\n * with recomputed hashes produces a different, equally perfect chain. For an\n * audit log of candidate registrations and promotion decisions that is the\n * difference between a record of a search that happened and a record of one\n * that did not.\n *\n * ## What the pin binds\n *\n * `<journal>.head` holds `{sequence, entryHash}` for one entry a writer\n * actually appended, in a file separate from the journal it attests. Each\n * entry hash commits to the entire prefix before it, so checking that the\n * entry at exactly `sequence` still carries `entryHash` proves two things at\n * once: the journal is at least `sequence + 1` entries long, and those entries\n * are byte-for-byte the ones the writer saw. A journal that merely grew past\n * the pin still verifies; a truncated, rolled-back, or wholesale-rewritten one\n * cannot.\n *\n * ## What the pin does NOT defend against\n *\n * The pin is a second file, not a signature. A writer with access to BOTH the\n * journal and its pin can rewrite both consistently, and the result verifies.\n * Pinning raises the cost of undetected tampering from one file to two and\n * nothing more; defending against a writer who holds both requires an anchor\n * outside this store entirely — a signed head, or a digest published to an\n * append-only service the tamperer does not control.\n *\n * Deleting the pin file downgrades a pinned journal to an unpinned one, which\n * cannot be distinguished from a journal written before pinning existed. That\n * is what `requireTrustedHead` is for: a journal opened under it refuses to\n * read at all once a non-empty journal has no pin.\n *\n * ## Abandoning a pin\n *\n * A pin outlives the journal it attests: deleting or rebuilding the journal\n * leaves a pin naming history the file no longer carries, and every read is\n * refused because that is precisely the deletion the pin exists to catch. The\n * refusal names the pin file, and `clearTrustedHeadFile` is the explicit way to\n * discard it — the same downgrade a writer with filesystem access could already\n * perform with `rm`, made reachable through the API so it is a decision in the\n * code rather than an undocumented manual step. It returns the guarantee it\n * gave up and writes no record of its own; a caller that needs the discard on\n * the record has to write one.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { canonicalString, LEDGER_HASH_PATTERN, type LedgerHash } from './canonical'\nimport { type LedgerFileContext, removeLedgerFile, writeLedgerFileAtomically } from './journal-file'\n\n/** A `(sequence, entryHash)` pair a writer pinned outside the journal. */\nexport interface LedgerTrustedHead {\n sequence: number\n entryHash: LedgerHash\n}\n\n/** Minimum entry shape the anchor check needs. */\nexport interface LedgerAnchoredEntry {\n sequence: number\n entryHash: LedgerHash\n}\n\n/** Sibling pin file for a journal — deliberately NOT the journal, so rewriting\n * the log is not enough to rewrite the trust recorded about it. */\nexport function trustedHeadPathFor(journalPath: string): string {\n return `${journalPath}.head`\n}\n\nconst TRUSTED_HEAD_RECOVERY =\n ' Restore the file, or discard the pin it held with clearTrustedHead().'\n\n/** The pin, or null when this journal has never been pinned. A pin that exists\n * but does not parse is corruption or tamper of the trust record itself and\n * fails loudly: reporting it as \"no pin\" would silently downgrade what the\n * journal can prove. */\nexport function readTrustedHeadFile(\n path: string,\n context: LedgerFileContext,\n): LedgerTrustedHead | null {\n let text: string\n try {\n text = readFileSync(path, 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return null\n throw context.integrityError(\n `${context.subject} trusted head ${path} could not be read.${TRUSTED_HEAD_RECOVERY}`,\n { cause: error },\n )\n }\n let raw: unknown\n try {\n raw = JSON.parse(text)\n } catch (error) {\n throw context.integrityError(\n `${context.subject} trusted head ${path} is not valid JSON.${TRUSTED_HEAD_RECOVERY}`,\n { cause: error },\n )\n }\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n throw context.integrityError(\n `${context.subject} trusted head ${path} is not an object.${TRUSTED_HEAD_RECOVERY}`,\n )\n }\n const record = raw as Record<string, unknown>\n const keys = Object.keys(record).sort()\n if (keys.length !== 2 || keys[0] !== 'entryHash' || keys[1] !== 'sequence') {\n throw context.integrityError(\n `${context.subject} trusted head ${path} has keys [${keys.join(', ')}], expected [entryHash, sequence].${TRUSTED_HEAD_RECOVERY}`,\n )\n }\n const { sequence, entryHash } = record\n if (typeof sequence !== 'number' || !Number.isSafeInteger(sequence) || sequence < 0) {\n throw context.integrityError(\n `${context.subject} trusted head ${path} sequence is not a non-negative integer.${TRUSTED_HEAD_RECOVERY}`,\n )\n }\n if (typeof entryHash !== 'string' || !LEDGER_HASH_PATTERN.test(entryHash)) {\n throw context.integrityError(\n `${context.subject} trusted head ${path} entryHash is not a sha256 digest.${TRUSTED_HEAD_RECOVERY}`,\n )\n }\n return { sequence, entryHash: entryHash as LedgerHash }\n}\n\n/** Publish a pin by write-then-rename, so a crash leaves either the previous\n * pin or the new one and never a torn file that would lock a journal out of\n * its own trust record.\n *\n * Not re-exported from the package's public surface. It takes no lock, checks no\n * chain, and enforces no monotonicity, so a public form would be the one call\n * able to move a pin BACKWARD onto a journal it never read — which is the\n * truncation the pin exists to refuse. `FileLedgerJournal.pinTrustedHead` is\n * the public writer: locked, chain-verified, forward-only. */\nexport function writeTrustedHeadFile(\n path: string,\n head: LedgerTrustedHead,\n context: LedgerFileContext,\n): void {\n try {\n writeLedgerFileAtomically(path, `${canonicalString(head)}\\n`, context)\n } catch (error) {\n throw context.integrityError(`${context.subject} trusted head ${path} could not be written`, {\n cause: error,\n })\n }\n}\n\n/** Outcome of discarding a pin: the guarantee that was given up. `unreadable`\n * carries why the discarded pin could not be named — corruption, a permission\n * fault, a wrong file type — so a caller recording the decision never has to\n * treat \"no pin was there\" and \"the pin could not be read\" as one state.\n * `cleanupError` reports a stale atomic-write sibling that remains after the\n * actual pin was cleared. */\nexport type LedgerTrustedHeadRemoval =\n | { removed: true; head: LedgerTrustedHead; cleanupError?: string }\n | { removed: true; head: null; unreadable: string; cleanupError?: string }\n | { removed: false; cleanupError?: string }\n\n/** Discard a journal's pin, reporting what was discarded. The journal file is\n * untouched and reads as unpinned afterwards, so the next pinning append opens\n * a new pin at the entry it writes. This gives up the deletion guarantee for\n * every entry the pin covered; it is the recovery for a pin whose journal was\n * deliberately deleted or rebuilt, and the only way past a pin file that can no\n * longer be read at all. */\nexport function clearTrustedHeadFile(\n path: string,\n context: LedgerFileContext,\n): LedgerTrustedHeadRemoval {\n let head: LedgerTrustedHead | null = null\n let unreadable = ''\n try {\n head = readTrustedHeadFile(path, context)\n } catch (error) {\n // A pin nobody can read is precisely what clearing has to get past, so the\n // fault is reported on a removal that still happens rather than raised —\n // but it is reported, never flattened into \"there was no pin\".\n unreadable = error instanceof Error ? error.message : String(error)\n }\n const removed = removeLedgerFile(path, context)\n let cleanupError: string | undefined\n try {\n // Cleanup cannot block the requested trust-state change. Report a stale\n // atomic-write sibling so the caller can repair it before the next write.\n removeLedgerFile(`${path}.tmp`, context)\n } catch (error) {\n cleanupError = error instanceof Error ? error.message : String(error)\n }\n const cleanup = cleanupError === undefined ? {} : { cleanupError }\n if (!removed) return { removed: false, ...cleanup }\n if (head === null) return { removed: true, head: null, unreadable, ...cleanup }\n return { removed: true, head, ...cleanup }\n}\n\n/** How a journal is named when a trusted-head check refuses it. */\nexport interface LedgerTrustedHeadSubject {\n /** The journal in error messages, e.g. `search ledger /runs/a/ledger.jsonl`. */\n subject: string\n /** The sibling pin file, named in every refusal so the operator can see which\n * of the two files carries the claim being enforced. */\n trustedHeadPath: string\n}\n\n/** The entry at exactly `head.sequence` must still carry `head.entryHash`.\n * Entries must already have passed chain verification, which is what makes\n * index and sequence interchangeable here. */\nexport function verifyEntriesAgainstTrustedHead(\n entries: readonly LedgerAnchoredEntry[],\n head: LedgerTrustedHead,\n context: LedgerFileContext,\n naming: LedgerTrustedHeadSubject,\n): void {\n // A caller passing the bare subject string an untyped build would accept\n // destructures into two undefineds, which would corrupt the text of a\n // security refusal rather than fail. Refuse instead.\n if (typeof naming !== 'object' || naming === null) {\n throw new TypeError(\n 'verifyEntriesAgainstTrustedHead expects { subject, trustedHeadPath } as its fourth argument',\n )\n }\n const { subject, trustedHeadPath } = naming\n const pinned = entries[head.sequence]\n if (pinned === undefined) {\n throw context.integrityError(\n `${subject} trusted head ${trustedHeadPath} pins sequence ${head.sequence} (${head.entryHash}) but the journal has ${entries.length} entries — pinned history is missing (truncation, rollback, or rewrite). Restore the journal, or abandon the pinned history on purpose with clearTrustedHead().`,\n )\n }\n if (pinned.sequence !== head.sequence) {\n throw context.integrityError(\n `${subject} entry at index ${head.sequence} carries sequence ${pinned.sequence}; the chain was not verified before the trusted head ${trustedHeadPath}`,\n )\n }\n if (pinned.entryHash !== head.entryHash) {\n throw context.integrityError(\n `${subject} entry at pinned sequence ${head.sequence} carries ${pinned.entryHash} but the trusted head ${trustedHeadPath} recorded ${head.entryHash} — the journal was rewritten. Restore the journal, or abandon the pinned history on purpose with clearTrustedHead().`,\n )\n }\n}\n","/**\n * Generic durable append-only journal with a SHA-256 hash chain.\n *\n * The file format is canonical JSONL: every row is the canonical JSON encoding\n * (RFC 8785, object keys sorted recursively) of one entry, and every entry\n * hashes the previous entry's hash so the log is tamper-evident. Appends are\n * serialized in-process per path and across processes via a lock file, fsynced\n * before acknowledgement, and idempotent by `eventId`. A malformed,\n * non-canonical, truncated, reordered, or conflicting log fails loudly; a bad\n * row is never skipped.\n *\n * A hash chain binds each entry to its predecessor, which cannot prove that no\n * entry was removed from the end: a valid shorter prefix is still a valid\n * chain. `pinHead` records the appended entry in a sibling `<journal>.head`\n * file and every read re-verifies the journal against a pin that is present,\n * so truncation and wholesale rewrite are refused. `trusted-head.ts` carries\n * the full threat model, including what a pin does not defend against.\n *\n * Domain vocabulary lives entirely in the consumer's codec: entry schema\n * validation, the constant header fields stamped into every entry, the error\n * taxonomy, and the state machine replayed over verified entries.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { Mutex } from '../concurrency'\nimport { canonicalString, hashCanonical, type LedgerHash } from './canonical'\nimport { appendLedgerLine, type LedgerFileContext, withLedgerFileLock } from './journal-file'\nimport {\n clearTrustedHeadFile,\n type LedgerTrustedHead,\n type LedgerTrustedHeadRemoval,\n readTrustedHeadFile,\n trustedHeadPathFor,\n verifyEntriesAgainstTrustedHead,\n writeTrustedHeadFile,\n} from './trusted-head'\n\n/** Minimum shape of a journal event: the idempotency key. */\nexport interface LedgerEventBase {\n eventId: string\n}\n\n/** Chain envelope every entry carries in addition to the journal's constant\n * header fields. `entryHash` covers the header, sequence, previous hash, and\n * event, so any rewrite of history invalidates every later entry. */\nexport interface LedgerChainFields<Event extends LedgerEventBase> {\n sequence: number\n previousHash: LedgerHash | null\n event: Event\n entryHash: LedgerHash\n}\n\nexport type LedgerEntryOf<Header extends object, Event extends LedgerEventBase> = Header &\n LedgerChainFields<Event>\n\nexport interface LedgerLineContext {\n path: string\n line: number\n}\n\n/** Domain state machine replayed over chain-verified entries in order.\n * `apply` throws (via the codec's error taxonomy) on an invalid transition;\n * `finish` builds the consumer's projection once every entry was applied. */\nexport interface LedgerProjector<Entry, Projection> {\n apply(entry: Entry, index: number): void\n finish(entries: Entry[]): Projection\n}\n\nexport interface LedgerJournalCodec<\n Header extends object,\n Event extends LedgerEventBase,\n Projection,\n> extends LedgerFileContext {\n /** Constant fields stamped into every entry and covered by its hash. */\n header: Header\n conflictError(message: string): Error\n /** Validate one parsed JSON row into a typed entry. Must reject malformed\n * rows and non-canonical event encodings with the binding's own errors;\n * the journal itself verifies the stored bytes and the hash chain. */\n parseEntry(raw: unknown, context: LedgerLineContext): LedgerEntryOf<Header, Event>\n /** Reject an entry whose constant header fields do not match this journal. */\n checkEntryHeader(entry: LedgerEntryOf<Header, Event>, index: number): void\n createProjector(): LedgerProjector<LedgerEntryOf<Header, Event>, Projection>\n}\n\nexport interface LedgerReplayResult<Entry, Projection> {\n entries: Entry[]\n projection: Projection\n}\n\nexport interface LedgerAppendResult<Entry, Projection> {\n entry: Entry\n /** False when the exact event was already durably present. */\n appended: boolean\n projection: Projection\n}\n\nexport interface LedgerAppendOptions {\n /** Record the appended entry as this journal's trusted head, so a later read\n * can prove nothing was deleted from the end.\n *\n * On the idempotent path — the event is already durable — the pin moves up to\n * that entry only when the entry is ahead of it, along a chain this call has\n * just verified. So a pin write that failed after its row was fsynced is\n * repaired by the retry that idempotency already asks the caller for, and the\n * pin still only ever moves forward over history it has checked.\n *\n * A strict journal whose first pin write failed still refuses the retry\n * because it cannot distinguish that failure from a deleted pin. Recover it\n * explicitly with `pinTrustedHead()`; silently adopting the current file\n * would defeat `requireTrustedHead`. */\n pinHead?: boolean\n}\n\nexport interface FileLedgerJournalOptions {\n /** Refuse to read a non-empty journal that has no trusted head.\n *\n * Off by default because journals written before pinning have no pin and\n * must keep opening. Turn it on for a journal this process pins: without it,\n * deleting the sibling pin file silently downgrades the journal back to a\n * chain that cannot detect deletion. */\n requireTrustedHead?: boolean\n}\n\n// One async mutex per resolved journal path so concurrent appends from a single\n// process queue up instead of failing on the held cross-process lock file.\nconst journalMutexes = new Map<string, Mutex>()\n\nfunction mutexFor(path: string): Mutex {\n const existing = journalMutexes.get(path)\n if (existing) return existing\n const mutex = new Mutex()\n journalMutexes.set(path, mutex)\n return mutex\n}\n\n/** Durable filesystem journal. Construction performs no I/O; `append` and\n * `replay` validate the complete existing file under the locks. */\nexport class FileLedgerJournal<Header extends object, Event extends LedgerEventBase, Projection> {\n readonly path: string\n /** Sibling file holding this journal's trusted head. */\n readonly trustedHeadPath: string\n private readonly codec: LedgerJournalCodec<Header, Event, Projection>\n private readonly mutex: Mutex\n private readonly requireTrustedHead: boolean\n\n constructor(\n path: string,\n codec: LedgerJournalCodec<Header, Event, Projection>,\n options: FileLedgerJournalOptions = {},\n ) {\n this.path = resolve(path)\n this.trustedHeadPath = trustedHeadPathFor(this.path)\n this.codec = codec\n this.mutex = mutexFor(this.path)\n this.requireTrustedHead = options.requireTrustedHead === true\n }\n\n async replay(): Promise<LedgerReplayResult<LedgerEntryOf<Header, Event>, Projection>> {\n return this.mutex.runExclusive(() =>\n withLedgerFileLock(this.path, this.codec, () => this.replayLocked()),\n )\n }\n\n async append(\n event: Event,\n options: LedgerAppendOptions = {},\n ): Promise<LedgerAppendResult<LedgerEntryOf<Header, Event>, Projection>> {\n return this.mutex.runExclusive(() =>\n withLedgerFileLock(this.path, this.codec, () => {\n const before = this.replayLocked()\n const existing = before.entries.find((entry) => entry.event.eventId === event.eventId)\n if (existing) {\n if (canonicalString(existing.event) !== canonicalString(event)) {\n throw this.codec.conflictError(\n `eventId ${event.eventId} already exists with different content`,\n )\n }\n if (options.pinHead === true) this.pinAcknowledged(existing, before.entries)\n return { entry: existing, appended: false, projection: before.projection }\n }\n\n const material = {\n ...this.codec.header,\n sequence: before.entries.length,\n previousHash: before.entries.at(-1)?.entryHash ?? null,\n event,\n }\n const entry = { ...material, entryHash: hashCanonical(material) }\n\n // Apply the state transition before spending an append: an entry the\n // projector rejects must never reach the durable file.\n const projection = this.project([...before.entries, entry])\n appendLedgerLine(this.path, `${canonicalString(entry)}\\n`, this.codec)\n if (options.pinHead === true) {\n // Journal first, pin second: a crash or a failed pin write between\n // them leaves the pin one entry behind, which still verifies and is\n // repaired by the next pinning append, including the idempotent\n // retry of this same event. The reverse order would leave a pin\n // naming an entry the journal does not carry, locking the journal\n // out of its own history.\n writeTrustedHeadFile(\n this.trustedHeadPath,\n { sequence: entry.sequence, entryHash: entry.entryHash },\n this.codec,\n )\n }\n return { entry, appended: true, projection }\n }),\n )\n }\n\n /** The pinned head, or null when this journal has never been pinned. */\n async trustedHead(): Promise<LedgerTrustedHead | null> {\n return this.mutex.runExclusive(() =>\n withLedgerFileLock(this.path, this.codec, () =>\n readTrustedHeadFile(this.trustedHeadPath, this.codec),\n ),\n )\n }\n\n /** Pin the current verified head. The chain and any existing pin are checked\n * first, so the pin only ever moves forward along a journal that still\n * carries the history it already recorded. */\n async pinTrustedHead(): Promise<LedgerTrustedHead> {\n return this.mutex.runExclusive(() =>\n withLedgerFileLock(this.path, this.codec, () => {\n const entries = this.readEntries()\n this.project(entries)\n // `requireTrustedHead` is deliberately not applied here: pinning is how\n // a journal that lacks a pin acquires one.\n this.verifyTrustedHead(entries, false)\n const last = entries.at(-1)\n if (last === undefined) {\n throw this.codec.integrityError(\n `${this.codec.subject} ${this.path} is empty; there is no head to pin`,\n )\n }\n const head: LedgerTrustedHead = { sequence: last.sequence, entryHash: last.entryHash }\n writeTrustedHeadFile(this.trustedHeadPath, head, this.codec)\n return head\n }),\n )\n }\n\n /** Discard this journal's pin, reporting what was discarded. The journal file\n * is untouched and reads as unpinned afterwards, so the next pinning append\n * opens a new pin at the entry it writes. It is the way out of a pin whose\n * journal was deliberately deleted or rebuilt — every read of such a path is\n * refused, correctly, because a pin naming history the file no longer carries\n * is indistinguishable from a deletion. Clearing removes the deletion\n * guarantee for every entry the pin covered. */\n async clearTrustedHead(): Promise<LedgerTrustedHeadRemoval> {\n return this.mutex.runExclusive(() =>\n withLedgerFileLock(this.path, this.codec, () =>\n clearTrustedHeadFile(this.trustedHeadPath, this.codec),\n ),\n )\n }\n\n /** Move the pin up to an entry this caller just acknowledged on the\n * idempotent path, when a pin write that failed after its row was already\n * fsynced left the pin behind. The move is safe because it is forward-only\n * along a chain `replayLocked` has just verified: every entry up to this one\n * is bound to it by hash, so the pin can only ever come to name more history\n * than it did, never different history.\n *\n * With no pin at all this only pins the journal's current head. An entry in\n * the middle would publish a pin that reads as protection while leaving every\n * later entry silently truncatable, which is a worse state to hand an auditor\n * than the honest absence of a pin; `pinTrustedHead()` is the way to pin an\n * unpinned journal on purpose. */\n private pinAcknowledged(\n entry: LedgerEntryOf<Header, Event>,\n entries: LedgerEntryOf<Header, Event>[],\n ): void {\n const pinned = readTrustedHeadFile(this.trustedHeadPath, this.codec)\n if (pinned === null) {\n if (entry.sequence !== entries.length - 1) return\n } else if (pinned.sequence >= entry.sequence) {\n return\n }\n writeTrustedHeadFile(\n this.trustedHeadPath,\n { sequence: entry.sequence, entryHash: entry.entryHash },\n this.codec,\n )\n }\n\n private replayLocked(): LedgerReplayResult<LedgerEntryOf<Header, Event>, Projection> {\n const entries = this.readEntries()\n const projection = this.project(entries)\n // The anchor is meaningful only over a chain that already verified, so it\n // is checked last — and on every read path, so a violated pin is refused\n // before an append can extend the journal over it.\n this.verifyTrustedHead(entries, this.requireTrustedHead)\n return { entries, projection }\n }\n\n private verifyTrustedHead(entries: LedgerEntryOf<Header, Event>[], requirePin: boolean): void {\n const pinned = readTrustedHeadFile(this.trustedHeadPath, this.codec)\n if (pinned === null) {\n if (requirePin && entries.length > 0) {\n throw this.codec.integrityError(\n `${this.codec.subject} ${this.path} has ${entries.length} entries but no trusted head at ${this.trustedHeadPath} — without its pin the journal cannot prove nothing was deleted. Restore the pin file, or adopt the current head with pinTrustedHead().`,\n )\n }\n return\n }\n verifyEntriesAgainstTrustedHead(entries, pinned, this.codec, {\n subject: `${this.codec.subject} ${this.path}`,\n trustedHeadPath: this.trustedHeadPath,\n })\n }\n\n private readEntries(): LedgerEntryOf<Header, Event>[] {\n if (!existsSync(this.path)) return []\n const text = readFileSync(this.path, 'utf8')\n if (text.length === 0) return []\n if (!text.endsWith('\\n')) {\n throw this.codec.integrityError(\n `${this.codec.subject} ${this.path} has a truncated final record (missing newline)`,\n )\n }\n\n const lines = text.slice(0, -1).split('\\n')\n const entries: LedgerEntryOf<Header, Event>[] = []\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index]!\n if (line.length === 0) {\n throw this.codec.integrityError(\n `${this.codec.subject} ${this.path} has a blank row at line ${index + 1}`,\n )\n }\n let raw: unknown\n try {\n raw = JSON.parse(line)\n } catch (error) {\n throw this.codec.integrityError(\n `${this.codec.subject} ${this.path} has invalid JSON at line ${index + 1}`,\n { cause: error },\n )\n }\n const entry = this.codec.parseEntry(raw, { path: this.path, line: index + 1 })\n let canonical: string\n try {\n canonical = canonicalString(entry)\n } catch (error) {\n throw this.codec.integrityError(\n `${this.codec.subject} ${this.path} has a row with no canonical JSON form at line ${index + 1}`,\n { cause: error },\n )\n }\n if (line !== canonical) {\n throw this.codec.integrityError(\n `${this.codec.subject} ${this.path} has non-canonical bytes at line ${index + 1}`,\n )\n }\n entries.push(entry)\n }\n return entries\n }\n\n private project(entries: LedgerEntryOf<Header, Event>[]): Projection {\n const projector = this.codec.createProjector()\n const eventIds = new Set<string>()\n let expectedPrevious: LedgerHash | null = null\n for (let index = 0; index < entries.length; index += 1) {\n const entry = entries[index]!\n this.codec.checkEntryHeader(entry, index)\n if (entry.sequence !== index) {\n throw this.codec.integrityError(\n `entry ${entry.event.eventId} has sequence ${entry.sequence}, expected ${index}`,\n )\n }\n if (entry.previousHash !== expectedPrevious) {\n throw this.codec.integrityError(\n `entry ${entry.event.eventId} does not extend the previous hash`,\n )\n }\n const { entryHash: _entryHash, ...material } = entry\n const expectedHash = hashCanonical(material)\n if (entry.entryHash !== expectedHash) {\n throw this.codec.integrityError(\n `entry ${entry.event.eventId} hash mismatch: expected ${expectedHash}, got ${entry.entryHash}`,\n )\n }\n expectedPrevious = entry.entryHash\n if (eventIds.has(entry.event.eventId)) {\n throw this.codec.integrityError(\n `duplicate eventId ${entry.event.eventId} in durable ledger`,\n )\n }\n eventIds.add(entry.event.eventId)\n projector.apply(entry, index)\n }\n return projector.finish(entries)\n }\n}\n"],"mappings":"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"}
@@ -1,51 +0,0 @@
1
- //#region src/trajectory-replay/steps.d.ts
2
- /**
3
- * Recorded shell-trajectory steps and the observation grammar they carry.
4
- *
5
- * A recorded trajectory is the action/observation sequence an agent actually
6
- * ran. Scaffolds that execute one shell command per step (mini-SWE and the
7
- * CodeTracer-normalized corpora built from it) tag each observation with the
8
- * command's returncode and its combined output:
9
- *
10
- * <returncode>2</returncode>
11
- * <output>
12
- * …command output…
13
- * </output>
14
- *
15
- * The parsers here are the only place that grammar is decoded. Everything
16
- * downstream — replay verdicts, corpus enumeration, fix prompts — reads the
17
- * returncode, the output, and the failure signature through these functions.
18
- */
19
- /**
20
- * One step of a recorded shell trajectory. Structural: any richer step record
21
- * (file refs, thinking text, tool type) satisfies it.
22
- */
23
- interface RecordedTrajectoryStep {
24
- /** 1-based position in the trajectory. */
25
- readonly step_id: number;
26
- readonly action: string;
27
- /** Null when the step recorded no observation (terminal submit steps). */
28
- readonly observation: string | null;
29
- }
30
- /** Recorded returncode of a step, or null when the observation carries none. */
31
- declare function parseRecordedReturncode(observation: string | null): number | null;
32
- /** Text between the observation's <output> tags, or the raw observation when
33
- * the tags are absent. */
34
- declare function parseObservationOutput(observation: string | null): string;
35
- /**
36
- * Stable failure-signature candidate: the first line of the recorded output
37
- * that contains the word "error". Null when no such line exists — a verdict
38
- * then falls back to returncode-only matching and says so.
39
- * Pass an explicit signature to override (compiler quote glyphs vary with
40
- * locale, so a hand-picked ASCII substring is often more robust).
41
- */
42
- declare function deriveFailureSignature(observation: string | null): string | null;
43
- /** mini-SWE's end-of-run submit convention: the agent echoes this sentinel
44
- * and dumps the diff. A label on this step marks a bad SUBMIT DECISION, not a
45
- * failed command — there is no executable failure to reproduce, so it is
46
- * never a counterfactual replay target. */
47
- declare const SUBMIT_ACTION_SIGNATURE = "COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT";
48
- declare function isSubmitAction(action: string): boolean;
49
- //#endregion
50
- export { parseObservationOutput as a, isSubmitAction as i, SUBMIT_ACTION_SIGNATURE as n, parseRecordedReturncode as o, deriveFailureSignature as r, RecordedTrajectoryStep as t };
51
- //# sourceMappingURL=steps-BArUxhna.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"steps-BArUxhna.d.ts","names":[],"sources":["../src/trajectory-replay/steps.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;UAsBiB;;WAEN;WACA;;WAEA;;;iBAIK,wBAAwB;;;iBAQxB,uBAAuB;;;;;;;;iBAavB,uBAAuB;;;;;cAW1B;iBAEG,eAAe"}