@martinloop/mcp 0.3.9 → 0.5.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 (96) hide show
  1. package/README.md +82 -141
  2. package/dist/discovery-metadata.d.ts +1 -1
  3. package/dist/discovery-metadata.js +1 -1
  4. package/dist/resources.d.ts +1 -2
  5. package/dist/resources.js +47 -95
  6. package/dist/server-validation.d.ts +1 -1
  7. package/dist/server-validation.js +16 -11
  8. package/dist/server.d.ts +23 -0
  9. package/dist/server.js +152 -7
  10. package/dist/tools/preflight.d.ts +2 -0
  11. package/dist/tools/preflight.js +42 -2
  12. package/dist/tools/run-dossier.d.ts +3 -1
  13. package/dist/tools/run-dossier.js +40 -2
  14. package/dist/tools/run-loop.d.ts +1 -4
  15. package/dist/tools/run-loop.js +6 -15
  16. package/dist/tools/tool-support.d.ts +2 -1
  17. package/dist/tools/tool-support.js +1 -12
  18. package/dist/vendor/adapters/claude-cli.js +38 -35
  19. package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
  20. package/dist/vendor/adapters/cli-bridge.js +54 -13
  21. package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
  22. package/dist/vendor/adapters/codex-launcher.js +118 -19
  23. package/dist/vendor/adapters/index.d.ts +1 -3
  24. package/dist/vendor/adapters/index.js +1 -3
  25. package/dist/vendor/adapters/openai-compatible.js +51 -12
  26. package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
  27. package/dist/vendor/contracts/context-handoff.d.ts +100 -0
  28. package/dist/vendor/contracts/context-handoff.js +11 -0
  29. package/dist/vendor/contracts/context-manifest.d.ts +195 -0
  30. package/dist/vendor/contracts/context-manifest.js +17 -0
  31. package/dist/vendor/contracts/context-shadow.d.ts +75 -0
  32. package/dist/vendor/contracts/context-shadow.js +8 -0
  33. package/dist/vendor/contracts/delivery.d.ts +47 -0
  34. package/dist/vendor/contracts/delivery.js +18 -0
  35. package/dist/vendor/contracts/exits.d.ts +126 -0
  36. package/dist/vendor/contracts/exits.js +32 -0
  37. package/dist/vendor/contracts/index.d.ts +41 -2
  38. package/dist/vendor/contracts/index.js +24 -0
  39. package/dist/vendor/contracts/mission.d.ts +102 -0
  40. package/dist/vendor/contracts/mission.js +64 -0
  41. package/dist/vendor/contracts/operator.d.ts +3 -1
  42. package/dist/vendor/contracts/operator.js +2 -1
  43. package/dist/vendor/contracts/verified-handoff.d.ts +100 -0
  44. package/dist/vendor/contracts/verified-handoff.js +35 -0
  45. package/dist/vendor/core/context-chain-gate.d.ts +89 -0
  46. package/dist/vendor/core/context-chain-gate.js +173 -0
  47. package/dist/vendor/core/context-compiler.d.ts +53 -0
  48. package/dist/vendor/core/context-compiler.js +261 -0
  49. package/dist/vendor/core/context-handoff.d.ts +42 -0
  50. package/dist/vendor/core/context-handoff.js +167 -0
  51. package/dist/vendor/core/context-shadow.d.ts +39 -0
  52. package/dist/vendor/core/context-shadow.js +200 -0
  53. package/dist/vendor/core/delivery/index.d.ts +6 -0
  54. package/dist/vendor/core/delivery/index.js +4 -0
  55. package/dist/vendor/core/delivery/message-client.d.ts +18 -0
  56. package/dist/vendor/core/delivery/message-client.js +38 -0
  57. package/dist/vendor/core/delivery/message-ledger.d.ts +35 -0
  58. package/dist/vendor/core/delivery/message-ledger.js +97 -0
  59. package/dist/vendor/core/delivery/message-schema.d.ts +16 -0
  60. package/dist/vendor/core/delivery/message-schema.js +111 -0
  61. package/dist/vendor/core/delivery/update-check.d.ts +19 -0
  62. package/dist/vendor/core/delivery/update-check.js +107 -0
  63. package/dist/vendor/core/exit-signal.d.ts +88 -0
  64. package/dist/vendor/core/exit-signal.js +339 -0
  65. package/dist/vendor/core/exits.d.ts +41 -0
  66. package/dist/vendor/core/exits.js +258 -0
  67. package/dist/vendor/core/index.d.ts +44 -1
  68. package/dist/vendor/core/index.js +1088 -662
  69. package/dist/vendor/core/mission/aggregate.d.ts +27 -0
  70. package/dist/vendor/core/mission/aggregate.js +42 -0
  71. package/dist/vendor/core/mission/index.d.ts +2 -0
  72. package/dist/vendor/core/mission/index.js +1 -0
  73. package/dist/vendor/core/persistence/compiler.d.ts +8 -0
  74. package/dist/vendor/core/persistence/compiler.js +156 -0
  75. package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
  76. package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
  77. package/dist/vendor/core/persistence/index.d.ts +4 -0
  78. package/dist/vendor/core/persistence/index.js +2 -0
  79. package/dist/vendor/core/persistence/ledger.d.ts +1 -1
  80. package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
  81. package/dist/vendor/core/persistence/mission-store.js +281 -0
  82. package/dist/vendor/core/policy.d.ts +2 -0
  83. package/dist/vendor/core/policy.js +5 -2
  84. package/dist/vendor/core/rollback.js +15 -1
  85. package/dist/vendor/core/savings.d.ts +13 -0
  86. package/dist/vendor/core/savings.js +22 -0
  87. package/dist/vendor/core/termination-store.d.ts +15 -0
  88. package/dist/vendor/core/termination-store.js +44 -0
  89. package/dist/vendor/core/verified-handoff.d.ts +67 -0
  90. package/dist/vendor/core/verified-handoff.js +210 -0
  91. package/package.json +3 -13
  92. package/server.json +3 -3
  93. package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
  94. package/dist/vendor/adapters/stub-agent-cli.js +0 -40
  95. package/dist/vendor/adapters/verifier-only.d.ts +0 -9
  96. package/dist/vendor/adapters/verifier-only.js +0 -63
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Mission aggregation — C2
3
+ *
4
+ * Computes verified-outcome and cost metrics from a mission's linked runs.
5
+ * Does not hit the filesystem — operates on already-loaded MissionRecord.
6
+ */
7
+ import type { MissionCost, MissionRecord, MissionRunLink } from "../../contracts/index.js";
8
+ export interface MissionMetrics {
9
+ totalActualUsd: number;
10
+ verifiedOutcomeCount: number;
11
+ totalRunCount: number;
12
+ /** USD per verified outcome. Infinity when verifiedOutcomeCount === 0. */
13
+ costPerVerifiedOutcome: number;
14
+ /** Fraction of runs with verified outcomes (0–1). */
15
+ verifiedRate: number;
16
+ }
17
+ /**
18
+ * Re-derive mission cost metrics from the run links.
19
+ * Use this to rebuild the cost object from ledger-authoritative data
20
+ * rather than trusting the cached mission.json cost field.
21
+ */
22
+ export declare function aggregateMissionMetrics(runLinks: MissionRunLink[]): MissionMetrics;
23
+ /**
24
+ * Rebuild the MissionCost record from the authoritative run links.
25
+ * Call after loading from ledger to ensure the cached cost field is consistent.
26
+ */
27
+ export declare function rebuildMissionCost(mission: MissionRecord): MissionCost;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Mission aggregation — C2
3
+ *
4
+ * Computes verified-outcome and cost metrics from a mission's linked runs.
5
+ * Does not hit the filesystem — operates on already-loaded MissionRecord.
6
+ */
7
+ /**
8
+ * Re-derive mission cost metrics from the run links.
9
+ * Use this to rebuild the cost object from ledger-authoritative data
10
+ * rather than trusting the cached mission.json cost field.
11
+ */
12
+ export function aggregateMissionMetrics(runLinks) {
13
+ let totalActualUsd = 0;
14
+ let verifiedOutcomeCount = 0;
15
+ for (const link of runLinks) {
16
+ totalActualUsd += link.actualUsd ?? 0;
17
+ if (link.verifiedOutcome === true)
18
+ verifiedOutcomeCount += 1;
19
+ }
20
+ const totalRunCount = runLinks.length;
21
+ const costPerVerifiedOutcome = verifiedOutcomeCount === 0 ? Infinity : totalActualUsd / verifiedOutcomeCount;
22
+ const verifiedRate = totalRunCount === 0 ? 0 : verifiedOutcomeCount / totalRunCount;
23
+ return {
24
+ totalActualUsd,
25
+ verifiedOutcomeCount,
26
+ totalRunCount,
27
+ costPerVerifiedOutcome,
28
+ verifiedRate
29
+ };
30
+ }
31
+ /**
32
+ * Rebuild the MissionCost record from the authoritative run links.
33
+ * Call after loading from ledger to ensure the cached cost field is consistent.
34
+ */
35
+ export function rebuildMissionCost(mission) {
36
+ const metrics = aggregateMissionMetrics(mission.runLinks);
37
+ return {
38
+ totalActualUsd: metrics.totalActualUsd,
39
+ verifiedOutcomeCount: metrics.verifiedOutcomeCount,
40
+ totalRunCount: metrics.totalRunCount
41
+ };
42
+ }
@@ -0,0 +1,2 @@
1
+ export { aggregateMissionMetrics, rebuildMissionCost } from "./aggregate.js";
2
+ export type { MissionMetrics } from "./aggregate.js";
@@ -0,0 +1 @@
1
+ export { aggregateMissionMetrics, rebuildMissionCost } from "./aggregate.js";
@@ -1,4 +1,5 @@
1
1
  import { type CompilerAdapterRequest, type PromptPacket } from "../compiler.js";
