@atrib/attest 0.2.0 → 0.3.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 +6 -0
- package/dist/attest.js +1 -2
- package/dist/index.d.ts +23 -1
- package/dist/index.js +24 -12
- package/dist/reference-resolution.d.ts +1 -0
- package/dist/reference-resolution.js +3 -1
- package/dist/storage.d.ts +2 -1
- package/dist/storage.js +5 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -146,6 +146,12 @@ Environment variables are unchanged from the legacy emit surface:
|
|
|
146
146
|
table with descriptions; the values and resolution order carried over
|
|
147
147
|
unchanged.
|
|
148
148
|
|
|
149
|
+
Programmatic callers can set `mirrorPath` and `autochainSource` in
|
|
150
|
+
`EmitInProcessOptions`. `mirrorPath` selects the write target for one call.
|
|
151
|
+
`autochainSource` selects its mirror-based chain inheritance source. When only
|
|
152
|
+
`mirrorPath` is set, it is also the read source. Calls that omit both options
|
|
153
|
+
retain the environment and per-agent default ladder above.
|
|
154
|
+
|
|
149
155
|
## Degradation and privacy
|
|
150
156
|
|
|
151
157
|
atrib failures never affect the primary call ([spec §5.8](../../atrib-spec.md#58-degradation-contract)), and for the write
|
package/dist/attest.js
CHANGED
|
@@ -86,9 +86,8 @@ export const AttestInput = z.object({
|
|
|
86
86
|
tool_name: z
|
|
87
87
|
.string()
|
|
88
88
|
.min(1)
|
|
89
|
-
.max(64)
|
|
90
89
|
.optional()
|
|
91
|
-
.describe('Optional §8.2 tool_name disclosure
|
|
90
|
+
.describe('Optional §8.2 tool_name disclosure. The 64-character cap applies only to opaque labels; hashed and verbatim forms follow §8.2.'),
|
|
92
91
|
args_hash: z
|
|
93
92
|
.string()
|
|
94
93
|
.regex(SHA256_REF_PATTERN)
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { type LocalSubstrateCoordinatorTransport, type LocalSubstrateDegradationPolicy, type LocalSubstrateHarnessClass, type LocalSubstrateProducer, type LocalSubstrateWalJoin, type ProofBundle, type SubmissionQueue, type TryLocalSubstrateCoordinatorResult } from '@atrib/mcp';
|
|
4
4
|
import { type ResolvedKey } from './keys.js';
|
|
5
5
|
import { type RecordReferenceResolver } from './reference-resolution.js';
|
|
6
|
+
import { resolveMirrorWritePath } from './storage.js';
|
|
6
7
|
import { type AttestInputT } from './attest.js';
|
|
7
8
|
declare function keyResolveRetryMs(): number;
|
|
8
9
|
export declare function requiresExplicitContextId(env?: NodeJS.ProcessEnv): boolean;
|
|
@@ -185,6 +186,13 @@ interface HandleEmitInput {
|
|
|
185
186
|
*/
|
|
186
187
|
producer?: string;
|
|
187
188
|
logEndpoint?: string | undefined;
|
|
189
|
+
/** Explicit write target for this call. Falls back to ATRIB_MIRROR_FILE. */
|
|
190
|
+
mirrorPath?: string | undefined;
|
|
191
|
+
/**
|
|
192
|
+
* Explicit read source for mirror-based chain inheritance. When omitted,
|
|
193
|
+
* mirrorPath wins before the legacy environment and default path ladder.
|
|
194
|
+
*/
|
|
195
|
+
autochainSource?: string | undefined;
|
|
188
196
|
recordReferenceResolver?: RecordReferenceResolver | undefined;
|
|
189
197
|
localSubstrate?: EmitLocalSubstrateShadowOptions | undefined;
|
|
190
198
|
localSubstrateCommit?: EmitLocalSubstrateCommitOptions | undefined;
|
|
@@ -193,7 +201,7 @@ interface HandleEmitInput {
|
|
|
193
201
|
* Build, sign, submit, mirror. Refused writes return `signed: false`;
|
|
194
202
|
* signed degradations stay `signed: true` and surface in `warnings`.
|
|
195
203
|
*/
|
|
196
|
-
declare function handleEmit({ input, key, queue, producer, logEndpoint, recordReferenceResolver, localSubstrate, localSubstrateCommit, }: HandleEmitInput): Promise<EmitOutput>;
|
|
204
|
+
declare function handleEmit({ input, key, queue, producer, logEndpoint, mirrorPath, autochainSource, recordReferenceResolver, localSubstrate, localSubstrateCommit, }: HandleEmitInput): Promise<EmitOutput>;
|
|
197
205
|
export declare function resolveEmitLocalSubstrateShadowFromEnv(options?: ResolveEmitLocalSubstrateShadowFromEnvOptions): EmitLocalSubstrateShadowOptions | undefined;
|
|
198
206
|
export declare function resolveEmitLocalSubstrateCommitFromEnv(options?: ResolveEmitLocalSubstrateCommitFromEnvOptions): EmitLocalSubstrateCommitOptions | undefined;
|
|
199
207
|
export interface EmitInProcessOptions {
|
|
@@ -210,6 +218,17 @@ export interface EmitInProcessOptions {
|
|
|
210
218
|
* consumers can bucket records by emitter without inspecting envelopes.
|
|
211
219
|
*/
|
|
212
220
|
producer?: string;
|
|
221
|
+
/**
|
|
222
|
+
* Explicit mirror write target for this call. When omitted,
|
|
223
|
+
* ATRIB_MIRROR_FILE and the per-agent default remain unchanged fallbacks.
|
|
224
|
+
*/
|
|
225
|
+
mirrorPath?: string | undefined;
|
|
226
|
+
/**
|
|
227
|
+
* Explicit mirror read source for chain inheritance. When omitted, an
|
|
228
|
+
* explicit mirrorPath is also the read source before the environment
|
|
229
|
+
* fallback ladder.
|
|
230
|
+
*/
|
|
231
|
+
autochainSource?: string | undefined;
|
|
213
232
|
/**
|
|
214
233
|
* Upper bound on the post-sign queue flush, in milliseconds. Default
|
|
215
234
|
* 5000ms. The submission queue itself has a 30s retry budget against an
|
|
@@ -277,6 +296,8 @@ export interface HandleAttestInput {
|
|
|
277
296
|
/** Sidecar label; defaults to 'atrib-attest'. input.producer wins. */
|
|
278
297
|
producer?: string;
|
|
279
298
|
logEndpoint?: string | undefined;
|
|
299
|
+
mirrorPath?: string | undefined;
|
|
300
|
+
autochainSource?: string | undefined;
|
|
280
301
|
recordReferenceResolver?: RecordReferenceResolver | undefined;
|
|
281
302
|
localSubstrate?: EmitLocalSubstrateShadowOptions | undefined;
|
|
282
303
|
localSubstrateCommit?: EmitLocalSubstrateCommitOptions | undefined;
|
|
@@ -301,6 +322,7 @@ export declare const __test_only__: {
|
|
|
301
322
|
keyResolveRetryMs: typeof keyResolveRetryMs;
|
|
302
323
|
};
|
|
303
324
|
export { handleEmit, EmitInput };
|
|
325
|
+
export { resolveMirrorWritePath };
|
|
304
326
|
export type { EmitOutput };
|
|
305
327
|
export { resolveKey } from './keys.js';
|
|
306
328
|
export type { ResolvedKey } from './keys.js';
|
package/dist/index.js
CHANGED
|
@@ -17,13 +17,11 @@
|
|
|
17
17
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
18
|
import { z } from 'zod';
|
|
19
19
|
import { randomBytes } from 'node:crypto';
|
|
20
|
-
import { homedir } from 'node:os';
|
|
21
|
-
import { join } from 'node:path';
|
|
22
20
|
import { EVENT_TYPE_ANNOTATION_URI, EVENT_TYPE_REVISION_URI, LOCAL_SUBSTRATE_DEFAULT_TIMEOUT_MS, LOCAL_SUBSTRATE_REQUEST_SCHEMA, canonicalRecord, createHttpLocalSubstrateTransport, createSubmissionQueue, genesisChainRoot, hexEncode, inheritChainContext, isValidEventTypeUri, normalizeEventType, parentRecordHashFromEnv, resolveEnvContextId, SHA256_REF_PATTERN, sha256, tryLocalSubstrateCoordinator, } from '@atrib/mcp';
|
|
23
21
|
import { resolveKey } from './keys.js';
|
|
24
22
|
import { filterResolvableInformedBy } from './reference-resolution.js';
|
|
25
23
|
import { buildAndSignEmitRecord } from './sign.js';
|
|
26
|
-
import { mirrorRecord } from './storage.js';
|
|
24
|
+
import { mirrorRecord, resolveMirrorWritePath } from './storage.js';
|
|
27
25
|
import { AttestInput, isAttestMappingRefusal, mapAttestInput } from './attest.js';
|
|
28
26
|
import { registerAnnotateTool } from './annotate.js';
|
|
29
27
|
import { registerReviseTool } from './revise.js';
|
|
@@ -33,10 +31,11 @@ import { registerReviseTool } from './revise.js';
|
|
|
33
31
|
// rarely useful as a read source, but kept for backward compatibility),
|
|
34
32
|
// then to the per-agent default. Distinct from ATRIB_MIRROR_FILE which is
|
|
35
33
|
// where emit's own records are persisted.
|
|
36
|
-
function readMirrorPath() {
|
|
37
|
-
return (
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
function readMirrorPath(options = {}) {
|
|
35
|
+
return (options.autochainSource ??
|
|
36
|
+
options.mirrorPath ??
|
|
37
|
+
process.env['ATRIB_AUTOCHAIN_SOURCE'] ??
|
|
38
|
+
resolveMirrorWritePath());
|
|
40
39
|
}
|
|
41
40
|
const HEX_32_PATTERN = /^[0-9a-f]{32}$/;
|
|
42
41
|
// 16 bytes encoded as base64url with no padding = 22 chars per spec §1.2.6.
|
|
@@ -127,10 +126,11 @@ const EmitInput = z.object({
|
|
|
127
126
|
tool_name: z
|
|
128
127
|
.string()
|
|
129
128
|
.min(1)
|
|
130
|
-
.max(64)
|
|
131
129
|
.optional()
|
|
132
130
|
.describe('Optional §8.2 tool_name disclosure. Verbatim or transformed name (verbatim, opaque, ' +
|
|
133
|
-
'or hashed per §8.2).
|
|
131
|
+
'or hashed per §8.2). The 64-character cap applies only to opaque-label form; ' +
|
|
132
|
+
'the hashed form is sha256:<64 lowercase hex>, and verbatim names have no ' +
|
|
133
|
+
'protocol-wide length cap. Lets emit-signed records carry the tool name for downstream ' +
|
|
134
134
|
'consumers (e.g. recall_my_attribution_history filtering by tool_name). Absence ' +
|
|
135
135
|
'indicates the §8.1 default posture (no disclosure).'),
|
|
136
136
|
args_hash: z
|
|
@@ -339,7 +339,7 @@ function createServerKeyResolver(options) {
|
|
|
339
339
|
* Build, sign, submit, mirror. Refused writes return `signed: false`;
|
|
340
340
|
* signed degradations stay `signed: true` and surface in `warnings`.
|
|
341
341
|
*/
|
|
342
|
-
async function handleEmit({ input, key, queue, producer, logEndpoint, recordReferenceResolver, localSubstrate, localSubstrateCommit, }) {
|
|
342
|
+
async function handleEmit({ input, key, queue, producer, logEndpoint, mirrorPath, autochainSource, recordReferenceResolver, localSubstrate, localSubstrateCommit, }) {
|
|
343
343
|
const warnings = [];
|
|
344
344
|
const eventType = normalizeEventType(input.event_type);
|
|
345
345
|
if (!isValidEventTypeUri(eventType)) {
|
|
@@ -430,10 +430,14 @@ async function handleEmit({ input, key, queue, producer, logEndpoint, recordRefe
|
|
|
430
430
|
// is the primary case that needs preserving; pre-fix this produced isolated
|
|
431
431
|
// genesis records because atrib-emit's local resolver short-circuited on
|
|
432
432
|
// caller context.
|
|
433
|
+
const autochainMirrorPath = readMirrorPath({ autochainSource, mirrorPath });
|
|
433
434
|
const chain = await inheritChainContext({
|
|
434
435
|
callerContextId,
|
|
435
436
|
callerChainRoot: input.chain_root,
|
|
436
|
-
mirrorPath:
|
|
437
|
+
mirrorPath: autochainMirrorPath,
|
|
438
|
+
...((mirrorPath !== undefined || autochainSource !== undefined) && {
|
|
439
|
+
mirrorScope: 'file',
|
|
440
|
+
}),
|
|
437
441
|
randomContextId,
|
|
438
442
|
});
|
|
439
443
|
const contextId = chain.contextId;
|
|
@@ -464,6 +468,9 @@ async function handleEmit({ input, key, queue, producer, logEndpoint, recordRefe
|
|
|
464
468
|
allowUnresolved: input.allow_unresolved_informed_by,
|
|
465
469
|
resolver: recordReferenceResolver,
|
|
466
470
|
logEndpoint,
|
|
471
|
+
...((mirrorPath !== undefined || autochainSource !== undefined) && {
|
|
472
|
+
localMirrorPaths: [resolveMirrorWritePath(mirrorPath), autochainMirrorPath],
|
|
473
|
+
}),
|
|
467
474
|
warnings,
|
|
468
475
|
});
|
|
469
476
|
const effectiveInformedBy = validParentHash
|
|
@@ -539,7 +546,7 @@ async function handleEmit({ input, key, queue, producer, logEndpoint, recordRefe
|
|
|
539
546
|
await mirrorRecord(record, getProofFor(queue, recordHash) ?? null, {
|
|
540
547
|
content: input.content,
|
|
541
548
|
producer: producer ?? 'atrib-emit',
|
|
542
|
-
});
|
|
549
|
+
}, mirrorPath);
|
|
543
550
|
// Try to read a proof if the queue submitted synchronously and the log
|
|
544
551
|
// returned one within the same tick. Most submissions return null here
|
|
545
552
|
// and the proof shows up on a later poll via getProof.
|
|
@@ -917,6 +924,8 @@ export async function emitInProcess(rawInput, options = {}) {
|
|
|
917
924
|
queue,
|
|
918
925
|
producer: options.producer,
|
|
919
926
|
logEndpoint,
|
|
927
|
+
mirrorPath: options.mirrorPath,
|
|
928
|
+
autochainSource: options.autochainSource,
|
|
920
929
|
recordReferenceResolver: options.recordReferenceResolver,
|
|
921
930
|
localSubstrate: resolveLocalSubstrateOption(options.localSubstrate, {
|
|
922
931
|
producer: options.producer ?? 'atrib-emit',
|
|
@@ -979,6 +988,8 @@ export async function handleAttest(args) {
|
|
|
979
988
|
queue: args.queue,
|
|
980
989
|
producer: args.input.producer ?? args.producer ?? 'atrib-attest',
|
|
981
990
|
logEndpoint: args.logEndpoint,
|
|
991
|
+
mirrorPath: args.mirrorPath,
|
|
992
|
+
autochainSource: args.autochainSource,
|
|
982
993
|
recordReferenceResolver: args.recordReferenceResolver,
|
|
983
994
|
localSubstrate: args.localSubstrate,
|
|
984
995
|
localSubstrateCommit: args.localSubstrateCommit,
|
|
@@ -1025,6 +1036,7 @@ export const __test_only__ = { createServerKeyResolver, handleEmit, keyResolveRe
|
|
|
1025
1036
|
//
|
|
1026
1037
|
// Stable as of @atrib/emit@0.8.0. Breaking changes here require a major bump.
|
|
1027
1038
|
export { handleEmit, EmitInput };
|
|
1039
|
+
export { resolveMirrorWritePath };
|
|
1028
1040
|
export { resolveKey } from './keys.js';
|
|
1029
1041
|
// Session-checkpoint emission (§1.2.10 / D139): reads the ordered record
|
|
1030
1042
|
// hashes for a context from the local mirror per §5.9 and emits the
|
|
@@ -4,6 +4,7 @@ interface FilterResolvableInformedByOptions {
|
|
|
4
4
|
allowUnresolved?: boolean | undefined;
|
|
5
5
|
resolver?: RecordReferenceResolver | undefined;
|
|
6
6
|
logEndpoint?: string | undefined;
|
|
7
|
+
localMirrorPaths?: readonly string[] | undefined;
|
|
7
8
|
warnings: string[];
|
|
8
9
|
}
|
|
9
10
|
export declare const defaultRecordReferenceResolver: typeof sharedDefaultRecordReferenceResolver;
|
|
@@ -8,7 +8,9 @@ export async function filterResolvableInformedBy(refs, options) {
|
|
|
8
8
|
return unique;
|
|
9
9
|
const kept = [];
|
|
10
10
|
const resolver = options.resolver ??
|
|
11
|
-
((recordHash) => defaultRecordReferenceResolver(recordHash, options.logEndpoint
|
|
11
|
+
((recordHash) => defaultRecordReferenceResolver(recordHash, options.logEndpoint, {
|
|
12
|
+
localMirrorPaths: options.localMirrorPaths,
|
|
13
|
+
}));
|
|
12
14
|
for (const ref of unique) {
|
|
13
15
|
const resolution = await resolver(ref);
|
|
14
16
|
if (resolution === 'found') {
|
package/dist/storage.d.ts
CHANGED
|
@@ -18,10 +18,11 @@ export interface MirrorLine {
|
|
|
18
18
|
/** Optional local-only sidecar; absent on legacy entries. */
|
|
19
19
|
_local?: LocalSidecar;
|
|
20
20
|
}
|
|
21
|
+
export declare function resolveMirrorWritePath(explicitPath?: string): string;
|
|
21
22
|
/**
|
|
22
23
|
* Append one record + optional proof + optional local sidecar to the
|
|
23
24
|
* mirror file. Failures log with the atrib-emit prefix and otherwise
|
|
24
25
|
* no-op, per §5.8 the mirror is best-effort and never blocks the
|
|
25
26
|
* agent.
|
|
26
27
|
*/
|
|
27
|
-
export declare function mirrorRecord(record: AtribRecord, proof: ProofBundle | null, localSidecar?: LocalSidecar): Promise<void>;
|
|
28
|
+
export declare function mirrorRecord(record: AtribRecord, proof: ProofBundle | null, localSidecar?: LocalSidecar, explicitPath?: string): Promise<void>;
|
package/dist/storage.js
CHANGED
|
@@ -30,8 +30,9 @@ import { appendFile, mkdir } from 'node:fs/promises';
|
|
|
30
30
|
import { homedir } from 'node:os';
|
|
31
31
|
import { dirname, join } from 'node:path';
|
|
32
32
|
let ensuredDirs = new Set();
|
|
33
|
-
function
|
|
34
|
-
return (
|
|
33
|
+
export function resolveMirrorWritePath(explicitPath) {
|
|
34
|
+
return (explicitPath ??
|
|
35
|
+
process.env['ATRIB_MIRROR_FILE'] ??
|
|
35
36
|
join(homedir(), '.atrib', 'records', `atrib-emit-${process.env['ATRIB_AGENT'] ?? 'claude-code'}.jsonl`));
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
@@ -40,8 +41,8 @@ function mirrorPath() {
|
|
|
40
41
|
* no-op, per §5.8 the mirror is best-effort and never blocks the
|
|
41
42
|
* agent.
|
|
42
43
|
*/
|
|
43
|
-
export async function mirrorRecord(record, proof, localSidecar) {
|
|
44
|
-
const path =
|
|
44
|
+
export async function mirrorRecord(record, proof, localSidecar, explicitPath) {
|
|
45
|
+
const path = resolveMirrorWritePath(explicitPath);
|
|
45
46
|
const line = { record, proof, written_at: Date.now() };
|
|
46
47
|
if (localSidecar) {
|
|
47
48
|
line._local = localSidecar;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atrib/attest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for atrib's write verb. One attest tool signs observations, annotations, and revisions; the legacy emit, atrib-annotate, and atrib-revise tool names stay mounted as aliases over the same handler.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"atrib",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@noble/hashes": "^2.2.0",
|
|
46
46
|
"canonicalize": "^3.0.0",
|
|
47
47
|
"zod": "^3.25.76",
|
|
48
|
-
"@atrib/mcp": "0.
|
|
48
|
+
"@atrib/mcp": "0.23.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^26.1.1",
|