audit-tools 0.40.1 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/audit/cli/auditStep.d.ts +1 -1
  2. package/dist/audit/cli/conceptualDispatch.d.ts +3 -0
  3. package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
  4. package/dist/audit/cli/conceptualDispatch.js +82 -24
  5. package/dist/audit/cli/conceptualDispatch.js.map +1 -1
  6. package/dist/audit/cli/dispatch/hostHandoff.d.ts +12 -0
  7. package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
  8. package/dist/audit/cli/dispatch/hostHandoff.js +84 -56
  9. package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
  10. package/dist/audit/cli/fanoutLanes.d.ts +47 -11
  11. package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
  12. package/dist/audit/cli/fanoutLanes.js +15 -7
  13. package/dist/audit/cli/fanoutLanes.js.map +1 -1
  14. package/dist/audit/cli/laneSubmissions.d.ts +241 -0
  15. package/dist/audit/cli/laneSubmissions.d.ts.map +1 -0
  16. package/dist/audit/cli/laneSubmissions.js +438 -0
  17. package/dist/audit/cli/laneSubmissions.js.map +1 -0
  18. package/dist/audit/cli/laneValidators.d.ts +65 -0
  19. package/dist/audit/cli/laneValidators.d.ts.map +1 -0
  20. package/dist/audit/cli/laneValidators.js +145 -0
  21. package/dist/audit/cli/laneValidators.js.map +1 -0
  22. package/dist/audit/cli/nextStepCommand.d.ts +1 -1
  23. package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
  24. package/dist/audit/cli/nextStepCommand.js +118 -69
  25. package/dist/audit/cli/nextStepCommand.js.map +1 -1
  26. package/dist/audit/cli/nextStepHelpers.d.ts +73 -42
  27. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  28. package/dist/audit/cli/nextStepHelpers.js +389 -253
  29. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  30. package/dist/audit/cli/recoverSubmissionCommand.d.ts +16 -0
  31. package/dist/audit/cli/recoverSubmissionCommand.d.ts.map +1 -0
  32. package/dist/audit/cli/recoverSubmissionCommand.js +73 -0
  33. package/dist/audit/cli/recoverSubmissionCommand.js.map +1 -0
  34. package/dist/audit/cli/semanticReviewStep.d.ts +3 -0
  35. package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
  36. package/dist/audit/cli/semanticReviewStep.js +26 -1
  37. package/dist/audit/cli/semanticReviewStep.js.map +1 -1
  38. package/dist/audit/cli/steps.d.ts +75 -2
  39. package/dist/audit/cli/steps.d.ts.map +1 -1
  40. package/dist/audit/cli/steps.js +14 -0
  41. package/dist/audit/cli/steps.js.map +1 -1
  42. package/dist/audit/cli.d.ts.map +1 -1
  43. package/dist/audit/cli.js +5 -1
  44. package/dist/audit/cli.js.map +1 -1
  45. package/dist/audit/contracts/wrapperResponse.d.ts +7 -7
  46. package/dist/audit/contracts/wrapperResponse.js +1 -1
  47. package/dist/audit/contracts/wrapperResponse.js.map +1 -1
  48. package/dist/audit/io/artifacts.d.ts +12 -1
  49. package/dist/audit/io/artifacts.d.ts.map +1 -1
  50. package/dist/audit/io/artifacts.js +28 -1
  51. package/dist/audit/io/artifacts.js.map +1 -1
  52. package/dist/audit/orchestrator/synthesisExecutors.d.ts.map +1 -1
  53. package/dist/audit/orchestrator/synthesisExecutors.js +2 -0
  54. package/dist/audit/orchestrator/synthesisExecutors.js.map +1 -1
  55. package/dist/audit/reporting/synthesis.d.ts +9 -1
  56. package/dist/audit/reporting/synthesis.d.ts.map +1 -1
  57. package/dist/audit/reporting/synthesis.js +69 -1
  58. package/dist/audit/reporting/synthesis.js.map +1 -1
  59. package/dist/audit/supervisor/operatorHandoff.d.ts +9 -2
  60. package/dist/audit/supervisor/operatorHandoff.d.ts.map +1 -1
  61. package/dist/audit/supervisor/operatorHandoff.js +17 -10
  62. package/dist/audit/supervisor/operatorHandoff.js.map +1 -1
  63. package/dist/audit/types/designAssessment.d.ts +9 -5
  64. package/dist/audit/types/designAssessment.d.ts.map +1 -1
  65. package/dist/remediate/index.d.ts.map +1 -1
  66. package/dist/remediate/index.js +48 -1
  67. package/dist/remediate/index.js.map +1 -1
  68. package/dist/remediate/steps/dispatch/hostHandoff.d.ts +38 -6
  69. package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
  70. package/dist/remediate/steps/dispatch/hostHandoff.js +87 -75
  71. package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
  72. package/dist/shared/decompose/charterClarification.d.ts +1 -1
  73. package/dist/shared/decompose/charterClarification.js +1 -1
  74. package/dist/shared/decompose/charterExtraction.js +1 -1
  75. package/dist/shared/decompose/charterExtraction.js.map +1 -1
  76. package/dist/shared/decompose/systemicChallenge.js +1 -1
  77. package/dist/shared/decompose/systemicChallenge.js.map +1 -1
  78. package/dist/shared/index.d.ts +11 -1
  79. package/dist/shared/index.d.ts.map +1 -1
  80. package/dist/shared/index.js +6 -1
  81. package/dist/shared/index.js.map +1 -1
  82. package/dist/shared/io/auditToolsPaths.d.ts +24 -4
  83. package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
  84. package/dist/shared/io/auditToolsPaths.js +29 -5
  85. package/dist/shared/io/auditToolsPaths.js.map +1 -1
  86. package/dist/shared/io/schemaVersion.d.ts +5 -4
  87. package/dist/shared/io/schemaVersion.d.ts.map +1 -1
  88. package/dist/shared/io/schemaVersion.js +17 -5
  89. package/dist/shared/io/schemaVersion.js.map +1 -1
  90. package/dist/shared/loopCorePaths.d.ts.map +1 -1
  91. package/dist/shared/loopCorePaths.js +8 -2
  92. package/dist/shared/loopCorePaths.js.map +1 -1
  93. package/dist/shared/submission/expectedSubmissions.d.ts +88 -0
  94. package/dist/shared/submission/expectedSubmissions.d.ts.map +1 -0
  95. package/dist/shared/submission/expectedSubmissions.js +84 -0
  96. package/dist/shared/submission/expectedSubmissions.js.map +1 -0
  97. package/dist/shared/submission/handRecovery.d.ts +37 -0
  98. package/dist/shared/submission/handRecovery.d.ts.map +1 -0
  99. package/dist/shared/submission/handRecovery.js +123 -0
  100. package/dist/shared/submission/handRecovery.js.map +1 -0
  101. package/dist/shared/submission/submissionClassifier.d.ts +48 -0
  102. package/dist/shared/submission/submissionClassifier.d.ts.map +1 -0
  103. package/dist/shared/submission/submissionClassifier.js +83 -0
  104. package/dist/shared/submission/submissionClassifier.js.map +1 -0
  105. package/dist/shared/submission/submissionIdentity.d.ts +49 -0
  106. package/dist/shared/submission/submissionIdentity.d.ts.map +1 -0
  107. package/dist/shared/submission/submissionIdentity.js +90 -0
  108. package/dist/shared/submission/submissionIdentity.js.map +1 -0
  109. package/dist/shared/submission/submissionLedger.d.ts +37 -0
  110. package/dist/shared/submission/submissionLedger.d.ts.map +1 -0
  111. package/dist/shared/submission/submissionLedger.js +81 -0
  112. package/dist/shared/submission/submissionLedger.js.map +1 -0
  113. package/docs/audit-pkg/product.md +0 -2
  114. package/package.json +1 -1
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Hand recovery — the ONLY place a human hand may re-land a submission.
3
+ *
4
+ * An operator sometimes has to rescue a submission a host mangled. The danger
5
+ * is that the rescue becomes a second, weaker door into the tool: a path that
6
+ * skips the schema the normal lane enforces, or that lets the operator choose
7
+ * where the payload lands. Both are closed here by construction — the caller
8
+ * hands in the NORMAL lane's validator (there is no other validator to pass),
9
+ * and the destination is derived from the submission id, so `--from` says what
10
+ * to land and never where.
11
+ *
12
+ * The third property is that the rescue is not erased: a successful recovery
13
+ * appends `recovered_by_hand`, so a run repaired by an operator stays
14
+ * distinguishable from one the host got right.
15
+ */
16
+ import { unlink } from "node:fs/promises";
17
+ import { readOptionalJsonFile, writeJsonFile } from "../io/json.js";
18
+ import { discardOnSchemaVersionMismatch } from "../io/schemaVersion.js";
19
+ import { expectedSubmissionsPath, submissionsDir, } from "../io/auditToolsPaths.js";
20
+ import { EXPECTED_SET_CONTRACT_VERSION } from "./expectedSubmissions.js";
21
+ import { readSubmissionDocument } from "./submissionClassifier.js";
22
+ import { absoluteSubmissionPath, submissionPathFor, } from "./submissionIdentity.js";
23
+ import { SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION, appendSubmissionEvent, } from "./submissionLedger.js";
24
+ /**
25
+ * Resolve which lane the id belongs to from the recorded expectation, so the
26
+ * ledger entry reads in lane vocabulary. Best-effort: a draw that keeps its
27
+ * expected set somewhere other than the artifacts dir simply records the id.
28
+ *
29
+ * The set is REGENERABLE bookkeeping (rewritten at every emit), so one left by
30
+ * another release is discarded rather than read under this release's field
31
+ * semantics — a lane name is a label on the record, and a wrong one taken from
32
+ * a foreign contract is worse than the raw id this function already falls back
33
+ * to. Discarding lands on exactly that existing degrade path.
34
+ */
35
+ async function resolveLane(artifactsDir, submissionId) {
36
+ const set = discardOnSchemaVersionMismatch(await readOptionalJsonFile(expectedSubmissionsPath(artifactsDir)).catch(() => undefined), EXPECTED_SET_CONTRACT_VERSION);
37
+ return (set?.entries?.find((entry) => entry.submission_id === submissionId)?.lane ??
38
+ submissionId);
39
+ }
40
+ /**
41
+ * Validate the operator's payload with the normal lane's validator and, only
42
+ * if it passes, land it at the tool-owned path. A refusal writes nothing and
43
+ * records nothing — a run must never look hand-repaired because a repair was
44
+ * ATTEMPTED.
45
+ */
46
+ export async function recoverSubmission(request, validate) {
47
+ const paths = {
48
+ root: request.root,
49
+ submissionDir: request.submissionDir ?? submissionsDir(request.artifactsDir),
50
+ };
51
+ // BOTH destinations are resolved first, because both can refuse: the
52
+ // repo-relative form runs the containment check, and a submission directory
53
+ // that escapes the declared root throws. Computed after the write, that
54
+ // refusal arrived with the payload already landed and `recovered_by_hand`
55
+ // already appended — a rescue reported as failed that a re-run would then
56
+ // record twice. Resolved here, a refusal is what the rejection path already
57
+ // guarantees: nothing written, nothing recorded.
58
+ const landingPath = absoluteSubmissionPath(paths, request.submissionId);
59
+ const boundPath = submissionPathFor(paths, request.submissionId);
60
+ const read = await readSubmissionDocument(request.fromPath);
61
+ if (read.kind === "missing") {
62
+ return {
63
+ ok: false,
64
+ issue: {
65
+ code: "submission_missing",
66
+ message: `nothing to recover at ${request.fromPath}`,
67
+ submission_id: request.submissionId,
68
+ },
69
+ };
70
+ }
71
+ if (read.kind === "malformed") {
72
+ return {
73
+ ok: false,
74
+ issue: {
75
+ code: "submission_malformed",
76
+ message: `the payload at ${request.fromPath} is not JSON: ${read.detail}`,
77
+ submission_id: request.submissionId,
78
+ },
79
+ };
80
+ }
81
+ const issue = validate(read.value);
82
+ if (issue !== null) {
83
+ return { ok: false, issue };
84
+ }
85
+ const lane = request.lane ?? (await resolveLane(request.artifactsDir, request.submissionId));
86
+ await writeJsonFile(landingPath, read.value);
87
+ try {
88
+ await appendSubmissionEvent(request.artifactsDir, {
89
+ contract_version: SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION,
90
+ run_id: request.runId,
91
+ submission_id: request.submissionId,
92
+ lane,
93
+ kind: "recovered_by_hand",
94
+ message: `re-landed by hand from ${request.fromPath}`,
95
+ recorded_at: new Date().toISOString(),
96
+ });
97
+ }
98
+ catch (error) {
99
+ // The payload is landed but the repair is unrecorded — the single state
100
+ // this verb must never leave behind, because the next `next-step` would
101
+ // consume it as a clean first-try submission and the run would read as one
102
+ // that never drifted. That is precisely the distinguishability the ledger
103
+ // exists to guarantee, so the write is rolled back and the operator keeps a
104
+ // failed rescue they can retry rather than a silent falsification.
105
+ const detail = error instanceof Error ? error.message : String(error);
106
+ try {
107
+ await unlink(landingPath);
108
+ }
109
+ catch (rollbackError) {
110
+ const rollbackDetail = rollbackError instanceof Error
111
+ ? rollbackError.message
112
+ : String(rollbackError);
113
+ throw new Error(`hand recovery for '${request.submissionId}' could not record the ledger event ` +
114
+ `(${detail}), and the landed payload could NOT be rolled back (${rollbackDetail}). ` +
115
+ `The payload IS on disk at ${boundPath} with no recovered_by_hand record — ` +
116
+ "remove it or retry the recovery before the next next-step consumes it.", { cause: error });
117
+ }
118
+ throw new Error(`hand recovery for '${request.submissionId}' could not record the ledger event: ` +
119
+ `${detail}. The payload was NOT landed (rolled back) — retry the recovery.`, { cause: error });
120
+ }
121
+ return { ok: true, submission_path: boundPath };
122
+ }
123
+ //# sourceMappingURL=handRecovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handRecovery.js","sourceRoot":"","sources":["../../../src/shared/submission/handRecovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ export declare const SUBMISSION_ISSUE_CODES: readonly ["submission_missing", "submission_malformed", "submission_contract_invalid", "submission_rejected", "duplicate_submission_id"];
2
+ export type SubmissionIssueCode = (typeof SUBMISSION_ISSUE_CODES)[number];
3
+ /**
4
+ * One classified failure. `TCode` widens for a draw that adds its own domain
5
+ * codes; the base parameterization is the shared vocabulary above.
6
+ *
7
+ * Two optional locators, because a submission is identified differently on the
8
+ * two lanes it can arrive through: an expected-set member is named by
9
+ * `submission_id` + `submission_path`, and a host work item by `work_item_id` +
10
+ * `result_path` (the field name its own persisted contract already uses).
11
+ */
12
+ export interface SubmissionIssue<TCode extends string = SubmissionIssueCode> {
13
+ readonly code: TCode;
14
+ readonly message: string;
15
+ readonly submission_id?: string;
16
+ readonly submission_path?: string;
17
+ readonly work_item_id?: string;
18
+ readonly result_path?: string;
19
+ }
20
+ /** The three-way read every gate narrows on — never a throw out of one lane. */
21
+ export type SubmissionReadOutcome = {
22
+ readonly kind: "missing";
23
+ } | {
24
+ readonly kind: "malformed";
25
+ readonly detail: string;
26
+ } | {
27
+ readonly kind: "value";
28
+ readonly value: unknown;
29
+ };
30
+ /**
31
+ * Read the bytes at a bound path and classify the outcome. A file that is not
32
+ * there and a file that will not parse are DIFFERENT answers; every other IO
33
+ * failure reads as malformed rather than escaping, so one unreadable lane can
34
+ * never destroy a sibling lane's already-consumed work.
35
+ */
36
+ export declare function readSubmissionDocument(absolutePath: string): Promise<SubmissionReadOutcome>;
37
+ /** Where a classified read came from, for the issue's message and locators. */
38
+ export interface SubmissionReadContext {
39
+ readonly submissionId: string;
40
+ readonly lane: string;
41
+ readonly submissionPath: string;
42
+ }
43
+ /**
44
+ * Turn a non-`value` read into the issue that names it, or `null` when the
45
+ * submission arrived. Lane vocabulary throughout: a member is a lane.
46
+ */
47
+ export declare function classifyRead(outcome: SubmissionReadOutcome, context: SubmissionReadContext): SubmissionIssue | null;
48
+ //# sourceMappingURL=submissionClassifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissionClassifier.d.ts","sourceRoot":"","sources":["../../../src/shared/submission/submissionClassifier.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,sBAAsB,0IAWzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe,CAAC,KAAK,SAAS,MAAM,GAAG,mBAAmB;IACzE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,gFAAgF;AAChF,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAExD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,qBAAqB,CAAC,CAqBhC;AAED,+EAA+E;AAC/E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,qBAAqB,EAC9B,OAAO,EAAE,qBAAqB,GAC7B,eAAe,GAAG,IAAI,CAkBxB"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The ONE failure vocabulary for a host submission both orchestrators read.
3
+ *
4
+ * Before this module the two draws had drifted: remediate classified a failed
5
+ * read into a named issue code, while the audit ingest collapsed a missing
6
+ * file, unparseable bytes, a contract-invalid body and a failed conversion into
7
+ * the same bare `null` — so a host that never wrote its submission and a host
8
+ * that wrote garbage were indistinguishable to every caller. One union, two
9
+ * thin draws.
10
+ *
11
+ * The vocabulary is deliberately narrow: it names what happened to a
12
+ * SUBMISSION. Domain corroboration a single draw performs (git ancestry, write
13
+ * scope, a required test rerun) extends this union on that draw's side rather
14
+ * than pulling its vocabulary in here — see `RemediationIssueCode`.
15
+ */
16
+ import { readFile } from "node:fs/promises";
17
+ export const SUBMISSION_ISSUE_CODES = [
18
+ /** Nothing exists at the bound path. */
19
+ "submission_missing",
20
+ /** Something exists at the bound path but is not JSON. */
21
+ "submission_malformed",
22
+ /** Valid JSON that fails the lane's schema / binding contract. */
23
+ "submission_contract_invalid",
24
+ /** Well-formed and valid, but refused for a stated reason. */
25
+ "submission_rejected",
26
+ /** A second submission re-uses an identity already accepted this run. */
27
+ "duplicate_submission_id",
28
+ ];
29
+ /**
30
+ * Read the bytes at a bound path and classify the outcome. A file that is not
31
+ * there and a file that will not parse are DIFFERENT answers; every other IO
32
+ * failure reads as malformed rather than escaping, so one unreadable lane can
33
+ * never destroy a sibling lane's already-consumed work.
34
+ */
35
+ export async function readSubmissionDocument(absolutePath) {
36
+ let source;
37
+ try {
38
+ source = await readFile(absolutePath, "utf8");
39
+ }
40
+ catch (error) {
41
+ if (error.code === "ENOENT") {
42
+ return { kind: "missing" };
43
+ }
44
+ return {
45
+ kind: "malformed",
46
+ detail: error instanceof Error ? error.message : String(error),
47
+ };
48
+ }
49
+ try {
50
+ return { kind: "value", value: JSON.parse(source) };
51
+ }
52
+ catch (error) {
53
+ return {
54
+ kind: "malformed",
55
+ detail: error instanceof Error ? error.message : String(error),
56
+ };
57
+ }
58
+ }
59
+ /**
60
+ * Turn a non-`value` read into the issue that names it, or `null` when the
61
+ * submission arrived. Lane vocabulary throughout: a member is a lane.
62
+ */
63
+ export function classifyRead(outcome, context) {
64
+ if (outcome.kind === "value")
65
+ return null;
66
+ const locators = {
67
+ submission_id: context.submissionId,
68
+ submission_path: context.submissionPath,
69
+ };
70
+ if (outcome.kind === "missing") {
71
+ return {
72
+ code: "submission_missing",
73
+ message: `lane '${context.lane}' submitted nothing at its bound path`,
74
+ ...locators,
75
+ };
76
+ }
77
+ return {
78
+ code: "submission_malformed",
79
+ message: `lane '${context.lane}' submitted bytes that are not JSON: ${outcome.detail}`,
80
+ ...locators,
81
+ };
82
+ }
83
+ //# sourceMappingURL=submissionClassifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissionClassifier.js","sourceRoot":"","sources":["../../../src/shared/submission/submissionClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,wCAAwC;IACxC,oBAAoB;IACpB,0DAA0D;IAC1D,sBAAsB;IACtB,kEAAkE;IAClE,6BAA6B;IAC7B,8DAA8D;IAC9D,qBAAqB;IACrB,yEAAyE;IACzE,yBAAyB;CACjB,CAAC;AA4BX;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,YAAoB;IAEpB,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACvD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7B,CAAC;QACD,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC/D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAY,EAAE,CAAC;IACjE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC/D,CAAC;IACJ,CAAC;AACH,CAAC;AASD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,OAA8B,EAC9B,OAA8B;IAE9B,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,QAAQ,GAAG;QACf,aAAa,EAAE,OAAO,CAAC,YAAY;QACnC,eAAe,EAAE,OAAO,CAAC,cAAc;KAC/B,CAAC;IACX,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO;YACL,IAAI,EAAE,oBAAoB;YAC1B,OAAO,EAAE,SAAS,OAAO,CAAC,IAAI,uCAAuC;YACrE,GAAG,QAAQ;SACZ,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,sBAAsB;QAC5B,OAAO,EAAE,SAAS,OAAO,CAAC,IAAI,wCAAwC,OAAO,CAAC,MAAM,EAAE;QACtF,GAAG,QAAQ;KACZ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Where submissions live for one draw: the repository root a bound path is
3
+ * expressed relative to, and the directory the payload files sit in.
4
+ */
5
+ export interface SubmissionRoots {
6
+ readonly root: string;
7
+ readonly submissionDir: string;
8
+ }
9
+ /** The identity components a submission id is derived from. */
10
+ export interface SubmissionIdParts {
11
+ /** The obligation/gate family the submission answers. */
12
+ readonly kind: string;
13
+ /** The content-coherent lane within that family. */
14
+ readonly lane: string;
15
+ /** The run (or run-equivalent scope) the lane belongs to. */
16
+ readonly runId: string;
17
+ }
18
+ /**
19
+ * Mint the submission id for one lane. DETERMINISTIC: the same kind + lane +
20
+ * run always yields the same id, so a re-emitted step re-declares the identical
21
+ * bound path and an already-satisfied lane is recognized on resume (K-of-N).
22
+ * No timestamp, no randomness — an id that changed per call would re-ask for
23
+ * work the host already delivered.
24
+ *
25
+ * The readable prefix is for humans reading the ledger; the trailing digest is
26
+ * what makes distinct lanes distinct.
27
+ */
28
+ export declare function mintSubmissionId(parts: SubmissionIdParts): string;
29
+ /** Absolute on-disk path of one submission payload. */
30
+ export declare function absoluteSubmissionPath(paths: SubmissionRoots, submissionId: string): string;
31
+ /**
32
+ * The bound path as it is declared in a step contract and rendered to a host:
33
+ * repository-relative and forward-slashed, so the same contract reads
34
+ * identically on win32 / darwin / linux.
35
+ */
36
+ export declare function submissionPathFor(paths: SubmissionRoots, submissionId: string): string;
37
+ /**
38
+ * Resolve `candidate` against `base` and refuse anything that escapes it. The
39
+ * containment rule both host handoffs hand-rolled.
40
+ */
41
+ export declare function resolveContainedPath(base: string, candidate: string, label: string): string;
42
+ /** Repository-relative, forward-slashed form of a contained absolute path. */
43
+ export declare function repoRelativePath(root: string, absolutePath: string, label: string): string;
44
+ /**
45
+ * The shared run-id grammar. A run id becomes a directory segment, so it may
46
+ * carry nothing that could climb out of the artifacts tree.
47
+ */
48
+ export declare function assertSubmissionRunId(runId: string, label?: string): void;
49
+ //# sourceMappingURL=submissionIdentity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissionIdentity.d.ts","sourceRoot":"","sources":["../../../src/shared/submission/submissionIdentity.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,+DAA+D;AAC/D,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAOD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAMjE;AAUD,uDAAuD;AACvD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,eAAe,EACtB,YAAY,EAAE,MAAM,GACnB,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,eAAe,EACtB,YAAY,EAAE,MAAM,GACnB,MAAM,CAMR;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,MAAM,CAWR;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,GACZ,MAAM,CAOR;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAW,GAAG,IAAI,CAQ3E"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The ONE tool-owned submission path rule.
3
+ *
4
+ * A host submission lands at `<submissionDir>/<sha256(submission_id)>.json` and
5
+ * nowhere else. The id is minted by the tool at step-emit time and the path is
6
+ * derived from it, so a host cannot invent, mistype, or re-render the name of a
7
+ * file the tool will read — a write to any other path is simply not read.
8
+ *
9
+ * Both draws share this module: the audit host handoff binds its work-item
10
+ * results through it, and the audit gate lanes bind their submissions through
11
+ * it. It replaces the two hand-rolled `resultPathFor` copies that carried the
12
+ * same rule in two places.
13
+ */
14
+ import { isAbsolute, join, relative, resolve } from "node:path";
15
+ import { hashContent } from "../hash.js";
16
+ import { stableStringify } from "../stableStringify.js";
17
+ /** Non-path-safe characters collapse so the id stays readable in a ledger. */
18
+ function slug(value) {
19
+ return value.replace(/[^A-Za-z0-9._-]+/gu, "-").replace(/^-+|-+$/gu, "");
20
+ }
21
+ /**
22
+ * Mint the submission id for one lane. DETERMINISTIC: the same kind + lane +
23
+ * run always yields the same id, so a re-emitted step re-declares the identical
24
+ * bound path and an already-satisfied lane is recognized on resume (K-of-N).
25
+ * No timestamp, no randomness — an id that changed per call would re-ask for
26
+ * work the host already delivered.
27
+ *
28
+ * The readable prefix is for humans reading the ledger; the trailing digest is
29
+ * what makes distinct lanes distinct.
30
+ */
31
+ export function mintSubmissionId(parts) {
32
+ const digest = hashContent(stableStringify({ kind: parts.kind, lane: parts.lane, run_id: parts.runId }), { length: 16 });
33
+ return `${slug(parts.kind)}.${slug(parts.lane)}.${digest}`;
34
+ }
35
+ /** The single filename rule: the sha256 of the id, nothing else. */
36
+ function submissionFilename(submissionId) {
37
+ if (typeof submissionId !== "string" || submissionId.length === 0) {
38
+ throw new Error("submission id must be a non-empty string");
39
+ }
40
+ return `${hashContent(submissionId)}.json`;
41
+ }
42
+ /** Absolute on-disk path of one submission payload. */
43
+ export function absoluteSubmissionPath(paths, submissionId) {
44
+ return join(resolve(paths.submissionDir), submissionFilename(submissionId));
45
+ }
46
+ /**
47
+ * The bound path as it is declared in a step contract and rendered to a host:
48
+ * repository-relative and forward-slashed, so the same contract reads
49
+ * identically on win32 / darwin / linux.
50
+ */
51
+ export function submissionPathFor(paths, submissionId) {
52
+ return repoRelativePath(paths.root, absoluteSubmissionPath(paths, submissionId), `submission path for ${submissionId}`);
53
+ }
54
+ /**
55
+ * Resolve `candidate` against `base` and refuse anything that escapes it. The
56
+ * containment rule both host handoffs hand-rolled.
57
+ */
58
+ export function resolveContainedPath(base, candidate, label) {
59
+ const absoluteBase = resolve(base);
60
+ const absoluteCandidate = isAbsolute(candidate)
61
+ ? resolve(candidate)
62
+ : resolve(absoluteBase, candidate);
63
+ const rel = relative(absoluteBase, absoluteCandidate);
64
+ const firstSegment = rel.split(/[\\/]/u)[0];
65
+ if (isAbsolute(rel) || firstSegment === "..") {
66
+ throw new Error(`${label} must remain beneath ${absoluteBase}`);
67
+ }
68
+ return absoluteCandidate;
69
+ }
70
+ /** Repository-relative, forward-slashed form of a contained absolute path. */
71
+ export function repoRelativePath(root, absolutePath, label) {
72
+ const contained = resolveContainedPath(root, absolutePath, label);
73
+ const rel = relative(resolve(root), contained).replaceAll("\\", "/");
74
+ if (rel.length === 0) {
75
+ throw new Error(`${label} must identify a path beneath the repository root`);
76
+ }
77
+ return rel;
78
+ }
79
+ /**
80
+ * The shared run-id grammar. A run id becomes a directory segment, so it may
81
+ * carry nothing that could climb out of the artifacts tree.
82
+ */
83
+ export function assertSubmissionRunId(runId, label = "run id") {
84
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(runId) ||
85
+ runId === "." ||
86
+ runId === "..") {
87
+ throw new Error(`Invalid ${label}: ${JSON.stringify(runId)}`);
88
+ }
89
+ }
90
+ //# sourceMappingURL=submissionIdentity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissionIdentity.js","sourceRoot":"","sources":["../../../src/shared/submission/submissionIdentity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEhE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAqBxD,8EAA8E;AAC9E,SAAS,IAAI,CAAC,KAAa;IACzB,OAAO,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAwB;IACvD,MAAM,MAAM,GAAG,WAAW,CACxB,eAAe,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,EAC5E,EAAE,MAAM,EAAE,EAAE,EAAE,CACf,CAAC;IACF,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,EAAE,CAAC;AAC7D,CAAC;AAED,oEAAoE;AACpE,SAAS,kBAAkB,CAAC,YAAoB;IAC9C,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,GAAG,WAAW,CAAC,YAAY,CAAC,OAAO,CAAC;AAC7C,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,sBAAsB,CACpC,KAAsB,EACtB,YAAoB;IAEpB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAsB,EACtB,YAAoB;IAEpB,OAAO,gBAAgB,CACrB,KAAK,CAAC,IAAI,EACV,sBAAsB,CAAC,KAAK,EAAE,YAAY,CAAC,EAC3C,uBAAuB,YAAY,EAAE,CACtC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,SAAiB,EACjB,KAAa;IAEb,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,iBAAiB,GAAG,UAAU,CAAC,SAAS,CAAC;QAC7C,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QACpB,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,QAAQ,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,wBAAwB,YAAY,EAAE,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,YAAoB,EACpB,KAAa;IAEb,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;IAClE,MAAM,GAAG,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACrE,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,mDAAmD,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,KAAK,GAAG,QAAQ;IACnE,IACE,CAAC,qCAAqC,CAAC,IAAI,CAAC,KAAK,CAAC;QAClD,KAAK,KAAK,GAAG;QACb,KAAK,KAAK,IAAI,EACd,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { SubmissionIssueCode } from "./submissionClassifier.js";
2
+ export declare const SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION: "submission-ledger-event/v1alpha1";
3
+ export declare const SUBMISSION_EVENT_KINDS: readonly ["expected", "accepted", "rejected", "recovered_by_hand"];
4
+ export type SubmissionEventKind = (typeof SUBMISSION_EVENT_KINDS)[number];
5
+ export interface SubmissionLedgerEvent {
6
+ readonly contract_version: typeof SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION;
7
+ readonly run_id: string;
8
+ readonly submission_id: string;
9
+ readonly lane: string;
10
+ readonly kind: SubmissionEventKind;
11
+ readonly issue_code?: SubmissionIssueCode;
12
+ readonly message?: string;
13
+ /** ISO-8601. A faithful event record is allowed to say when. */
14
+ readonly recorded_at: string;
15
+ }
16
+ /** `<artifactsDir>/submissions/submission-ledger.jsonl`. */
17
+ export declare function submissionLedgerPath(artifactsDir: string): string;
18
+ /** Append one event. The parent directory is created on demand. */
19
+ export declare function appendSubmissionEvent(artifactsDir: string, event: SubmissionLedgerEvent): Promise<void>;
20
+ /**
21
+ * Read the ledger in arrival order. An absent ledger reads as empty — a run
22
+ * that never drifted has nothing to say — and a partially-written tail is
23
+ * skipped rather than thrown, because a bookkeeping record must never be able
24
+ * to fail the call it is recording.
25
+ *
26
+ * An event stamped with another release's contract version is skipped EXACTLY
27
+ * like a torn line, per event. The FILE stays a faithful historical record —
28
+ * nothing is rewritten or dropped from disk — but this function is a REPORTING
29
+ * surface, and its callers read `kind`, `issue_code` and `message` to decide
30
+ * whether a lane is outstanding because it was refused, and to dedupe against
31
+ * the last recorded event. Reinterpreting a foreign contract's event under
32
+ * those field semantics is how a run gets MISreported; skipping it degrades to
33
+ * the same shape as a ledger that had not recorded that event yet. The skip is
34
+ * per line, so the current release's events on either side of it still load.
35
+ */
36
+ export declare function readSubmissionLedger(artifactsDir: string): Promise<readonly SubmissionLedgerEvent[]>;
37
+ //# sourceMappingURL=submissionLedger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissionLedger.d.ts","sourceRoot":"","sources":["../../../src/shared/submission/submissionLedger.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,eAAO,MAAM,wCAAwC,EACnD,kCAA2C,CAAC;AAE9C,eAAO,MAAM,sBAAsB,oEASzB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,wCAAwC,CAAC;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,mEAAmE;AACnE,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,SAAS,qBAAqB,EAAE,CAAC,CAsB3C"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The append-only record of what happened to each submission.
3
+ *
4
+ * A run that drifted and was repaired must stay distinguishable, after the
5
+ * fact, from a run that was clean on the first try. Nothing in the pipeline
6
+ * used to survive the call: an issue rode the returned summary into a prompt
7
+ * string and was gone. So "the host got it right 9 times out of 10" was a fact
8
+ * a human read off a transcript rather than one the artifacts could state.
9
+ *
10
+ * NDJSON, appended, never rewritten: ARRIVAL order is the file order and a
11
+ * rejection stays on the record after the later acceptance lands. This is the
12
+ * one place the stable-content-order rule deliberately does NOT apply — the
13
+ * ledger is a faithful EVENT record, not a derived artifact, and re-sorting it
14
+ * by timestamp or id would erase exactly the sequence it exists to preserve.
15
+ * Any derived summary a reporter renders from it may sort; the file may not.
16
+ */
17
+ import { join } from "node:path";
18
+ import { readFile } from "node:fs/promises";
19
+ import { appendNdjsonFile } from "../io/json.js";
20
+ import { discardOnSchemaVersionMismatch } from "../io/schemaVersion.js";
21
+ import { submissionsDir } from "../io/auditToolsPaths.js";
22
+ export const SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION = "submission-ledger-event/v1alpha1";
23
+ export const SUBMISSION_EVENT_KINDS = [
24
+ /** The emission declared this lane owed a submission. */
25
+ "expected",
26
+ /** A valid submission arrived and was applied. */
27
+ "accepted",
28
+ /** A submission arrived and was refused (held, never discarded). */
29
+ "rejected",
30
+ /** An operator re-landed a submission by hand through the recovery lane. */
31
+ "recovered_by_hand",
32
+ ];
33
+ /** `<artifactsDir>/submissions/submission-ledger.jsonl`. */
34
+ export function submissionLedgerPath(artifactsDir) {
35
+ return join(submissionsDir(artifactsDir), "submission-ledger.jsonl");
36
+ }
37
+ /** Append one event. The parent directory is created on demand. */
38
+ export async function appendSubmissionEvent(artifactsDir, event) {
39
+ await appendNdjsonFile(submissionLedgerPath(artifactsDir), event);
40
+ }
41
+ /**
42
+ * Read the ledger in arrival order. An absent ledger reads as empty — a run
43
+ * that never drifted has nothing to say — and a partially-written tail is
44
+ * skipped rather than thrown, because a bookkeeping record must never be able
45
+ * to fail the call it is recording.
46
+ *
47
+ * An event stamped with another release's contract version is skipped EXACTLY
48
+ * like a torn line, per event. The FILE stays a faithful historical record —
49
+ * nothing is rewritten or dropped from disk — but this function is a REPORTING
50
+ * surface, and its callers read `kind`, `issue_code` and `message` to decide
51
+ * whether a lane is outstanding because it was refused, and to dedupe against
52
+ * the last recorded event. Reinterpreting a foreign contract's event under
53
+ * those field semantics is how a run gets MISreported; skipping it degrades to
54
+ * the same shape as a ledger that had not recorded that event yet. The skip is
55
+ * per line, so the current release's events on either side of it still load.
56
+ */
57
+ export async function readSubmissionLedger(artifactsDir) {
58
+ let content;
59
+ try {
60
+ content = await readFile(submissionLedgerPath(artifactsDir), "utf8");
61
+ }
62
+ catch {
63
+ return [];
64
+ }
65
+ const events = [];
66
+ for (const line of content.split(/\r?\n/u)) {
67
+ if (line.trim().length === 0)
68
+ continue;
69
+ try {
70
+ const event = discardOnSchemaVersionMismatch(JSON.parse(line), SUBMISSION_LEDGER_EVENT_CONTRACT_VERSION);
71
+ if (event !== undefined)
72
+ events.push(event);
73
+ }
74
+ catch {
75
+ // A torn final line (crash mid-append) drops; every complete event before
76
+ // it stays readable, which is the whole point of an append-only record.
77
+ }
78
+ }
79
+ return events;
80
+ }
81
+ //# sourceMappingURL=submissionLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submissionLedger.js","sourceRoot":"","sources":["../../../src/shared/submission/submissionLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG1D,MAAM,CAAC,MAAM,wCAAwC,GACnD,kCAA2C,CAAC;AAE9C,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,yDAAyD;IACzD,UAAU;IACV,kDAAkD;IAClD,UAAU;IACV,oEAAoE;IACpE,UAAU;IACV,4EAA4E;IAC5E,mBAAmB;CACX,CAAC;AAgBX,4DAA4D;AAC5D,MAAM,UAAU,oBAAoB,CAAC,YAAoB;IACvD,OAAO,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,yBAAyB,CAAC,CAAC;AACvE,CAAC;AAED,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,YAAoB,EACpB,KAA4B;IAE5B,MAAM,gBAAgB,CAAC,oBAAoB,CAAC,YAAY,CAAC,EAAE,KAAK,CAAC,CAAC;AACpE,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,YAAoB;IAEpB,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,oBAAoB,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACvC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,8BAA8B,CAC1C,IAAI,CAAC,KAAK,CAAC,IAAI,CAA0B,EACzC,wCAAwC,CACzC,CAAC;YACF,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,wEAAwE;QAC1E,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -113,7 +113,5 @@ crossings, weak-packet gaps) is how extraction gaps are found and prioritized.
113
113
  ## Non-goals
114
114
 
115
115
  - repositioning the CLI as a peer product surface
116
- - making session config the normal way to redirect semantic review into a
117
- second execution backend
118
116
  - making backend implementation details outrank the conversation contract
119
117
  - tying packetization quality to one programming language
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "audit-tools",
3
- "version": "0.40.1",
3
+ "version": "0.41.0",
4
4
  "private": false,
5
5
  "license": "ISC",
6
6
  "description": "Provider-neutral code auditing and remediation workflows for arbitrary repositories.",