2
+ import { type ContextAdapter } from "../context-compiler.js";
2
3
  import type { RunStore } from "./store.js";
3
4
  export interface CompileResult {
4
5
  packet: PromptPacket;
@@ -10,9 +11,16 @@ export interface CompileResult {
10
11
  *
11
12
  * R3.7: Context compiler produces deterministic compiled-context.json.
12
13
  * R3.8: Any attempt prompt can be reconstructed from disk artifacts alone.
14
+ *
15
+ * A-CTX-0: After compiling, emit a shadow manifest via compileContextShadow
16
+ * and append a context.shadow_compiled ledger event. Shadow failure must not
17
+ * break the governed run — original packet is returned unchanged in all cases.
13
18
  */
14
19
  export declare function compileAndPersistContext(request: CompilerAdapterRequest, options: {
15
20
  attemptIndex: number;
16
21
  store?: RunStore;
17
22
  now?: string;
23
+ nowMs?: number;
24
+ contextShadowBudgetTokens?: number;
25
+ contextAdapter?: ContextAdapter;
18
26
  }): Promise<CompileResult>;
@@ -1,4 +1,7 @@
1
+ import { createHash } from "node:crypto";
1
2
  import { compilePromptPacket } from "../compiler.js";
3
+ import { compileContext, HEURISTIC_ADAPTER } from "../context-compiler.js";
4
+ import { compileContextShadow } from "../context-shadow.js";
2
5
  import { makeLedgerEvent } from "./ledger.js";
3
6
  /**
4
7
  * Compile a deterministic PromptPacket from the request state and, when a
@@ -7,6 +10,10 @@ import { makeLedgerEvent } from "./ledger.js";
7
10
  *
8
11
  * R3.7: Context compiler produces deterministic compiled-context.json.
9
12
  * R3.8: Any attempt prompt can be reconstructed from disk artifacts alone.
13
+ *
14
+ * A-CTX-0: After compiling, emit a shadow manifest via compileContextShadow
15
+ * and append a context.shadow_compiled ledger event. Shadow failure must not
16
+ * break the governed run — original packet is returned unchanged in all cases.
10
17
  */
11
18
  export async function compileAndPersistContext(request, options) {
12
19
  const packet = compilePromptPacket(request);
@@ -29,6 +36,155 @@ export async function compileAndPersistContext(request, options) {
29
36
  },
30
37
  timestamp: ts
31
38
  }));
