@atrib/recall 0.14.6 → 1.0.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 +40 -4
- package/dist/event-type-filter.d.ts +9 -0
- package/dist/event-type-filter.js +15 -0
- package/dist/event-type-filter.js.map +1 -0
- package/dist/index.d.ts +59 -2
- package/dist/index.js +392 -453
- package/dist/index.js.map +1 -1
- package/dist/recall-verb.d.ts +128 -0
- package/dist/recall-verb.js +299 -0
- package/dist/recall-verb.js.map +1 -0
- package/dist/trace-storage.d.ts +36 -0
- package/dist/trace-storage.js +93 -0
- package/dist/trace-storage.js.map +1 -0
- package/dist/trace-tools.d.ts +102 -0
- package/dist/trace-tools.js +314 -0
- package/dist/trace-tools.js.map +1 -0
- package/dist/trace-walk.d.ts +106 -0
- package/dist/trace-walk.js +275 -0
- package/dist/trace-walk.js.map +1 -0
- package/dist/verification.d.ts +108 -0
- package/dist/verification.js +223 -0
- package/dist/verification.js.map +1 -0
- package/package.json +15 -3
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Walk informed_by edges backward from the starting record.
|
|
4
|
+
*
|
|
5
|
+
* Behaviors:
|
|
6
|
+
* - Cycle-safe: every record is visited at most once, even if multiple
|
|
7
|
+
* parents reference it.
|
|
8
|
+
* - Cap-safe: if maxNodes is hit mid-walk, returns partial result with
|
|
9
|
+
* truncated_by_cap=true. Callers can re-run with a smaller depth or
|
|
10
|
+
* start from a sub-tree if needed.
|
|
11
|
+
* - Dangling-aware: informed_by entries pointing at records not in the
|
|
12
|
+
* local mirror surface as `dangling`. Those entries do NOT advance the
|
|
13
|
+
* walk (we have nothing to walk from).
|
|
14
|
+
*/
|
|
15
|
+
export function traceBackward(startHash, depth, index, options = {}) {
|
|
16
|
+
const maxNodes = options.maxNodes ?? 200;
|
|
17
|
+
const contextId = options.contextId;
|
|
18
|
+
const warnings = [];
|
|
19
|
+
const visited = new Map();
|
|
20
|
+
const dangling = new Set();
|
|
21
|
+
let truncatedByCap = false;
|
|
22
|
+
let truncatedByDepth = false;
|
|
23
|
+
let depthReached = 0;
|
|
24
|
+
const startIdx = index.get(startHash);
|
|
25
|
+
if (!startIdx) {
|
|
26
|
+
return {
|
|
27
|
+
start_hash: startHash,
|
|
28
|
+
direction: 'backward',
|
|
29
|
+
depth_requested: depth,
|
|
30
|
+
depth_reached: 0,
|
|
31
|
+
visited: [],
|
|
32
|
+
dangling: [startHash],
|
|
33
|
+
truncated_by_depth: false,
|
|
34
|
+
truncated_by_cap: false,
|
|
35
|
+
warnings: [`start_hash ${startHash} not in local mirror`],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// context_id scope check: when the caller pinned a context_id, the start
|
|
39
|
+
// record itself must already live within it. Reject explicitly rather than
|
|
40
|
+
// silently returning an empty walk; the caller likely wants to know they
|
|
41
|
+
// pointed trace at the wrong record for the requested scope.
|
|
42
|
+
if (contextId && startIdx.record.context_id !== contextId) {
|
|
43
|
+
return {
|
|
44
|
+
start_hash: startHash,
|
|
45
|
+
direction: 'backward',
|
|
46
|
+
depth_requested: depth,
|
|
47
|
+
depth_reached: 0,
|
|
48
|
+
visited: [],
|
|
49
|
+
dangling: [],
|
|
50
|
+
truncated_by_depth: false,
|
|
51
|
+
truncated_by_cap: false,
|
|
52
|
+
warnings: [
|
|
53
|
+
`start_hash ${startHash} lives in context_id ${startIdx.record.context_id} but trace was scoped to ${contextId} (set ATRIB_CONTEXT_ID to override or omit context_id to walk cross-context)`,
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const frontier = [{ hash: startHash, depth: 0, parent: null }];
|
|
58
|
+
while (frontier.length > 0) {
|
|
59
|
+
if (visited.size >= maxNodes) {
|
|
60
|
+
truncatedByCap = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
const current = frontier.shift();
|
|
64
|
+
if (current.depth > depth) {
|
|
65
|
+
truncatedByDepth = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
depthReached = Math.max(depthReached, current.depth);
|
|
69
|
+
const idx = index.get(current.hash);
|
|
70
|
+
if (!idx) {
|
|
71
|
+
dangling.add(current.hash);
|
|
72
|
+
// Tag the parent so caller can see which walkable record had a
|
|
73
|
+
// dangling reference, but we don't visit further.
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// context_id scope filter: an upstream record that lives in a different
|
|
77
|
+
// context_id is treated as dangling from this walk's perspective. The
|
|
78
|
+
// record itself exists, but it sits outside the requested scope. This
|
|
79
|
+
// keeps per-arm trace results clean when ATRIB_CONTEXT_ID is set per
|
|
80
|
+
// D072 (cross-arm informed_by edges, if any leaked, do not pollute
|
|
81
|
+
// the walk).
|
|
82
|
+
if (contextId && idx.record.context_id !== contextId) {
|
|
83
|
+
dangling.add(current.hash);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
let v = visited.get(current.hash);
|
|
87
|
+
if (!v) {
|
|
88
|
+
const informedByEntries = Array.isArray(idx.record.informed_by)
|
|
89
|
+
? idx.record.informed_by
|
|
90
|
+
: [];
|
|
91
|
+
v = {
|
|
92
|
+
depth: current.depth,
|
|
93
|
+
record_hash: current.hash,
|
|
94
|
+
parent_hashes: [],
|
|
95
|
+
record: idx.record,
|
|
96
|
+
source: idx.source,
|
|
97
|
+
next_informed_by: informedByEntries,
|
|
98
|
+
};
|
|
99
|
+
visited.set(current.hash, v);
|
|
100
|
+
// Schedule informed_by entries for visit at depth+1.
|
|
101
|
+
if (current.depth < depth) {
|
|
102
|
+
for (const upstream of informedByEntries) {
|
|
103
|
+
frontier.push({ hash: upstream, depth: current.depth + 1, parent: current.hash });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (informedByEntries.length > 0) {
|
|
107
|
+
truncatedByDepth = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (current.parent && !v.parent_hashes.includes(current.parent)) {
|
|
111
|
+
v.parent_hashes.push(current.parent);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
start_hash: startHash,
|
|
116
|
+
direction: 'backward',
|
|
117
|
+
depth_requested: depth,
|
|
118
|
+
depth_reached: depthReached,
|
|
119
|
+
visited: Array.from(visited.values()),
|
|
120
|
+
dangling: Array.from(dangling),
|
|
121
|
+
truncated_by_depth: truncatedByDepth,
|
|
122
|
+
truncated_by_cap: truncatedByCap,
|
|
123
|
+
warnings,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build a reverse informed_by index: for each record_hash R, the list of
|
|
128
|
+
* record_hashes that cite R in their informed_by field. O(total
|
|
129
|
+
* informed_by entries) build; O(1) lookup per parent during walk.
|
|
130
|
+
*
|
|
131
|
+
* Exported so callers (or tests) can pre-build the index when running
|
|
132
|
+
* multiple traceForward calls against the same mirror.
|
|
133
|
+
*/
|
|
134
|
+
export function buildReverseInformedByIndex(index) {
|
|
135
|
+
const reverse = new Map();
|
|
136
|
+
for (const { record, record_hash } of index.values()) {
|
|
137
|
+
const ib = Array.isArray(record.informed_by) ? record.informed_by : [];
|
|
138
|
+
for (const upstream of ib) {
|
|
139
|
+
if (typeof upstream !== 'string')
|
|
140
|
+
continue;
|
|
141
|
+
const existing = reverse.get(upstream);
|
|
142
|
+
if (existing)
|
|
143
|
+
existing.push(record_hash);
|
|
144
|
+
else
|
|
145
|
+
reverse.set(upstream, [record_hash]);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return reverse;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Walk informed_by edges FORWARD from the starting record.
|
|
152
|
+
*
|
|
153
|
+
* Answers: "what records cited THIS record via informed_by?" — i.e. what
|
|
154
|
+
* decisions/observations followed from this one in the causal substrate.
|
|
155
|
+
* The dual of traceBackward. Useful for "I made decision X, what did I do
|
|
156
|
+
* because of it?" lookups.
|
|
157
|
+
*
|
|
158
|
+
* Same behaviors as traceBackward:
|
|
159
|
+
* - Cycle-safe via visited-set
|
|
160
|
+
* - Cap-safe via maxNodes
|
|
161
|
+
* - context_id-scoped (if specified): edges into a different context_id
|
|
162
|
+
* are treated as dangling
|
|
163
|
+
* - Dangling: walking forward to a record present in the reverse index
|
|
164
|
+
* but missing from the mirror surfaces it as dangling (defensive; in
|
|
165
|
+
* practice the same mirror that built the reverse index has the
|
|
166
|
+
* record itself)
|
|
167
|
+
*/
|
|
168
|
+
export function traceForward(startHash, depth, index, options = {}) {
|
|
169
|
+
const maxNodes = options.maxNodes ?? 200;
|
|
170
|
+
const contextId = options.contextId;
|
|
171
|
+
const warnings = [];
|
|
172
|
+
const visited = new Map();
|
|
173
|
+
const dangling = new Set();
|
|
174
|
+
let truncatedByCap = false;
|
|
175
|
+
let truncatedByDepth = false;
|
|
176
|
+
let depthReached = 0;
|
|
177
|
+
const startIdx = index.get(startHash);
|
|
178
|
+
if (!startIdx) {
|
|
179
|
+
return {
|
|
180
|
+
start_hash: startHash,
|
|
181
|
+
direction: 'forward',
|
|
182
|
+
depth_requested: depth,
|
|
183
|
+
depth_reached: 0,
|
|
184
|
+
visited: [],
|
|
185
|
+
dangling: [startHash],
|
|
186
|
+
truncated_by_depth: false,
|
|
187
|
+
truncated_by_cap: false,
|
|
188
|
+
warnings: [`start_hash ${startHash} not in local mirror`],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (contextId && startIdx.record.context_id !== contextId) {
|
|
192
|
+
return {
|
|
193
|
+
start_hash: startHash,
|
|
194
|
+
direction: 'forward',
|
|
195
|
+
depth_requested: depth,
|
|
196
|
+
depth_reached: 0,
|
|
197
|
+
visited: [],
|
|
198
|
+
dangling: [],
|
|
199
|
+
truncated_by_depth: false,
|
|
200
|
+
truncated_by_cap: false,
|
|
201
|
+
warnings: [
|
|
202
|
+
`start_hash ${startHash} lives in context_id ${startIdx.record.context_id} but trace was scoped to ${contextId} (set ATRIB_CONTEXT_ID to override or omit context_id to walk cross-context)`,
|
|
203
|
+
],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Build the reverse index once per call. For Layer 1 corpus sizes
|
|
207
|
+
// (~14K records) the O(N) build is negligible; the saved per-hop O(1)
|
|
208
|
+
// child lookup is worth the linear scan.
|
|
209
|
+
const reverse = buildReverseInformedByIndex(index);
|
|
210
|
+
const frontier = [{ hash: startHash, depth: 0, parent: null }];
|
|
211
|
+
while (frontier.length > 0) {
|
|
212
|
+
if (visited.size >= maxNodes) {
|
|
213
|
+
truncatedByCap = true;
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
const current = frontier.shift();
|
|
217
|
+
if (current.depth > depth) {
|
|
218
|
+
truncatedByDepth = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
depthReached = Math.max(depthReached, current.depth);
|
|
222
|
+
const idx = index.get(current.hash);
|
|
223
|
+
if (!idx) {
|
|
224
|
+
// The reverse index pointed at a record not present in the mirror.
|
|
225
|
+
// Defensive case (the reverse index was built FROM the same mirror,
|
|
226
|
+
// so this branch is unreachable in practice). Treat as dangling.
|
|
227
|
+
dangling.add(current.hash);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (contextId && idx.record.context_id !== contextId) {
|
|
231
|
+
dangling.add(current.hash);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
let v = visited.get(current.hash);
|
|
235
|
+
if (!v) {
|
|
236
|
+
const children = reverse.get(current.hash) ?? [];
|
|
237
|
+
v = {
|
|
238
|
+
depth: current.depth,
|
|
239
|
+
record_hash: current.hash,
|
|
240
|
+
parent_hashes: [],
|
|
241
|
+
record: idx.record,
|
|
242
|
+
source: idx.source,
|
|
243
|
+
// `next_informed_by` reused here as "edges out of this node in
|
|
244
|
+
// the walk direction" — for forward walk that's the children
|
|
245
|
+
// (records citing this one), not this record's own informed_by.
|
|
246
|
+
// Field name kept for shape-compat with traceBackward.
|
|
247
|
+
next_informed_by: children,
|
|
248
|
+
};
|
|
249
|
+
visited.set(current.hash, v);
|
|
250
|
+
if (current.depth < depth) {
|
|
251
|
+
for (const child of children) {
|
|
252
|
+
frontier.push({ hash: child, depth: current.depth + 1, parent: current.hash });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
else if (children.length > 0) {
|
|
256
|
+
truncatedByDepth = true;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (current.parent && !v.parent_hashes.includes(current.parent)) {
|
|
260
|
+
v.parent_hashes.push(current.parent);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
start_hash: startHash,
|
|
265
|
+
direction: 'forward',
|
|
266
|
+
depth_requested: depth,
|
|
267
|
+
depth_reached: depthReached,
|
|
268
|
+
visited: Array.from(visited.values()),
|
|
269
|
+
dangling: Array.from(dangling),
|
|
270
|
+
truncated_by_depth: truncatedByDepth,
|
|
271
|
+
truncated_by_cap: truncatedByCap,
|
|
272
|
+
warnings,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=trace-walk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trace-walk.js","sourceRoot":"","sources":["../src/trace-walk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The legacy `atrib-verify` tool and the `recall` verb's `verification`
|
|
3
|
+
* parameter: verify counterparty handoff evidence before the receiving
|
|
4
|
+
* agent signs a follow-up that cites it through informed_by. The surface
|
|
5
|
+
* is deliberately thin. @atrib/verify owns the actual cryptographic
|
|
6
|
+
* checks, packet extraction, and rejection reasons.
|
|
7
|
+
*
|
|
8
|
+
* Moved here from @atrib/verify-mcp per the attest/recall rename;
|
|
9
|
+
* @atrib/verify-mcp re-exports this module so existing imports keep
|
|
10
|
+
* working.
|
|
11
|
+
*
|
|
12
|
+
* Dependency posture: @atrib/verify is an OPTIONAL PEER of @atrib/recall,
|
|
13
|
+
* loaded lazily on first use. Its module closure transitively reaches the
|
|
14
|
+
* D090/D091 JOSE/SD-JWT evidence stack, which most reads never need;
|
|
15
|
+
* pulling it into every recall install would contradict the primitive's
|
|
16
|
+
* narrow framing. When the peer is unresolvable, the read itself still
|
|
17
|
+
* succeeds and verification degrades to a typed `verifier_unavailable`
|
|
18
|
+
* result per the §5.8 degradation contract. The daemon / primitives
|
|
19
|
+
* runtime and the @atrib/verify-mcp shim always bundle the verifier, so
|
|
20
|
+
* their behavior is unchanged.
|
|
21
|
+
*/
|
|
22
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
23
|
+
import { z } from 'zod';
|
|
24
|
+
import type { HandoffClaimVerification } from '@atrib/verify';
|
|
25
|
+
type VerifyModule = typeof import('@atrib/verify');
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the optional @atrib/verify peer once per process. Unresolvable
|
|
28
|
+
* peers log one `atrib:`-prefixed diagnostic and degrade; they never throw
|
|
29
|
+
* into a read path.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadVerifyModule(): Promise<VerifyModule | null>;
|
|
32
|
+
/** Test hook: reset the memoized peer resolution. */
|
|
33
|
+
export declare function __resetVerifyModuleForTests(loader?: () => Promise<VerifyModule | null>): void;
|
|
34
|
+
export declare const VerifyInput: z.ZodObject<{
|
|
35
|
+
packet: z.ZodOptional<z.ZodUnknown>;
|
|
36
|
+
records: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
37
|
+
claims: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
38
|
+
required_record_hashes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
39
|
+
trusted_creator_keys: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
40
|
+
allowed_context_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
41
|
+
require_body: z.ZodOptional<z.ZodBoolean>;
|
|
42
|
+
require_body_commitment: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
+
require_log_inclusion: z.ZodOptional<z.ZodBoolean>;
|
|
44
|
+
log_public_key_b64: z.ZodOptional<z.ZodString>;
|
|
45
|
+
now_ms: z.ZodOptional<z.ZodNumber>;
|
|
46
|
+
max_age_ms: z.ZodOptional<z.ZodNumber>;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
export type AtribVerifyInput = z.infer<typeof VerifyInput>;
|
|
49
|
+
export interface AtribVerifyServer {
|
|
50
|
+
mcp: McpServer;
|
|
51
|
+
}
|
|
52
|
+
export interface AtribVerifyOutput {
|
|
53
|
+
primitive: 'atrib-verify';
|
|
54
|
+
all_accepted: boolean;
|
|
55
|
+
accepted_record_hashes: string[];
|
|
56
|
+
accepted: CompactHandoffClaim[];
|
|
57
|
+
rejected: CompactHandoffClaim[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The `verification` block attached to `recall` verb responses. Degraded
|
|
61
|
+
* verification never blocks a read: when the optional @atrib/verify peer
|
|
62
|
+
* is unresolvable the block carries status verifier_unavailable and the
|
|
63
|
+
* read result is unchanged.
|
|
64
|
+
*/
|
|
65
|
+
export type RecallVerificationBlock = {
|
|
66
|
+
status: 'ok';
|
|
67
|
+
result: AtribVerifyOutput;
|
|
68
|
+
} | {
|
|
69
|
+
status: 'verifier_unavailable';
|
|
70
|
+
reason: string;
|
|
71
|
+
};
|
|
72
|
+
interface CompactHandoffClaim {
|
|
73
|
+
record_hash: string;
|
|
74
|
+
accepted: boolean;
|
|
75
|
+
rejection_reasons: string[];
|
|
76
|
+
warnings: string[];
|
|
77
|
+
signature_ok: boolean | null;
|
|
78
|
+
computed_record_hash: string | null;
|
|
79
|
+
signer_trusted: boolean | null;
|
|
80
|
+
context_allowed: boolean | null;
|
|
81
|
+
record_context_id?: string;
|
|
82
|
+
record_creator_key?: string;
|
|
83
|
+
record_timestamp?: number;
|
|
84
|
+
body?: HandoffClaimVerification['body'];
|
|
85
|
+
proof?: HandoffClaimVerification['proof'];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Run the Pattern 3 handoff-claim acceptance checks. Throws when the
|
|
89
|
+
* optional @atrib/verify peer is unresolvable (the @atrib/verify-mcp shim
|
|
90
|
+
* and the daemon topology always bundle it, so their behavior is
|
|
91
|
+
* unchanged); callers that must not fail use tryHandleAtribVerify.
|
|
92
|
+
*/
|
|
93
|
+
export declare function handleAtribVerify(input: AtribVerifyInput): Promise<AtribVerifyOutput>;
|
|
94
|
+
/**
|
|
95
|
+
* Degradation-safe wrapper for the `recall` verb's `verification`
|
|
96
|
+
* parameter: unresolvable peer -> typed verifier_unavailable block, never
|
|
97
|
+
* a thrown error into the read path.
|
|
98
|
+
*/
|
|
99
|
+
export declare function tryHandleAtribVerify(input: AtribVerifyInput): Promise<RecallVerificationBlock>;
|
|
100
|
+
/** Register the legacy `atrib-verify` tool on a server. */
|
|
101
|
+
export declare function registerVerifyTool(mcp: McpServer): void;
|
|
102
|
+
/**
|
|
103
|
+
* Wire up the legacy atrib-verify MCP server. Mounts `atrib-verify` plus
|
|
104
|
+
* the `recall` verb (alias-window rule W1). The @atrib/verify-mcp package
|
|
105
|
+
* re-exports this factory.
|
|
106
|
+
*/
|
|
107
|
+
export declare function createAtribVerifyServer(): Promise<AtribVerifyServer>;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* The legacy `atrib-verify` tool and the `recall` verb's `verification`
|
|
4
|
+
* parameter: verify counterparty handoff evidence before the receiving
|
|
5
|
+
* agent signs a follow-up that cites it through informed_by. The surface
|
|
6
|
+
* is deliberately thin. @atrib/verify owns the actual cryptographic
|
|
7
|
+
* checks, packet extraction, and rejection reasons.
|
|
8
|
+
*
|
|
9
|
+
* Moved here from @atrib/verify-mcp per the attest/recall rename;
|
|
10
|
+
* @atrib/verify-mcp re-exports this module so existing imports keep
|
|
11
|
+
* working.
|
|
12
|
+
*
|
|
13
|
+
* Dependency posture: @atrib/verify is an OPTIONAL PEER of @atrib/recall,
|
|
14
|
+
* loaded lazily on first use. Its module closure transitively reaches the
|
|
15
|
+
* D090/D091 JOSE/SD-JWT evidence stack, which most reads never need;
|
|
16
|
+
* pulling it into every recall install would contradict the primitive's
|
|
17
|
+
* narrow framing. When the peer is unresolvable, the read itself still
|
|
18
|
+
* succeeds and verification degrades to a typed `verifier_unavailable`
|
|
19
|
+
* result per the §5.8 degradation contract. The daemon / primitives
|
|
20
|
+
* runtime and the @atrib/verify-mcp shim always bundle the verifier, so
|
|
21
|
+
* their behavior is unchanged.
|
|
22
|
+
*/
|
|
23
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
24
|
+
import { z } from 'zod';
|
|
25
|
+
import { extractRecordHashesFromMcpResult, logReadPrimitiveCall, SHA256_REF_PATTERN, } from '@atrib/mcp';
|
|
26
|
+
const HEX_32_PATTERN = /^[0-9a-f]{32}$/;
|
|
27
|
+
let verifyModulePromise = null;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the optional @atrib/verify peer once per process. Unresolvable
|
|
30
|
+
* peers log one `atrib:`-prefixed diagnostic and degrade; they never throw
|
|
31
|
+
* into a read path.
|
|
32
|
+
*/
|
|
33
|
+
export function loadVerifyModule() {
|
|
34
|
+
verifyModulePromise ??= import('@atrib/verify').then((mod) => mod, (error) => {
|
|
35
|
+
try {
|
|
36
|
+
console.error('atrib: optional peer @atrib/verify is not installed; verification degrades to verifier_unavailable', error instanceof Error ? error.message : String(error));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Diagnostics must never affect the read path.
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
});
|
|
43
|
+
return verifyModulePromise;
|
|
44
|
+
}
|
|
45
|
+
/** Test hook: reset the memoized peer resolution. */
|
|
46
|
+
export function __resetVerifyModuleForTests(loader) {
|
|
47
|
+
verifyModulePromise = loader ? loader() : null;
|
|
48
|
+
}
|
|
49
|
+
export const VerifyInput = z.object({
|
|
50
|
+
packet: z
|
|
51
|
+
.unknown()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Private continuation or handoff packet carrying records, proof bundles, local mirror sidecars, and optional trust policy.'),
|
|
54
|
+
records: z
|
|
55
|
+
.array(z.unknown())
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('Evidence entries, usually parsed D062 local mirror envelopes or packet records. Used when packet is omitted.'),
|
|
58
|
+
claims: z
|
|
59
|
+
.array(z.unknown())
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Alias for records. Each entry can be a bare AtribRecord or an envelope with record, proof, and _local sidecar.'),
|
|
62
|
+
required_record_hashes: z
|
|
63
|
+
.array(z.string().regex(SHA256_REF_PATTERN))
|
|
64
|
+
.optional()
|
|
65
|
+
.describe('Record hashes the receiving agent expected. Missing entries are preserved as verifier rejections.'),
|
|
66
|
+
trusted_creator_keys: z
|
|
67
|
+
.array(z.string().min(1))
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Allowed creator_key values for upstream records. When set, records from other signers are rejected.'),
|
|
70
|
+
allowed_context_ids: z
|
|
71
|
+
.array(z.string().regex(HEX_32_PATTERN))
|
|
72
|
+
.optional()
|
|
73
|
+
.describe('Allowed upstream context_id values. When set, records from other contexts are rejected.'),
|
|
74
|
+
require_body: z
|
|
75
|
+
.boolean()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe('Require private body material from body, args/result, or D062 _local content.'),
|
|
78
|
+
require_body_commitment: z
|
|
79
|
+
.boolean()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe('Require args_hash or result_hash on the signed record so private body material can be checked.'),
|
|
82
|
+
require_log_inclusion: z
|
|
83
|
+
.boolean()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe('Require a supplied proof bundle whose inclusion path verifies against the checkpoint root.'),
|
|
86
|
+
log_public_key_b64: z
|
|
87
|
+
.string()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Optional trusted log Ed25519 public key, base64 or base64url encoded. When supplied, checkpoint signatures are verified.'),
|
|
90
|
+
now_ms: z
|
|
91
|
+
.number()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe('Verifier wall-clock override in milliseconds. Mostly for deterministic tests.'),
|
|
94
|
+
max_age_ms: z
|
|
95
|
+
.number()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe('Freshness window. Records older than this, or dated in the future, are rejected as stale.'),
|
|
98
|
+
});
|
|
99
|
+
function decodeLogPublicKey(value) {
|
|
100
|
+
if (!value)
|
|
101
|
+
return undefined;
|
|
102
|
+
const normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
103
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');
|
|
104
|
+
const decoded = new Uint8Array(Buffer.from(padded, 'base64'));
|
|
105
|
+
if (decoded.length !== 32) {
|
|
106
|
+
throw new Error(`log_public_key_b64 must decode to 32 bytes, got ${decoded.length}`);
|
|
107
|
+
}
|
|
108
|
+
return decoded;
|
|
109
|
+
}
|
|
110
|
+
function packetFromInput(input) {
|
|
111
|
+
if (input.packet !== undefined)
|
|
112
|
+
return input.packet;
|
|
113
|
+
return {
|
|
114
|
+
records: (input.records ?? input.claims ?? []),
|
|
115
|
+
required_record_hashes: input.required_record_hashes,
|
|
116
|
+
trusted_creator_keys: input.trusted_creator_keys,
|
|
117
|
+
allowed_context_ids: input.allowed_context_ids,
|
|
118
|
+
max_age_ms: input.max_age_ms,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function compactClaim(claim) {
|
|
122
|
+
const out = {
|
|
123
|
+
record_hash: claim.record_hash,
|
|
124
|
+
accepted: claim.accepted,
|
|
125
|
+
rejection_reasons: claim.rejection_reasons,
|
|
126
|
+
warnings: claim.warnings,
|
|
127
|
+
signature_ok: claim.signature_ok,
|
|
128
|
+
computed_record_hash: claim.computed_record_hash,
|
|
129
|
+
signer_trusted: claim.signer_trusted,
|
|
130
|
+
context_allowed: claim.context_allowed,
|
|
131
|
+
};
|
|
132
|
+
if (claim.record) {
|
|
133
|
+
out.record_context_id = claim.record.context_id;
|
|
134
|
+
out.record_creator_key = claim.record.creator_key;
|
|
135
|
+
out.record_timestamp = claim.record.timestamp;
|
|
136
|
+
}
|
|
137
|
+
if (claim.body)
|
|
138
|
+
out.body = claim.body;
|
|
139
|
+
if (claim.proof)
|
|
140
|
+
out.proof = claim.proof;
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
function compactResult(result) {
|
|
144
|
+
return {
|
|
145
|
+
primitive: 'atrib-verify',
|
|
146
|
+
all_accepted: result.all_accepted,
|
|
147
|
+
accepted_record_hashes: result.accepted_record_hashes,
|
|
148
|
+
accepted: result.accepted.map(compactClaim),
|
|
149
|
+
rejected: result.rejected.map(compactClaim),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Run the Pattern 3 handoff-claim acceptance checks. Throws when the
|
|
154
|
+
* optional @atrib/verify peer is unresolvable (the @atrib/verify-mcp shim
|
|
155
|
+
* and the daemon topology always bundle it, so their behavior is
|
|
156
|
+
* unchanged); callers that must not fail use tryHandleAtribVerify.
|
|
157
|
+
*/
|
|
158
|
+
export async function handleAtribVerify(input) {
|
|
159
|
+
const verify = await loadVerifyModule();
|
|
160
|
+
if (!verify) {
|
|
161
|
+
throw new Error('atrib: @atrib/verify is not installed; install it (optional peer of @atrib/recall) to run handoff verification');
|
|
162
|
+
}
|
|
163
|
+
const packet = packetFromInput(input);
|
|
164
|
+
const claims = verify.handoffClaimsFromEvidencePacket(packet, {
|
|
165
|
+
required_record_hashes: input.required_record_hashes,
|
|
166
|
+
trusted_creator_keys: input.trusted_creator_keys,
|
|
167
|
+
allowed_context_ids: input.allowed_context_ids,
|
|
168
|
+
max_age_ms: input.max_age_ms,
|
|
169
|
+
});
|
|
170
|
+
const result = await verify.verifyHandoffClaims(claims, {
|
|
171
|
+
trusted_creator_keys: input.trusted_creator_keys,
|
|
172
|
+
allowed_context_ids: input.allowed_context_ids,
|
|
173
|
+
require_body: input.require_body === true,
|
|
174
|
+
require_body_commitment: input.require_body_commitment === true,
|
|
175
|
+
require_log_inclusion: input.require_log_inclusion === true,
|
|
176
|
+
log_public_key: decodeLogPublicKey(input.log_public_key_b64),
|
|
177
|
+
now_ms: input.now_ms,
|
|
178
|
+
max_age_ms: input.max_age_ms,
|
|
179
|
+
});
|
|
180
|
+
return compactResult(result);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Degradation-safe wrapper for the `recall` verb's `verification`
|
|
184
|
+
* parameter: unresolvable peer -> typed verifier_unavailable block, never
|
|
185
|
+
* a thrown error into the read path.
|
|
186
|
+
*/
|
|
187
|
+
export async function tryHandleAtribVerify(input) {
|
|
188
|
+
const verify = await loadVerifyModule();
|
|
189
|
+
if (!verify) {
|
|
190
|
+
return {
|
|
191
|
+
status: 'verifier_unavailable',
|
|
192
|
+
reason: '@atrib/verify (optional peer of @atrib/recall) is not installed; the read result is unaffected',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return { status: 'ok', result: await handleAtribVerify(input) };
|
|
196
|
+
}
|
|
197
|
+
/** Register the legacy `atrib-verify` tool on a server. */
|
|
198
|
+
export function registerVerifyTool(mcp) {
|
|
199
|
+
mcp.registerTool('atrib-verify', {
|
|
200
|
+
description: 'Verify counterparty handoff evidence before linking follow-up work through informed_by. ' +
|
|
201
|
+
"Legacy alias: new callers should prefer the `recall` tool's `verification` parameter.",
|
|
202
|
+
inputSchema: VerifyInput.shape,
|
|
203
|
+
}, async (rawInput) => logReadPrimitiveCall('atrib-verify', rawInput, async () => {
|
|
204
|
+
const input = VerifyInput.parse(rawInput);
|
|
205
|
+
const result = await handleAtribVerify(input);
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
208
|
+
};
|
|
209
|
+
}, extractRecordHashesFromMcpResult));
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Wire up the legacy atrib-verify MCP server. Mounts `atrib-verify` plus
|
|
213
|
+
* the `recall` verb (alias-window rule W1). The @atrib/verify-mcp package
|
|
214
|
+
* re-exports this factory.
|
|
215
|
+
*/
|
|
216
|
+
export async function createAtribVerifyServer() {
|
|
217
|
+
const mcp = new McpServer({ name: 'atrib-verify', version: '0.1.0' });
|
|
218
|
+
registerVerifyTool(mcp);
|
|
219
|
+
const { registerRecallVerbTool } = await import('./recall-verb.js');
|
|
220
|
+
registerRecallVerbTool(mcp);
|
|
221
|
+
return { mcp };
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../src/verification.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atrib/recall",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "MCP server for atrib's verifiable action layer. Lets agents query their own provable past from the local mirror.",
|
|
5
5
|
"author": "atrib <hello@atrib.dev>",
|
|
6
6
|
"keywords": [
|
|
@@ -24,12 +24,16 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
26
26
|
"zod": "^4.3.6",
|
|
27
|
-
"@atrib/mcp": "0.
|
|
27
|
+
"@atrib/mcp": "0.21.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@noble/ed25519": "^3.1.0",
|
|
31
|
+
"@noble/hashes": "^2.2.0",
|
|
30
32
|
"@types/node": "^25.9.3",
|
|
33
|
+
"canonicalize": "^3.0.0",
|
|
31
34
|
"typescript": "^6.0.3",
|
|
32
|
-
"vitest": "^4.1.8"
|
|
35
|
+
"vitest": "^4.1.8",
|
|
36
|
+
"@atrib/verify": "0.9.0"
|
|
33
37
|
},
|
|
34
38
|
"license": "Apache-2.0",
|
|
35
39
|
"files": [
|
|
@@ -43,6 +47,14 @@
|
|
|
43
47
|
"url": "git+https://github.com/creatornader/atrib.git",
|
|
44
48
|
"directory": "services/atrib-recall"
|
|
45
49
|
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@atrib/verify": "^0.9.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@atrib/verify": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
46
58
|
"scripts": {
|
|
47
59
|
"build": "tsc",
|
|
48
60
|
"dev": "tsc --watch",
|