@martinloop/mcp 0.3.8 → 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.
- package/README.md +90 -106
- package/dist/discovery-metadata.d.ts +1 -1
- package/dist/discovery-metadata.js +1 -1
- package/dist/resources.d.ts +1 -2
- package/dist/resources.js +190 -238
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +16 -11
- package/dist/server.d.ts +23 -0
- package/dist/server.js +152 -7
- package/dist/tools/run-dossier.d.ts +3 -1
- package/dist/tools/run-dossier.js +40 -2
- package/dist/tools/run-loop.d.ts +1 -4
- package/dist/tools/run-loop.js +6 -15
- package/dist/tools/tool-support.d.ts +2 -1
- package/dist/tools/tool-support.js +1 -12
- package/dist/vendor/adapters/claude-cli.js +38 -35
- package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
- package/dist/vendor/adapters/cli-bridge.js +54 -13
- package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
- package/dist/vendor/adapters/codex-launcher.js +118 -19
- package/dist/vendor/adapters/index.d.ts +1 -3
- package/dist/vendor/adapters/index.js +1 -3
- package/dist/vendor/adapters/openai-compatible.js +57 -18
- package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
- package/dist/vendor/contracts/context-handoff.d.ts +100 -0
- package/dist/vendor/contracts/context-handoff.js +11 -0
- package/dist/vendor/contracts/context-manifest.d.ts +195 -0
- package/dist/vendor/contracts/context-manifest.js +17 -0
- package/dist/vendor/contracts/context-shadow.d.ts +75 -0
- package/dist/vendor/contracts/context-shadow.js +8 -0
- package/dist/vendor/contracts/exits.d.ts +126 -0
- package/dist/vendor/contracts/exits.js +32 -0
- package/dist/vendor/contracts/index.d.ts +39 -2
- package/dist/vendor/contracts/index.js +22 -0
- package/dist/vendor/contracts/mission.d.ts +102 -0
- package/dist/vendor/contracts/mission.js +64 -0
- package/dist/vendor/contracts/operator.d.ts +3 -1
- package/dist/vendor/contracts/operator.js +2 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +100 -0
- package/dist/vendor/contracts/verified-handoff.js +35 -0
- package/dist/vendor/core/context-chain-gate.d.ts +89 -0
- package/dist/vendor/core/context-chain-gate.js +173 -0
- package/dist/vendor/core/context-compiler.d.ts +53 -0
- package/dist/vendor/core/context-compiler.js +261 -0
- package/dist/vendor/core/context-handoff.d.ts +42 -0
- package/dist/vendor/core/context-handoff.js +167 -0
- package/dist/vendor/core/context-shadow.d.ts +39 -0
- package/dist/vendor/core/context-shadow.js +200 -0
- package/dist/vendor/core/exit-signal.d.ts +88 -0
- package/dist/vendor/core/exit-signal.js +339 -0
- package/dist/vendor/core/exits.d.ts +41 -0
- package/dist/vendor/core/exits.js +258 -0
- package/dist/vendor/core/index.d.ts +42 -1
- package/dist/vendor/core/index.js +1086 -662
- package/dist/vendor/core/mission/aggregate.d.ts +27 -0
- package/dist/vendor/core/mission/aggregate.js +42 -0
- package/dist/vendor/core/mission/index.d.ts +2 -0
- package/dist/vendor/core/mission/index.js +1 -0
- package/dist/vendor/core/persistence/compiler.d.ts +8 -0
- package/dist/vendor/core/persistence/compiler.js +156 -0
- package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
- package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
- package/dist/vendor/core/persistence/index.d.ts +4 -0
- package/dist/vendor/core/persistence/index.js +2 -0
- package/dist/vendor/core/persistence/ledger.d.ts +1 -1
- package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
- package/dist/vendor/core/persistence/mission-store.js +281 -0
- package/dist/vendor/core/policy.d.ts +2 -0
- package/dist/vendor/core/policy.js +5 -2
- package/dist/vendor/core/rollback.js +15 -1
- package/dist/vendor/core/savings.d.ts +13 -0
- package/dist/vendor/core/savings.js +22 -0
- package/dist/vendor/core/termination-store.d.ts +15 -0
- package/dist/vendor/core/termination-store.js +44 -0
- package/dist/vendor/core/verified-handoff.d.ts +67 -0
- package/dist/vendor/core/verified-handoff.js +210 -0
- package/package.json +76 -83
- package/server.json +21 -21
- package/LICENSE +0 -216
- package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +0 -146
- package/dist/vendor/adapters/adapters/src/claude-cli.js +0 -1100
- package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +0 -103
- package/dist/vendor/adapters/adapters/src/cli-bridge.js +0 -569
- package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +0 -78
- package/dist/vendor/adapters/adapters/src/codex-launcher.js +0 -663
- package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +0 -10
- package/dist/vendor/adapters/adapters/src/direct-provider.js +0 -40
- package/dist/vendor/adapters/adapters/src/index.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/index.js +0 -8
- package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +0 -62
- package/dist/vendor/adapters/adapters/src/openai-compatible.js +0 -306
- package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +0 -17
- package/dist/vendor/adapters/adapters/src/runtime-support.js +0 -59
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +0 -12
- package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +0 -9
- package/dist/vendor/adapters/adapters/src/verifier-only.js +0 -63
- package/dist/vendor/adapters/contracts/src/context-graph.d.ts +0 -48
- package/dist/vendor/adapters/contracts/src/context-graph.js +0 -26
- package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +0 -78
- package/dist/vendor/adapters/contracts/src/execution-policy.js +0 -43
- package/dist/vendor/adapters/contracts/src/governance.d.ts +0 -24
- package/dist/vendor/adapters/contracts/src/governance.js +0 -11
- package/dist/vendor/adapters/contracts/src/identity.d.ts +0 -29
- package/dist/vendor/adapters/contracts/src/identity.js +0 -30
- package/dist/vendor/adapters/contracts/src/index.d.ts +0 -412
- package/dist/vendor/adapters/contracts/src/index.js +0 -225
- package/dist/vendor/adapters/contracts/src/operator.d.ts +0 -19
- package/dist/vendor/adapters/contracts/src/operator.js +0 -11
- package/dist/vendor/adapters/contracts/src/trajectory.d.ts +0 -26
- package/dist/vendor/adapters/contracts/src/trajectory.js +0 -27
- package/dist/vendor/adapters/core/src/compiler.d.ts +0 -52
- package/dist/vendor/adapters/core/src/compiler.js +0 -52
- package/dist/vendor/adapters/core/src/context-graph.d.ts +0 -3
- package/dist/vendor/adapters/core/src/context-graph.js +0 -371
- package/dist/vendor/adapters/core/src/context-integrity.d.ts +0 -26
- package/dist/vendor/adapters/core/src/context-integrity.js +0 -80
- package/dist/vendor/adapters/core/src/grounding.d.ts +0 -38
- package/dist/vendor/adapters/core/src/grounding.js +0 -273
- package/dist/vendor/adapters/core/src/identity.d.ts +0 -20
- package/dist/vendor/adapters/core/src/identity.js +0 -86
- package/dist/vendor/adapters/core/src/index.d.ts +0 -186
- package/dist/vendor/adapters/core/src/index.js +0 -1495
- package/dist/vendor/adapters/core/src/leash.d.ts +0 -48
- package/dist/vendor/adapters/core/src/leash.js +0 -497
- package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +0 -18
- package/dist/vendor/adapters/core/src/persistence/compiler.js +0 -34
- package/dist/vendor/adapters/core/src/persistence/index.d.ts +0 -10
- package/dist/vendor/adapters/core/src/persistence/index.js +0 -5
- package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +0 -39
- package/dist/vendor/adapters/core/src/persistence/integrity.js +0 -252
- package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +0 -23
- package/dist/vendor/adapters/core/src/persistence/ledger.js +0 -9
- package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +0 -63
- package/dist/vendor/adapters/core/src/persistence/memory-store.js +0 -115
- package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +0 -66
- package/dist/vendor/adapters/core/src/persistence/runs-reader.js +0 -140
- package/dist/vendor/adapters/core/src/persistence/store.d.ts +0 -89
- package/dist/vendor/adapters/core/src/persistence/store.js +0 -125
- package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +0 -85
- package/dist/vendor/adapters/core/src/persistence/trace-store.js +0 -125
- package/dist/vendor/adapters/core/src/policy-compiler.d.ts +0 -2
- package/dist/vendor/adapters/core/src/policy-compiler.js +0 -195
- package/dist/vendor/adapters/core/src/policy.d.ts +0 -156
- package/dist/vendor/adapters/core/src/policy.js +0 -704
- package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +0 -53
- package/dist/vendor/adapters/core/src/repo-analyzer.js +0 -263
- package/dist/vendor/adapters/core/src/rollback.d.ts +0 -15
- package/dist/vendor/adapters/core/src/rollback.js +0 -253
- package/dist/vendor/adapters/core/src/routing.d.ts +0 -82
- package/dist/vendor/adapters/core/src/routing.js +0 -336
- package/dist/vendor/adapters/core/src/test-integrity.d.ts +0 -64
- package/dist/vendor/adapters/core/src/test-integrity.js +0 -197
- package/dist/vendor/adapters/core/src/trajectory.d.ts +0 -18
- package/dist/vendor/adapters/core/src/trajectory.js +0 -295
- package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/verifier-only.d.ts +0 -9
- package/dist/vendor/adapters/verifier-only.js +0 -63
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compiler — A-CTX-1
|
|
3
|
+
*
|
|
4
|
+
* Deterministic working-set selector. Given a set of ContextObject candidates,
|
|
5
|
+
* a budget, a policy, and an explicit nowMs, produces a ContextManifest whose
|
|
6
|
+
* hash is identical for identical inputs on any platform.
|
|
7
|
+
*
|
|
8
|
+
* Shadow mode: the manifest is computed and emitted but the original provider
|
|
9
|
+
* packet is returned byte-for-byte unchanged.
|
|
10
|
+
*
|
|
11
|
+
* Standing invariants:
|
|
12
|
+
* - nowMs is ALWAYS a captured input — never Date.now() internally
|
|
13
|
+
* - No raw source text in manifest or ledger receipt — hashes and counts only
|
|
14
|
+
* - No confidence float, no outcome_delta — both permanently banned
|
|
15
|
+
* - Fault budget accumulates by taskId, not runId
|
|
16
|
+
* - required-over-budget is an explicit failure, never a silent drop
|
|
17
|
+
* - MAX_RENDER_PASSES = 5 — hard constant
|
|
18
|
+
*/
|
|
19
|
+
import type { ContextBudget, ContextLedgerEntry, ContextManifest, ContextObject, ContextPolicy } from "../contracts/index.js";
|
|
20
|
+
export declare const MAX_RENDER_PASSES = 5;
|
|
21
|
+
/**
|
|
22
|
+
* Minimal adapter interface for token recounting.
|
|
23
|
+
* Implementations may call the provider SDK's token counter.
|
|
24
|
+
* Must not make network calls to the provider inference endpoint.
|
|
25
|
+
*/
|
|
26
|
+
export interface ContextAdapter {
|
|
27
|
+
readonly version: string;
|
|
28
|
+
/** Recount the actual rendered tokens for a serialized text segment. */
|
|
29
|
+
recountTokens(text: string): number;
|
|
30
|
+
}
|
|
31
|
+
/** Fallback adapter: char÷4 heuristic, no external calls. */
|
|
32
|
+
export declare const HEURISTIC_ADAPTER: ContextAdapter;
|
|
33
|
+
export interface CompileContextInput {
|
|
34
|
+
taskId: string;
|
|
35
|
+
runId: string;
|
|
36
|
+
/** Must be a captured value — never call Date.now() inside the compiler. */
|
|
37
|
+
nowMs: number;
|
|
38
|
+
candidates: readonly ContextObject[];
|
|
39
|
+
budget: ContextBudget;
|
|
40
|
+
policy: ContextPolicy;
|
|
41
|
+
adapter: ContextAdapter;
|
|
42
|
+
}
|
|
43
|
+
export type CompileContextOutput = {
|
|
44
|
+
ok: true;
|
|
45
|
+
manifest: ContextManifest;
|
|
46
|
+
ledgerEntry: ContextLedgerEntry;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
reason: "required_over_budget" | "recount_exceeded_passes";
|
|
50
|
+
manifest: null;
|
|
51
|
+
ledgerEntry: null;
|
|
52
|
+
};
|
|
53
|
+
export declare function compileContext(input: CompileContextInput): CompileContextOutput;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Compiler — A-CTX-1
|
|
3
|
+
*
|
|
4
|
+
* Deterministic working-set selector. Given a set of ContextObject candidates,
|
|
5
|
+
* a budget, a policy, and an explicit nowMs, produces a ContextManifest whose
|
|
6
|
+
* hash is identical for identical inputs on any platform.
|
|
7
|
+
*
|
|
8
|
+
* Shadow mode: the manifest is computed and emitted but the original provider
|
|
9
|
+
* packet is returned byte-for-byte unchanged.
|
|
10
|
+
*
|
|
11
|
+
* Standing invariants:
|
|
12
|
+
* - nowMs is ALWAYS a captured input — never Date.now() internally
|
|
13
|
+
* - No raw source text in manifest or ledger receipt — hashes and counts only
|
|
14
|
+
* - No confidence float, no outcome_delta — both permanently banned
|
|
15
|
+
* - Fault budget accumulates by taskId, not runId
|
|
16
|
+
* - required-over-budget is an explicit failure, never a silent drop
|
|
17
|
+
* - MAX_RENDER_PASSES = 5 — hard constant
|
|
18
|
+
*/
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import { CONTEXT_LEDGER_VERSION, CONTEXT_MANIFEST_VERSION } from "../contracts/index.js";
|
|
21
|
+
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
22
|
+
export const MAX_RENDER_PASSES = 5;
|
|
23
|
+
/** Fallback adapter: char÷4 heuristic, no external calls. */
|
|
24
|
+
export const HEURISTIC_ADAPTER = {
|
|
25
|
+
version: "heuristic@1",
|
|
26
|
+
recountTokens: (text) => text.length === 0 ? 0 : Math.max(1, Math.ceil(Buffer.byteLength(text, "utf8") / 4))
|
|
27
|
+
};
|
|
28
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
29
|
+
function sha256(value) {
|
|
30
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
31
|
+
}
|
|
32
|
+
function canonicalize(value) {
|
|
33
|
+
if (Array.isArray(value))
|
|
34
|
+
return value.map(canonicalize);
|
|
35
|
+
if (value !== null && typeof value === "object") {
|
|
36
|
+
return Object.fromEntries(Object.entries(value)
|
|
37
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
38
|
+
.map(([k, v]) => [k, canonicalize(v)]));
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function canonicalJson(value) {
|
|
43
|
+
return JSON.stringify(canonicalize(value));
|
|
44
|
+
}
|
|
45
|
+
/** Priority order for selection: required → pinned → high → normal → low */
|
|
46
|
+
const PRIORITY_RANK = {
|
|
47
|
+
required: 0,
|
|
48
|
+
high: 2,
|
|
49
|
+
normal: 3,
|
|
50
|
+
low: 4
|
|
51
|
+
};
|
|
52
|
+
/** Pinned objects slot between required and high. */
|
|
53
|
+
function selectionRank(obj) {
|
|
54
|
+
if (obj.priority === "required")
|
|
55
|
+
return 0;
|
|
56
|
+
if (obj.pinnedBy !== undefined)
|
|
57
|
+
return 1;
|
|
58
|
+
return PRIORITY_RANK[obj.priority];
|
|
59
|
+
}
|
|
60
|
+
// ─── Compiler ────────────────────────────────────────────────────────────────
|
|
61
|
+
export function compileContext(input) {
|
|
62
|
+
const { taskId, runId, nowMs, candidates, budget, policy, adapter } = input;
|
|
63
|
+
// ── Eligibility filter — policy-denied objects become governed exclusion records ──
|
|
64
|
+
//
|
|
65
|
+
// A-CTX-2 fix: denied objects must NOT silently disappear. They enter decisions[]
|
|
66
|
+
// as "excluded" with a reason code so the exclusion is visible as governed evidence.
|
|
67
|
+
// executableContext = admittedObjects only; ledgerDecisions = admitted + excluded.
|
|
68
|
+
const policyExcluded = [];
|
|
69
|
+
const eligibleList = [];
|
|
70
|
+
for (const obj of candidates) {
|
|
71
|
+
if (policy.deniedSensitivities.includes(obj.sensitivity)) {
|
|
72
|
+
policyExcluded.push({
|
|
73
|
+
objectId: obj.id,
|
|
74
|
+
decision: "excluded",
|
|
75
|
+
reason: `sensitivity_denied:${obj.sensitivity}`
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (policy.deniedTrustLevels.includes(obj.trust)) {
|
|
79
|
+
policyExcluded.push({
|
|
80
|
+
objectId: obj.id,
|
|
81
|
+
decision: "excluded",
|
|
82
|
+
reason: `trust_denied:${obj.trust}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
eligibleList.push(obj);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const eligible = eligibleList;
|
|
90
|
+
// ── Compute compilerInputHash ────────────────────────────────────────────
|
|
91
|
+
const inputFingerprint = {
|
|
92
|
+
taskId,
|
|
93
|
+
runId,
|
|
94
|
+
nowMs,
|
|
95
|
+
adapterVersion: adapter.version,
|
|
96
|
+
policyHash: policy.policyHash,
|
|
97
|
+
budget: {
|
|
98
|
+
modelWindowTokens: budget.modelWindowTokens,
|
|
99
|
+
maxWorkingSetTokens: budget.maxWorkingSetTokens,
|
|
100
|
+
pinnedTokensMax: budget.pinnedTokensMax
|
|
101
|
+
},
|
|
102
|
+
candidateIds: eligible.map((o) => o.id).sort()
|
|
103
|
+
};
|
|
104
|
+
// ── Pinned token cap ─────────────────────────────────────────────────────
|
|
105
|
+
const pinnedTokensTotal = eligible
|
|
106
|
+
.filter((o) => o.pinnedBy !== undefined)
|
|
107
|
+
.reduce((s, o) => s + o.estimatedTokens, 0);
|
|
108
|
+
if (pinnedTokensTotal > budget.pinnedTokensMax) {
|
|
109
|
+
// Fail-closed: reject the entire batch, not individual pins
|
|
110
|
+
return { ok: false, reason: "required_over_budget", manifest: null, ledgerEntry: null };
|
|
111
|
+
}
|
|
112
|
+
// ── Sort by selection rank, then objectId as stable tie-breaker ──────────
|
|
113
|
+
const sorted = [...eligible].sort((a, b) => {
|
|
114
|
+
const rankDiff = selectionRank(a) - selectionRank(b);
|
|
115
|
+
if (rankDiff !== 0)
|
|
116
|
+
return rankDiff;
|
|
117
|
+
// Stable tie-breaker: objectId lexicographic
|
|
118
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
119
|
+
});
|
|
120
|
+
// ── Selection pass ───────────────────────────────────────────────────────
|
|
121
|
+
let remaining = budget.maxWorkingSetTokens;
|
|
122
|
+
const decisions = [];
|
|
123
|
+
let requiredOverBudget = false;
|
|
124
|
+
// Required objects total token check first
|
|
125
|
+
const requiredTokens = sorted
|
|
126
|
+
.filter((o) => o.priority === "required")
|
|
127
|
+
.reduce((s, o) => s + o.estimatedTokens, 0);
|
|
128
|
+
if (requiredTokens > remaining) {
|
|
129
|
+
requiredOverBudget = true;
|
|
130
|
+
if (policy.requiredOverBudgetAction === "fail_closed") {
|
|
131
|
+
return { ok: false, reason: "required_over_budget", manifest: null, ledgerEntry: null };
|
|
132
|
+
}
|
|
133
|
+
// explicit_escalation: record and continue — all required marked excluded
|
|
134
|
+
}
|
|
135
|
+
for (const obj of sorted) {
|
|
136
|
+
const isRequired = obj.priority === "required";
|
|
137
|
+
if (requiredOverBudget && isRequired) {
|
|
138
|
+
decisions.push({
|
|
139
|
+
objectId: obj.id,
|
|
140
|
+
decision: "excluded",
|
|
141
|
+
reason: "required_over_budget"
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (obj.estimatedTokens <= remaining) {
|
|
146
|
+
decisions.push({
|
|
147
|
+
objectId: obj.id,
|
|
148
|
+
decision: "included",
|
|
149
|
+
reason: isRequired ? "required" : obj.pinnedBy !== undefined ? "pinned" : "within_budget"
|
|
150
|
+
});
|
|
151
|
+
remaining -= obj.estimatedTokens;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Attempt truncation for required/pinned, exclude otherwise
|
|
155
|
+
if (isRequired || obj.pinnedBy !== undefined) {
|
|
156
|
+
decisions.push({
|
|
157
|
+
objectId: obj.id,
|
|
158
|
+
decision: "included_truncated",
|
|
159
|
+
truncatedFromTokens: obj.estimatedTokens,
|
|
160
|
+
reason: isRequired ? "required_truncated" : "pinned_truncated"
|
|
161
|
+
});
|
|
162
|
+
// Truncated objects consume whatever is left
|
|
163
|
+
remaining = 0;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
decisions.push({
|
|
167
|
+
objectId: obj.id,
|
|
168
|
+
decision: "excluded",
|
|
169
|
+
reason: "budget_exhausted"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ── Adapter recount (up to MAX_RENDER_PASSES) ────────────────────────────
|
|
175
|
+
const includedDecisions = decisions.filter((d) => d.decision === "included" || d.decision === "included_truncated");
|
|
176
|
+
let totalRenderedTokens = 0;
|
|
177
|
+
let renderPasses = 0;
|
|
178
|
+
let recountConverged = false;
|
|
179
|
+
const includedObjects = new Map(eligible.map((o) => [o.id, o]));
|
|
180
|
+
for (let pass = 0; pass < MAX_RENDER_PASSES; pass++) {
|
|
181
|
+
renderPasses = pass + 1;
|
|
182
|
+
let passTotal = 0;
|
|
183
|
+
for (const d of includedDecisions) {
|
|
184
|
+
const obj = includedObjects.get(d.objectId);
|
|
185
|
+
if (!obj)
|
|
186
|
+
continue;
|
|
187
|
+
// In shadow mode we use the contentHash as a proxy text for recounting
|
|
188
|
+
const recounted = adapter.recountTokens(obj.contentHash);
|
|
189
|
+
d.renderedTokens = recounted;
|
|
190
|
+
passTotal += recounted;
|
|
191
|
+
}
|
|
192
|
+
if (passTotal === totalRenderedTokens && pass > 0) {
|
|
193
|
+
recountConverged = true;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
totalRenderedTokens = passTotal;
|
|
197
|
+
if (pass === 0)
|
|
198
|
+
recountConverged = false;
|
|
199
|
+
}
|
|
200
|
+
if (!recountConverged && renderPasses >= MAX_RENDER_PASSES) {
|
|
201
|
+
return { ok: false, reason: "recount_exceeded_passes", manifest: null, ledgerEntry: null };
|
|
202
|
+
}
|
|
203
|
+
// ── Merge policy exclusions — excluded objects precede selection decisions ──
|
|
204
|
+
//
|
|
205
|
+
// A-CTX-2: executableContext = admittedObjects (decisions[]);
|
|
206
|
+
// ledgerDecisions = [...policyExcluded, ...decisions]
|
|
207
|
+
// Policy-excluded objects never enter the admitted working set, but their
|
|
208
|
+
// exclusion is visible as governed evidence in the manifest and ledger.
|
|
209
|
+
const allDecisions = [...policyExcluded, ...decisions];
|
|
210
|
+
// ── Manifest hash ────────────────────────────────────────────────────────
|
|
211
|
+
const manifestHashInput = canonicalJson({
|
|
212
|
+
adapterVersion: adapter.version,
|
|
213
|
+
budget: inputFingerprint.budget,
|
|
214
|
+
decisions: allDecisions.map((d) => ({
|
|
215
|
+
decision: d.decision,
|
|
216
|
+
objectId: d.objectId,
|
|
217
|
+
reason: d.reason,
|
|
218
|
+
...(d.truncatedFromTokens !== undefined
|
|
219
|
+
? { truncatedFromTokens: d.truncatedFromTokens }
|
|
220
|
+
: {})
|
|
221
|
+
})),
|
|
222
|
+
nowMs,
|
|
223
|
+
policyHash: policy.policyHash,
|
|
224
|
+
runId,
|
|
225
|
+
taskId,
|
|
226
|
+
totalRenderedTokens
|
|
227
|
+
});
|
|
228
|
+
const manifestHash = sha256(manifestHashInput);
|
|
229
|
+
const compilationDurationMs = 0; // In shadow mode: nowMs is frozen; duration is metadata-only
|
|
230
|
+
const manifest = {
|
|
231
|
+
schemaVersion: CONTEXT_MANIFEST_VERSION,
|
|
232
|
+
manifestHash,
|
|
233
|
+
taskId,
|
|
234
|
+
runId,
|
|
235
|
+
nowMs,
|
|
236
|
+
adapterVersion: adapter.version,
|
|
237
|
+
policyHash: policy.policyHash,
|
|
238
|
+
budget,
|
|
239
|
+
decisions: allDecisions,
|
|
240
|
+
totalRenderedTokens,
|
|
241
|
+
renderPasses,
|
|
242
|
+
compilationDurationMs
|
|
243
|
+
};
|
|
244
|
+
const totalIncluded = allDecisions.filter((d) => d.decision === "included" || d.decision === "included_truncated").length;
|
|
245
|
+
const totalExcluded = allDecisions.filter((d) => d.decision === "excluded").length;
|
|
246
|
+
const totalFaultLoaded = allDecisions.filter((d) => d.decision === "fault_loaded").length;
|
|
247
|
+
const ledgerEntry = {
|
|
248
|
+
schemaVersion: CONTEXT_LEDGER_VERSION,
|
|
249
|
+
entryType: "context",
|
|
250
|
+
taskId,
|
|
251
|
+
runId,
|
|
252
|
+
manifestHash,
|
|
253
|
+
totalIncluded,
|
|
254
|
+
totalExcluded,
|
|
255
|
+
totalFaultLoaded,
|
|
256
|
+
overflowCount: requiredOverBudget ? 1 : 0,
|
|
257
|
+
faultBudgetUsed: 0,
|
|
258
|
+
adapterVersion: adapter.version
|
|
259
|
+
};
|
|
260
|
+
return { ok: true, manifest, ledgerEntry };
|
|
261
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Handoff Verifier — A-CTX-2
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for verifying upstream-to-downstream context handoffs and
|
|
5
|
+
* deciding whether to circuit-break before the downstream agent executes.
|
|
6
|
+
*
|
|
7
|
+
* Standing rules:
|
|
8
|
+
* - These functions are pure and side-effect-free.
|
|
9
|
+
* - verifyContextHandoff fails closed — any ambiguity → not ok.
|
|
10
|
+
* - decideContextCircuitBreak is the sole gate before adapter execution.
|
|
11
|
+
* - The downstream adapter MUST NOT be called when shouldStop is true.
|
|
12
|
+
*/
|
|
13
|
+
import type { ContextCircuitBreakResult, ContextHandoffReceipt, ContextHandoffVerification } from "../contracts/index.js";
|
|
14
|
+
export interface VerifyContextHandoffInput {
|
|
15
|
+
handoff: ContextHandoffReceipt;
|
|
16
|
+
/** true when the producer receipt file hash has been independently confirmed */
|
|
17
|
+
producerReceiptVerified: boolean;
|
|
18
|
+
/** Map of sha256 → true for every artifact hash available to the verifier */
|
|
19
|
+
availableArtifacts: ReadonlyMap<string, true>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Deterministically verifies a context handoff.
|
|
23
|
+
*
|
|
24
|
+
* Returns ok=false when ANY of the following is true:
|
|
25
|
+
* - schemaVersion is not in SUPPORTED_SCHEMAS
|
|
26
|
+
* - producerReceiptVerified is false
|
|
27
|
+
* - upstreamIntegrity is not "verified"
|
|
28
|
+
* - a required artifact is absent from availableArtifacts
|
|
29
|
+
* - an artifact sha256 does not appear in availableArtifacts (hash changed)
|
|
30
|
+
* - any required claim has state "unverified" or "rejected"
|
|
31
|
+
* - any claim has state "unknown"
|
|
32
|
+
* - unresolvedAssumptions is non-empty
|
|
33
|
+
* - handoffId, chainId, producerRunId, or producerReceiptHash is blank
|
|
34
|
+
*/
|
|
35
|
+
export declare function verifyContextHandoff(input: VerifyContextHandoffInput): ContextHandoffVerification;
|
|
36
|
+
/**
|
|
37
|
+
* Converts a ContextHandoffVerification into a gate decision.
|
|
38
|
+
*
|
|
39
|
+
* Healthy: { shouldStop: false, silent: true }
|
|
40
|
+
* Broken: { shouldStop: true, silent: false, reasonCode, message, nextAction }
|
|
41
|
+
*/
|
|
42
|
+
export declare function decideContextCircuitBreak(verification: ContextHandoffVerification): ContextCircuitBreakResult;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Handoff Verifier — A-CTX-2
|
|
3
|
+
*
|
|
4
|
+
* Pure functions for verifying upstream-to-downstream context handoffs and
|
|
5
|
+
* deciding whether to circuit-break before the downstream agent executes.
|
|
6
|
+
*
|
|
7
|
+
* Standing rules:
|
|
8
|
+
* - These functions are pure and side-effect-free.
|
|
9
|
+
* - verifyContextHandoff fails closed — any ambiguity → not ok.
|
|
10
|
+
* - decideContextCircuitBreak is the sole gate before adapter execution.
|
|
11
|
+
* - The downstream adapter MUST NOT be called when shouldStop is true.
|
|
12
|
+
*/
|
|
13
|
+
import { HANDOFF_SCHEMA_VERSION } from "../contracts/index.js";
|
|
14
|
+
// ─── Supported schema versions ────────────────────────────────────────────────
|
|
15
|
+
const SUPPORTED_SCHEMAS = new Set([HANDOFF_SCHEMA_VERSION]);
|
|
16
|
+
/**
|
|
17
|
+
* Deterministically verifies a context handoff.
|
|
18
|
+
*
|
|
19
|
+
* Returns ok=false when ANY of the following is true:
|
|
20
|
+
* - schemaVersion is not in SUPPORTED_SCHEMAS
|
|
21
|
+
* - producerReceiptVerified is false
|
|
22
|
+
* - upstreamIntegrity is not "verified"
|
|
23
|
+
* - a required artifact is absent from availableArtifacts
|
|
24
|
+
* - an artifact sha256 does not appear in availableArtifacts (hash changed)
|
|
25
|
+
* - any required claim has state "unverified" or "rejected"
|
|
26
|
+
* - any claim has state "unknown"
|
|
27
|
+
* - unresolvedAssumptions is non-empty
|
|
28
|
+
* - handoffId, chainId, producerRunId, or producerReceiptHash is blank
|
|
29
|
+
*/
|
|
30
|
+
export function verifyContextHandoff(input) {
|
|
31
|
+
const { handoff, producerReceiptVerified, availableArtifacts } = input;
|
|
32
|
+
const reasons = [];
|
|
33
|
+
// ── Schema ──────────────────────────────────────────────────────────────────
|
|
34
|
+
if (!SUPPORTED_SCHEMAS.has(handoff.schemaVersion)) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
integrity: "unsupported_schema",
|
|
38
|
+
reasons: [
|
|
39
|
+
{
|
|
40
|
+
code: "unsupported_schema",
|
|
41
|
+
message: `Handoff schemaVersion "${handoff.schemaVersion}" is not supported. Supported: ${[...SUPPORTED_SCHEMAS].join(", ")}.`
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// ── Required identity fields ─────────────────────────────────────────────────
|
|
47
|
+
if (!isPresent(handoff.handoffId)) {
|
|
48
|
+
reasons.push({ code: "missing_handoff_id", message: "handoffId is required." });
|
|
49
|
+
}
|
|
50
|
+
if (!isPresent(handoff.chainId)) {
|
|
51
|
+
reasons.push({ code: "missing_chain_id", message: "chainId is required." });
|
|
52
|
+
}
|
|
53
|
+
if (!isPresent(handoff.producerRunId)) {
|
|
54
|
+
reasons.push({ code: "missing_producer_run_id", message: "producerRunId is required." });
|
|
55
|
+
}
|
|
56
|
+
if (!isPresent(handoff.producerReceiptHash)) {
|
|
57
|
+
reasons.push({ code: "missing_producer_receipt_hash", message: "producerReceiptHash is required." });
|
|
58
|
+
}
|
|
59
|
+
// ── Producer receipt verification ────────────────────────────────────────────
|
|
60
|
+
if (!producerReceiptVerified) {
|
|
61
|
+
reasons.push({
|
|
62
|
+
code: "producer_receipt_unverified",
|
|
63
|
+
message: "The producer receipt hash could not be independently verified."
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// ── Upstream integrity propagation ───────────────────────────────────────────
|
|
67
|
+
if (handoff.upstreamIntegrity !== "verified") {
|
|
68
|
+
reasons.push({
|
|
69
|
+
code: `upstream_integrity_${handoff.upstreamIntegrity}`,
|
|
70
|
+
message: `Upstream integrity is "${handoff.upstreamIntegrity}" — cannot proceed.`
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// ── Artifact verification ────────────────────────────────────────────────────
|
|
74
|
+
for (const artifact of handoff.artifacts) {
|
|
75
|
+
const present = availableArtifacts.has(artifact.sha256);
|
|
76
|
+
if (artifact.required && !present) {
|
|
77
|
+
reasons.push({
|
|
78
|
+
code: "missing_required_artifact",
|
|
79
|
+
message: `Required artifact is absent (sha256: ${artifact.sha256}${artifact.label ? `, label: ${artifact.label}` : ""}).`,
|
|
80
|
+
artifactSha256: artifact.sha256
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else if (!present) {
|
|
84
|
+
reasons.push({
|
|
85
|
+
code: "artifact_hash_mismatch",
|
|
86
|
+
message: `Artifact hash not found in available set (sha256: ${artifact.sha256}${artifact.label ? `, label: ${artifact.label}` : ""}).`,
|
|
87
|
+
artifactSha256: artifact.sha256
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ── Claim verification ────────────────────────────────────────────────────────
|
|
92
|
+
for (const claim of handoff.claims) {
|
|
93
|
+
if (claim.verificationState === "rejected") {
|
|
94
|
+
reasons.push({
|
|
95
|
+
code: "claim_rejected",
|
|
96
|
+
message: `Claim "${claim.claimId}" was rejected: ${claim.statement}`,
|
|
97
|
+
claimId: claim.claimId
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (claim.verificationState === "unverified") {
|
|
101
|
+
reasons.push({
|
|
102
|
+
code: "claim_unverified",
|
|
103
|
+
message: `Claim "${claim.claimId}" is unverified: ${claim.statement}`,
|
|
104
|
+
claimId: claim.claimId
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else if (claim.verificationState === "unknown") {
|
|
108
|
+
reasons.push({
|
|
109
|
+
code: "claim_unknown",
|
|
110
|
+
message: `Claim "${claim.claimId}" has unknown verification state: ${claim.statement}`,
|
|
111
|
+
claimId: claim.claimId
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// ── Unresolved assumptions ────────────────────────────────────────────────────
|
|
116
|
+
if (handoff.unresolvedAssumptions.length > 0) {
|
|
117
|
+
reasons.push({
|
|
118
|
+
code: "unresolved_assumptions",
|
|
119
|
+
message: `${handoff.unresolvedAssumptions.length} assumption(s) remain unresolved: ${handoff.unresolvedAssumptions.slice(0, 3).join("; ")}${handoff.unresolvedAssumptions.length > 3 ? " …" : ""}`
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const ok = reasons.length === 0;
|
|
123
|
+
const integrity = ok ? "verified" : deriveIntegrity(handoff, reasons);
|
|
124
|
+
return { ok, integrity, reasons };
|
|
125
|
+
}
|
|
126
|
+
// ─── Circuit breaker ──────────────────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Converts a ContextHandoffVerification into a gate decision.
|
|
129
|
+
*
|
|
130
|
+
* Healthy: { shouldStop: false, silent: true }
|
|
131
|
+
* Broken: { shouldStop: true, silent: false, reasonCode, message, nextAction }
|
|
132
|
+
*/
|
|
133
|
+
export function decideContextCircuitBreak(verification) {
|
|
134
|
+
if (verification.ok) {
|
|
135
|
+
return {
|
|
136
|
+
shouldStop: false,
|
|
137
|
+
silent: true,
|
|
138
|
+
verification
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const first = verification.reasons[0];
|
|
142
|
+
return {
|
|
143
|
+
shouldStop: true,
|
|
144
|
+
silent: false,
|
|
145
|
+
verification,
|
|
146
|
+
reasonCode: first?.code ?? "handoff_verification_failed",
|
|
147
|
+
message: first?.message ?? "Context handoff verification failed.",
|
|
148
|
+
nextAction: "Repair or replace the upstream handoff receipt, then retry."
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
152
|
+
function isPresent(value) {
|
|
153
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
154
|
+
}
|
|
155
|
+
function deriveIntegrity(handoff, reasons) {
|
|
156
|
+
const codes = reasons.map((r) => r.code);
|
|
157
|
+
if (codes.includes("unsupported_schema"))
|
|
158
|
+
return "unsupported_schema";
|
|
159
|
+
if (codes.some((c) => c === "upstream_integrity_tamper_detected" ||
|
|
160
|
+
c === "artifact_hash_mismatch")) {
|
|
161
|
+
return "tamper_detected";
|
|
162
|
+
}
|
|
163
|
+
if (codes.includes("producer_receipt_unverified") || codes.includes("upstream_integrity_evidence_boundary")) {
|
|
164
|
+
return "evidence_boundary";
|
|
165
|
+
}
|
|
166
|
+
return "incomplete";
|
|
167
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Shadow Compiler — A-CTX-0
|
|
3
|
+
*
|
|
4
|
+
* Deterministic shadow manifest emitter. Observes the compiled prompt and
|
|
5
|
+
* records what a context-aware selector would have chosen, without altering
|
|
6
|
+
* the actual provider input in any way.
|
|
7
|
+
*
|
|
8
|
+
* Invariants:
|
|
9
|
+
* - nowMs is always a captured input; Date.now() is never called internally
|
|
10
|
+
* - No source text appears in any output type
|
|
11
|
+
* - Required segments are always preferred first
|
|
12
|
+
* - required-over-budget is recorded, never thrown
|
|
13
|
+
* - Manifest hash covers metadata only (no content)
|
|
14
|
+
*/
|
|
15
|
+
import type { ContextC5EnvelopeV1, ContextEvidence, ContextShadowManifestV1 } from "../contracts/index.js";
|
|
16
|
+
export interface ContextShadowSegment {
|
|
17
|
+
segmentId: string;
|
|
18
|
+
kind: string;
|
|
19
|
+
required: boolean;
|
|
20
|
+
text: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CompileContextShadowInput {
|
|
23
|
+
runId: string;
|
|
24
|
+
taskId?: string;
|
|
25
|
+
adapter: string;
|
|
26
|
+
model?: string;
|
|
27
|
+
/** Milliseconds since epoch — must be explicit. Never call Date.now() here. */
|
|
28
|
+
nowMs: number;
|
|
29
|
+
shadowBudgetTokens: number;
|
|
30
|
+
modelWindowTokens?: number;
|
|
31
|
+
modelWindowEvidence?: ContextEvidence;
|
|
32
|
+
segments: readonly ContextShadowSegment[];
|
|
33
|
+
}
|
|
34
|
+
export interface CompileContextShadowResult {
|
|
35
|
+
manifest: ContextShadowManifestV1;
|
|
36
|
+
receipt: ContextC5EnvelopeV1;
|
|
37
|
+
}
|
|
38
|
+
export declare function estimateContextTokens(text: string): number;
|
|
39
|
+
export declare function compileContextShadow(input: CompileContextShadowInput): CompileContextShadowResult;
|