39
+ // ── A-CTX-0: Shadow manifest emission ────────────────────────────────
40
+ // Use the serialized PromptPacket as the single shadow segment.
41
+ // This is a A-CTX-0 fallback — A-CTX-1 will map structured segments.
42
+ // Shadow failure MUST NOT break the governed run.
43
+ try {
44
+ const nowMs = options.nowMs ?? Date.now();
45
+ const shadow = compileContextShadow({
46
+ runId: request.loopId,
47
+ adapter: "martin-core",
48
+ nowMs,
49
+ shadowBudgetTokens: options.contextShadowBudgetTokens ?? 8_000,
50
+ modelWindowEvidence: "unknown",
51
+ segments: [
52
+ {
53
+ segmentId: "compiled-prompt",
54
+ kind: "compiled_prompt",
55
+ required: true,
56
+ text: JSON.stringify(packet)
57
+ }
58
+ ]
59
+ });
60
+ await options.store.appendLedger(request.loopId, makeLedgerEvent({
61
+ kind: "context.shadow_compiled",
62
+ runId: request.loopId,
63
+ attemptIndex: options.attemptIndex,
64
+ payload: shadow.receipt,
65
+ timestamp: ts
66
+ }));
67
+ }
68
+ catch (shadowErr) {
69
+ // Shadow telemetry failure is non-fatal. Emit a diagnostic ledger entry
70
+ // so the failure is visible without breaking the governed run.
71
+ const errorKind = shadowErr instanceof Error ? shadowErr.name : "UnknownError";
72
+ try {
73
+ await options.store.appendLedger(request.loopId, makeLedgerEvent({
74
+ kind: "context.shadow_compiled",
75
+ runId: request.loopId,
76
+ attemptIndex: options.attemptIndex,
77
+ payload: {
78
+ mode: "shadow",
79
+ error: "shadow_emit_failed",
80
+ errorKind
81
+ },
82
+ timestamp: ts
83
+ }));
84
+ }
85
+ catch {
86
+ // Last-resort: if the diagnostic ledger write also fails, swallow it
87
+ // so the governed run completes. No prompt content is stored here.
88
+ }
89
+ }
90
+ // ── End A-CTX-0 shadow emission ───────────────────────────────────────
91
+ // ── A-CTX-1: Deterministic governed context compilation ───────────────
92
+ // Shadow mode: compute manifest but return original packet unchanged.
93
+ // nowMs captured once here — never passed as Date.now() inside compileContext.
94
+ // Failure must not break the governed run.
95
+ try {
96
+ const nowMs = options.nowMs ?? Date.now();
97
+ const adapter = options.contextAdapter ?? HEURISTIC_ADAPTER;
98
+ // Build one candidate from the serialized packet.
99
+ // Only the hash goes into the manifest — never the raw text.
100
+ const packetText = JSON.stringify(packet);
101
+ const packetHash = createHash("sha256").update(packetText, "utf8").digest("hex");
102
+ const estimatedTokens = Math.max(1, Math.ceil(Buffer.byteLength(packetText, "utf8") / 4));
103
+ const result = compileContext({
104
+ taskId: request.attemptId,
105
+ runId: request.loopId,
106
+ nowMs,
107
+ candidates: [
108
+ {
109
+ id: "compiled-prompt",
110
+ kind: "task",
111
+ priority: "required",
112
+ trust: "authoritative",
113
+ sensitivity: "workspace",
114
+ sourceRef: `run://${request.loopId}/compiled-context`,
115
+ contentHash: packetHash,
116
+ estimatedTokens
117
+ }
118
+ ],
119
+ budget: {
120
+ modelWindowTokens: 200_000,
121
+ systemReserveTokens: 2_000,
122
+ outputReserveTokens: 4_000,
123
+ toolReserveTokens: 3_000,
124
+ overflowReserveTokens: 2_000,
125
+ maxWorkingSetTokens: options.contextShadowBudgetTokens ?? 8_000,
126
+ pinnedTokensMax: 1_500
127
+ },
128
+ policy: {
129
+ policyHash: "shadow-passthrough-v1",
130
+ deniedSensitivities: ["secret"],
131
+ deniedTrustLevels: [],
132
+ requiredOverBudgetAction: "explicit_escalation",
133
+ maxCompilerDurationMs: 250,
134
+ maxOverheadRatio: 0.15
135
+ },
136
+ adapter
137
+ });
138
+ if (result.ok) {
139
+ await options.store.appendLedger(request.loopId, makeLedgerEvent({
140
+ kind: "context.manifest_compiled",
141
+ runId: request.loopId,
142
+ attemptIndex: options.attemptIndex,
143
+ payload: result.ledgerEntry,
144
+ timestamp: ts
145
+ }));
146
+ }
147
+ else {
148
+ // Explicit compiler failure (required_over_budget or recount_exceeded_passes).
149
+ // Record as a diagnostic — not a crash, not silent.
150
+ await options.store.appendLedger(request.loopId, makeLedgerEvent({
151
+ kind: "context.manifest_compiled",
152
+ runId: request.loopId,
153
+ attemptIndex: options.attemptIndex,
154
+ payload: {
155
+ mode: "shadow",
156
+ error: "compiler_failed",
157
+ reason: result.reason
158
+ },
159
+ timestamp: ts
160
+ }));
161
+ }
162
+ }
163
+ catch (compilerErr) {
164
+ // Unexpected compiler failure is non-fatal. Emit a named diagnostic so
165
+ // the failure is visible in the ledger without breaking the governed run.
166
+ // Only error.name (e.g. "TypeError") is recorded — never error.message,
167
+ // which could contain prompt or segment text.
168
+ const errorKind = compilerErr instanceof Error ? compilerErr.name : "UnknownError";
169
+ try {
170
+ await options.store.appendLedger(request.loopId, makeLedgerEvent({
171
+ kind: "context.manifest_compiled",
172
+ runId: request.loopId,
173
+ attemptIndex: options.attemptIndex,
174
+ payload: {
175
+ mode: "shadow",
176
+ error: "compiler_threw",
177
+ errorKind
178
+ },
179
+ timestamp: ts
180
+ }));
181
+ }
182
+ catch {
183
+ // Last-resort: diagnostic write failed too. The run must not be affected.
184
+ // No prompt content is stored anywhere in this path.
185
+ }
186
+ }
187
+ // ── End A-CTX-1 compiler ──────────────────────────────────────────────
32
188
  }
