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.
- package/dist/audit/cli/auditStep.d.ts +1 -1
- package/dist/audit/cli/conceptualDispatch.d.ts +3 -0
- package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
- package/dist/audit/cli/conceptualDispatch.js +82 -24
- package/dist/audit/cli/conceptualDispatch.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts +12 -0
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +84 -56
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/fanoutLanes.d.ts +47 -11
- package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.js +15 -7
- package/dist/audit/cli/fanoutLanes.js.map +1 -1
- package/dist/audit/cli/laneSubmissions.d.ts +241 -0
- package/dist/audit/cli/laneSubmissions.d.ts.map +1 -0
- package/dist/audit/cli/laneSubmissions.js +438 -0
- package/dist/audit/cli/laneSubmissions.js.map +1 -0
- package/dist/audit/cli/laneValidators.d.ts +65 -0
- package/dist/audit/cli/laneValidators.d.ts.map +1 -0
- package/dist/audit/cli/laneValidators.js +145 -0
- package/dist/audit/cli/laneValidators.js.map +1 -0
- package/dist/audit/cli/nextStepCommand.d.ts +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +118 -69
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts +73 -42
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +389 -253
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/recoverSubmissionCommand.d.ts +16 -0
- package/dist/audit/cli/recoverSubmissionCommand.d.ts.map +1 -0
- package/dist/audit/cli/recoverSubmissionCommand.js +73 -0
- package/dist/audit/cli/recoverSubmissionCommand.js.map +1 -0
- package/dist/audit/cli/semanticReviewStep.d.ts +3 -0
- package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
- package/dist/audit/cli/semanticReviewStep.js +26 -1
- package/dist/audit/cli/semanticReviewStep.js.map +1 -1
- package/dist/audit/cli/steps.d.ts +75 -2
- package/dist/audit/cli/steps.d.ts.map +1 -1
- package/dist/audit/cli/steps.js +14 -0
- package/dist/audit/cli/steps.js.map +1 -1
- package/dist/audit/cli.d.ts.map +1 -1
- package/dist/audit/cli.js +5 -1
- package/dist/audit/cli.js.map +1 -1
- package/dist/audit/contracts/wrapperResponse.d.ts +7 -7
- package/dist/audit/contracts/wrapperResponse.js +1 -1
- package/dist/audit/contracts/wrapperResponse.js.map +1 -1
- package/dist/audit/io/artifacts.d.ts +12 -1
- package/dist/audit/io/artifacts.d.ts.map +1 -1
- package/dist/audit/io/artifacts.js +28 -1
- package/dist/audit/io/artifacts.js.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.d.ts.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.js +2 -0
- package/dist/audit/orchestrator/synthesisExecutors.js.map +1 -1
- package/dist/audit/reporting/synthesis.d.ts +9 -1
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +69 -1
- package/dist/audit/reporting/synthesis.js.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.d.ts +9 -2
- package/dist/audit/supervisor/operatorHandoff.d.ts.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.js +17 -10
- package/dist/audit/supervisor/operatorHandoff.js.map +1 -1
- package/dist/audit/types/designAssessment.d.ts +9 -5
- package/dist/audit/types/designAssessment.d.ts.map +1 -1
- package/dist/remediate/index.d.ts.map +1 -1
- package/dist/remediate/index.js +48 -1
- package/dist/remediate/index.js.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts +38 -6
- package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/remediate/steps/dispatch/hostHandoff.js +87 -75
- package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
- package/dist/shared/decompose/charterClarification.d.ts +1 -1
- package/dist/shared/decompose/charterClarification.js +1 -1
- package/dist/shared/decompose/charterExtraction.js +1 -1
- package/dist/shared/decompose/charterExtraction.js.map +1 -1
- package/dist/shared/decompose/systemicChallenge.js +1 -1
- package/dist/shared/decompose/systemicChallenge.js.map +1 -1
- package/dist/shared/index.d.ts +11 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +6 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/io/auditToolsPaths.d.ts +24 -4
- package/dist/shared/io/auditToolsPaths.d.ts.map +1 -1
- package/dist/shared/io/auditToolsPaths.js +29 -5
- package/dist/shared/io/auditToolsPaths.js.map +1 -1
- package/dist/shared/io/schemaVersion.d.ts +5 -4
- package/dist/shared/io/schemaVersion.d.ts.map +1 -1
- package/dist/shared/io/schemaVersion.js +17 -5
- package/dist/shared/io/schemaVersion.js.map +1 -1
- package/dist/shared/loopCorePaths.d.ts.map +1 -1
- package/dist/shared/loopCorePaths.js +8 -2
- package/dist/shared/loopCorePaths.js.map +1 -1
- package/dist/shared/submission/expectedSubmissions.d.ts +88 -0
- package/dist/shared/submission/expectedSubmissions.d.ts.map +1 -0
- package/dist/shared/submission/expectedSubmissions.js +84 -0
- package/dist/shared/submission/expectedSubmissions.js.map +1 -0
- package/dist/shared/submission/handRecovery.d.ts +37 -0
- package/dist/shared/submission/handRecovery.d.ts.map +1 -0
- package/dist/shared/submission/handRecovery.js +123 -0
- package/dist/shared/submission/handRecovery.js.map +1 -0
- package/dist/shared/submission/submissionClassifier.d.ts +48 -0
- package/dist/shared/submission/submissionClassifier.d.ts.map +1 -0
- package/dist/shared/submission/submissionClassifier.js +83 -0
- package/dist/shared/submission/submissionClassifier.js.map +1 -0
- package/dist/shared/submission/submissionIdentity.d.ts +49 -0
- package/dist/shared/submission/submissionIdentity.d.ts.map +1 -0
- package/dist/shared/submission/submissionIdentity.js +90 -0
- package/dist/shared/submission/submissionIdentity.js.map +1 -0
- package/dist/shared/submission/submissionLedger.d.ts +37 -0
- package/dist/shared/submission/submissionLedger.d.ts.map +1 -0
- package/dist/shared/submission/submissionLedger.js +81 -0
- package/dist/shared/submission/submissionLedger.js.map +1 -0
- package/docs/audit-pkg/product.md +0 -2
- 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":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EACL,uBAAuB,EACvB,cAAc,GACf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC;AAGzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EACL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,wCAAwC,EACxC,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AA4B/B;;;;;;;;;;GAUG;AACH,KAAK,UAAU,WAAW,CACxB,YAAoB,EACpB,YAAoB;IAEpB,MAAM,GAAG,GAAG,8BAA8B,CACxC,MAAM,oBAAoB,CACxB,uBAAuB,CAAC,YAAY,CAAC,CACtC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,EACxB,6BAA6B,CAC9B,CAAC;IACF,OAAO,CACL,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,aAAa,KAAK,YAAY,CAAC,EAAE,IAAI;QACzE,YAAY,CACb,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAA4B,EAC5B,QAAoD;IAEpD,MAAM,KAAK,GAAG;QACZ,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,cAAc,CAAC,OAAO,CAAC,YAAY,CAAC;KAC7E,CAAC;IACF,qEAAqE;IACrE,4EAA4E;IAC5E,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,4EAA4E;IAC5E,iDAAiD;IACjD,MAAM,WAAW,GAAG,sBAAsB,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACxE,MAAM,SAAS,GAAG,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,MAAM,sBAAsB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5D,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,yBAAyB,OAAO,CAAC,QAAQ,EAAE;gBACpD,aAAa,EAAE,OAAO,CAAC,YAAY;aACpC;SACF,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE;gBACL,IAAI,EAAE,sBAAsB;gBAC5B,OAAO,EAAE,kBAAkB,OAAO,CAAC,QAAQ,iBAAiB,IAAI,CAAC,MAAM,EAAE;gBACzE,aAAa,EAAE,OAAO,CAAC,YAAY;aACpC;SACF,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,MAAM,IAAI,GACR,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;IAClF,MAAM,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAE7C,IAAI,CAAC;QACH,MAAM,qBAAqB,CAAC,OAAO,CAAC,YAAY,EAAE;YAChD,gBAAgB,EAAE,wCAAwC;YAC1D,MAAM,EAAE,OAAO,CAAC,KAAK;YACrB,aAAa,EAAE,OAAO,CAAC,YAAY;YACnC,IAAI;YACJ,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,0BAA0B,OAAO,CAAC,QAAQ,EAAE;YACrD,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACtC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wEAAwE;QACxE,wEAAwE;QACxE,2EAA2E;QAC3E,0EAA0E;QAC1E,4EAA4E;QAC5E,mEAAmE;QACnE,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,aAAa,EAAE,CAAC;YACvB,MAAM,cAAc,GAClB,aAAa,YAAY,KAAK;gBAC5B,CAAC,CAAC,aAAa,CAAC,OAAO;gBACvB,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YAC5B,MAAM,IAAI,KAAK,CACb,sBAAsB,OAAO,CAAC,YAAY,sCAAsC;gBAC9E,IAAI,MAAM,uDAAuD,cAAc,KAAK;gBACpF,6BAA6B,SAAS,sCAAsC;gBAC5E,wEAAwE,EAC1E,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CACb,sBAAsB,OAAO,CAAC,YAAY,uCAAuC;YAC/E,GAAG,MAAM,kEAAkE,EAC7E,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -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":"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"}
|
|
@@ -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
|