33
189
  return { packet };
34
190
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Context Handoff Store — A-CTX-2 persistence layer.
3
+ *
4
+ * Produces, writes, and reads ContextHandoffReceipt files so that
5
+ * downstream processes can verify them without rerunning the upstream agent.
6
+ *
7
+ * Storage layout (under runsRoot/<runId>/):
8
+ * context-handoff.json — the receipt produced by this run for the next hop
9
+ *
10
+ * The producerReceiptHash is computed as SHA-256 of the receipt-integrity.json
11
+ * file written by writeLoopRecord. If that file is absent, upstreamIntegrity is
12
+ * set to "evidence_boundary" — the receipt is still written but will not pass
13
+ * verifyContextHandoff.
14
+ */
15
+ import type { ContextHandoffArtifact, ContextHandoffClaim, ContextHandoffReceipt } from "../../contracts/index.js";
16
+ /** Compute the SHA-256 hex digest of a file's raw bytes. */
17
+ export declare function computeFileHash(filePath: string): Promise<string>;
18
+ export declare function contextHandoffPath(runsRoot: string, runId: string): string;
19
+ /**
20
+ * Persist a ContextHandoffReceipt for this run so the next hop can load it.
21
+ * Overwrites any existing file — callers must not call this more than once per run.
22
+ */
23
+ export declare function writeContextHandoff(runsRoot: string, runId: string, receipt: ContextHandoffReceipt): Promise<void>;
24
+ /**
25
+ * Load a ContextHandoffReceipt from a previous run.
26
+ * Returns null when the file does not exist (not an error — the consumer
27
+ * must treat absence as a hard gate failure).
28
+ */
29
+ export declare function readContextHandoff(runsRoot: string, runId: string): Promise<ContextHandoffReceipt | null>;
30
+ export interface BuildHandoffReceiptInput {
31
+ runsRoot: string;
32
+ /** Run that is producing this handoff (the upstream/producer run). */
33
+ producerRunId: string;
34
+ /** Stable identifier for the chain. Shared across all hops. */
35
+ chainId: string;
36
+ /** Optional mission identifier shared across hops. */
37
+ missionId?: string;
38
+ /** Unique ID for this specific handoff crossing. Generated if absent. */
39
+ handoffId?: string;
40
+ /** Verified claims the producer is asserting. All must be state "verified". */
41
+ claims?: ContextHandoffClaim[];
42
+ /**
43
+ * File paths whose SHA-256 hashes should be captured as artifacts.
44
+ * Each file is hashed at call time — callers must call after writing artifacts.
45
+ */
46
+ artifactFiles?: Array<{
47
+ filePath: string;
48
+ label?: string;
49
+ required: boolean;
50
+ }>;
51
+ /** Optional pre-computed artifacts (bypasses file hashing). */
52
+ artifacts?: ContextHandoffArtifact[];
53
+ /** Assumptions that are not yet resolved at handoff time. */
54
+ unresolvedAssumptions?: string[];
55
+ /** Parent handoff IDs for multi-hop lineage. */
56
+ parentHandoffIds?: string[];
57
+ /** Clock override (defaults to new Date().toISOString()). */
58
+ now?: () => string;
59
+ }
60
+ /**
61
+ * Build a ContextHandoffReceipt from a completed run.
62
+ *
63
+ * - producerReceiptHash is SHA-256 of the receipt-integrity.json file
64
+ * written by writeLoopRecord. If that file is absent, upstreamIntegrity
65
+ * is "evidence_boundary".
66
+ * - Artifact hashes are computed from real files at call time.
67
+ */
68
+ export declare function buildContextHandoffReceipt(input: BuildHandoffReceiptInput): Promise<ContextHandoffReceipt>;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Context Handoff Store — A-CTX-2 persistence layer.
3
+ *
4
+ * Produces, writes, and reads ContextHandoffReceipt files so that
5
+ * downstream processes can verify them without rerunning the upstream agent.
6
+ *
7
+ * Storage layout (under runsRoot/<runId>/):
8
+ * context-handoff.json — the receipt produced by this run for the next hop
9
+ *
10
+ * The producerReceiptHash is computed as SHA-256 of the receipt-integrity.json
11
+ * file written by writeLoopRecord. If that file is absent, upstreamIntegrity is
12
+ * set to "evidence_boundary" — the receipt is still written but will not pass
13
+ * verifyContextHandoff.
14
+ */
15
+ import { createHash } from "node:crypto";
16
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
17
+ import { join } from "node:path";
18
+ import { HANDOFF_SCHEMA_VERSION } from "../../contracts/index.js";
19
+ import { resolveReceiptIntegrityPath } from "./integrity.js";
20
+ import { runDir } from "./store.js";
21
+ // ─── Hash helpers ──────────────────────────────────────────────────────────────
22
+ /** Compute the SHA-256 hex digest of a file's raw bytes. */
23
+ export async function computeFileHash(filePath) {
24
+ const buf = await readFile(filePath);
25
+ return createHash("sha256").update(buf).digest("hex");
26
+ }
27
+ /** Compute the SHA-256 hex digest of a UTF-8 string. */
28
+ function sha256String(value) {
29
+ return createHash("sha256").update(value, "utf8").digest("hex");
30
+ }
31
+ // ─── Paths ─────────────────────────────────────────────────────────────────────
32
+ export function contextHandoffPath(runsRoot, runId) {
33
+ return join(runDir(runsRoot, runId), "context-handoff.json");
34
+ }
35
+ // ─── Write ─────────────────────────────────────────────────────────────────────
36
+ /**
37
+ * Persist a ContextHandoffReceipt for this run so the next hop can load it.
38
+ * Overwrites any existing file — callers must not call this more than once per run.
39
+ */
40
+ export async function writeContextHandoff(runsRoot, runId, receipt) {
41
+ const dir = runDir(runsRoot, runId);
42
+ await mkdir(dir, { recursive: true });
43
+ await writeFile(contextHandoffPath(runsRoot, runId), JSON.stringify(receipt, null, 2), "utf8");
44
+ }
45
+ // ─── Read ──────────────────────────────────────────────────────────────────────
46
+ /**
47
+ * Load a ContextHandoffReceipt from a previous run.
48
+ * Returns null when the file does not exist (not an error — the consumer
49
+ * must treat absence as a hard gate failure).
50
+ */
51
+ export async function readContextHandoff(runsRoot, runId) {
52
+ const path = contextHandoffPath(runsRoot, runId);
53
+ const raw = await readFile(path, "utf8").catch(() => null);
54
+ if (raw === null)
55
+ return null;
56
+ return JSON.parse(raw);
57
+ }
58
+ /**
59
+ * Build a ContextHandoffReceipt from a completed run.
60
+ *
61
+ * - producerReceiptHash is SHA-256 of the receipt-integrity.json file
62
+ * written by writeLoopRecord. If that file is absent, upstreamIntegrity
63
+ * is "evidence_boundary".
64
+ * - Artifact hashes are computed from real files at call time.
65
+ */
66
+ export async function buildContextHandoffReceipt(input) {
67
+ const { runsRoot, producerRunId, chainId, missionId, claims = [], unresolvedAssumptions = [], parentHandoffIds, now = () => new Date().toISOString() } = input;
68
+ // Determine handoffId
69
+ const handoffId = input.handoffId ??
70
+ `hoff_${sha256String(`${chainId}:${producerRunId}:${now()}`).slice(0, 16)}`;
71
+ // Hash the receipt-integrity.json file to obtain producerReceiptHash
72
+ const integrityPath = resolveReceiptIntegrityPath(runsRoot, producerRunId);
73
+ let producerReceiptHash;
74
+ let upstreamIntegrity;
75
+ const integrityRaw = await readFile(integrityPath, "utf8").catch(() => null);
76
+ if (integrityRaw === null) {
77
+ // No integrity file — cannot establish upstream integrity
78
+ producerReceiptHash = "";
79
+ upstreamIntegrity = "evidence_boundary";
80
+ }
81
+ else {
82
+ producerReceiptHash = sha256String(integrityRaw);
83
+ upstreamIntegrity = "verified";
84
+ }
85
+ // Hash artifact files
86
+ const artifactFiles = input.artifactFiles ?? [];
87
+ const fileArtifacts = await Promise.all(artifactFiles.map(async (af) => ({
88
+ path: af.filePath,
89
+ sha256: await computeFileHash(af.filePath),
90
+ required: af.required,
91
+ label: af.label
92
+ })));
93
+ const artifacts = [
94
+ ...(input.artifacts ?? []),
95
+ ...fileArtifacts
96
+ ];
97
+ const receipt = {
98
+ schemaVersion: HANDOFF_SCHEMA_VERSION,
99
+ handoffId,
100
+ chainId,
101
+ ...(missionId !== undefined ? { missionId } : {}),
102
+ producerRunId,
103
+ producerReceiptHash,
104
+ ...(parentHandoffIds !== undefined ? { parentHandoffIds } : {}),
105
+ claims,
106
+ artifacts,
107
+ unresolvedAssumptions,
108
+ upstreamIntegrity,
109
+ createdAt: now()
110
+ };
111
+ return receipt;
112
+ }
@@ -8,3 +8,7 @@ export { readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile,
8
8
  export type { LoopAttemptRecord, LoopRunRecord } from "./runs-reader.js";
9
9
  export { compileAndPersistContext } from "./compiler.js";
10
10
  export type { CompileResult } from "./compiler.js";
11
+ export { buildContextHandoffReceipt, computeFileHash, contextHandoffPath, readContextHandoff, writeContextHandoff } from "./context-handoff-store.js";
12
+ export type { BuildHandoffReceiptInput } from "./context-handoff-store.js";
13
+ export { attachRun, changeMissionStatus, createMission, missionDir, readMission, readMissionLedger, verifyMissionLedger } from "./mission-store.js";
14
+ export type { AttachRunOptions, ChangeMissionStatusOptions, LedgerIntegrityResult } from "./mission-store.js";
@@ -3,3 +3,5 @@ export { resolveReceiptIntegrityPath, verifyReceiptIntegrityFromFiles, writeRece
3
3
  export { artifactDir, createFileRunStore, resolveRunsRoot, runDir } from "./store.js";
4
4
  export { readAllLoopRecords, readLatestLoopRecord, readLatestLoopRecordFromFile, readLoopRecordsFromFile } from "./runs-reader.js";
5
5
  export { compileAndPersistContext } from "./compiler.js";
6
+ export { buildContextHandoffReceipt, computeFileHash, contextHandoffPath, readContextHandoff, writeContextHandoff } from "./context-handoff-store.js";
7
+ export { attachRun, changeMissionStatus, createMission, missionDir, readMission, readMissionLedger, verifyMissionLedger } from "./mission-store.js";
@@ -3,7 +3,7 @@
3
3
  * These are WRITE-side events that go into ledger.jsonl — separate from the
4
4
  * read-model LoopEventType used by the control plane.
5
5
  */
6
- export type LedgerEventKind = "contract.created" | "attempt.admitted" | "attempt.rejected" | "prompt.compiled" | "patch.generated" | "verification.completed" | "grounding.violations_found" | "safety.violations_found" | "budget.settled" | "attempt.kept" | "attempt.discarded" | "run.exited";
6
+ export type LedgerEventKind = "contract.created" | "attempt.admitted" | "attempt.rejected" | "prompt.compiled" | "patch.generated" | "verification.completed" | "grounding.violations_found" | "safety.violations_found" | "budget.settled" | "attempt.kept" | "attempt.discarded" | "run.exited" | "run.terminated" | "run.diagnostic" | "context.shadow_compiled" | "context.manifest_compiled" | "context.handoff.received" | "context.handoff.verified" | "context.handoff.blocked" | "context.object.excluded";
7
7
  export interface LedgerEvent {
8
8
  kind: LedgerEventKind;
9
9
  runId: string;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Mission Store — C2 durable persistence.
3
+ *
4
+ * Layout under <runsRoot>/missions/<missionId>/:
5
+ * mission.json — rebuildable snapshot/cache (NOT the authority)
6
+ * ledger.jsonl — append-only, SHA-256 hash-chained event log (authority)
7
+ * ledger-chain.json — chain head hash for tamper detection
8
+ * .lock — cross-process exclusive lock (created with O_EXCL)
9
+ *
10
+ * Rules:
11
+ * - Ledger is always written before mission.json is updated.
12
+ * - mission.json is written atomically (temp file → rename).
13
+ * - Cross-process lock is held during every write; stale locks (>8s) are removed.
14
+ * - CAS revision must match before any write is accepted.
15
+ * - Corrupt or unverifiable ledger data fails closed.
16
+ * - Workspace isolation: all paths are under caller-supplied runsRoot.
17
+ */
18
+ import type { MissionDecision, MissionEvent, MissionRecord, MissionRunRole, MissionStatus } from "../../contracts/index.js";
19
+ export declare function missionDir(runsRoot: string, missionId: string): string;
20
+ /**
21
+ * Read the current mission snapshot.
22
+ * Returns null when no mission exists at this path.
23
+ * Fails closed when the snapshot schema version is not recognised.
24
+ */
25
+ export declare function readMission(runsRoot: string, missionId: string): Promise<MissionRecord | null>;
26
+ export declare function readMissionLedger(runsRoot: string, missionId: string): Promise<MissionEvent[]>;
27
+ export interface LedgerIntegrityResult {
28
+ ok: boolean;
29
+ reason?: string;
30
+ entryCount: number;
31
+ }
32
+ /**
33
+ * Verify the stored chain head matches a full replay of the ledger.
34
+ * Returns ok=false when the ledger has been tampered with or entries are missing.
35
+ */
36
+ export declare function verifyMissionLedger(runsRoot: string, missionId: string): Promise<LedgerIntegrityResult>;
37
+ export declare function createMission(runsRoot: string, mission: MissionRecord): Promise<void>;
38
+ export interface AttachRunOptions {
39
+ loopId: string;
40
+ role: MissionRunRole;
41
+ verifiedOutcome?: boolean;
42
+ actualUsd?: number;
43
+ now?: () => string;
44
+ /** Expected revision for CAS enforcement. */
45
+ expectedRevision: number;
46
+ }
47
+ export declare function attachRun(runsRoot: string, missionId: string, options: AttachRunOptions): Promise<MissionRecord>;
48
+ export interface ChangeMissionStatusOptions {
49
+ toStatus: MissionStatus;
50
+ expectedRevision: number;
51
+ decidedBy?: string;
52
+ decision?: MissionDecision;
53
+ note?: string;
54
+ now?: () => string;
55
+ }
56
+ export declare function changeMissionStatus(runsRoot: string, missionId: string, options: ChangeMissionStatusOptions): Promise<MissionRecord>;