@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 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, unchanged from the legacy emit tool.'),
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 (process.env['ATRIB_AUTOCHAIN_SOURCE'] ??
38
- process.env['ATRIB_MIRROR_FILE'] ??
39
- join(homedir(), '.atrib', 'records', `atrib-emit-${process.env['ATRIB_AGENT'] ?? 'claude-code'}.jsonl`));
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). Lets emit-signed records carry the tool name for downstream ' +
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: readMirrorPath(),
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 mirrorPath() {
34
- return (process.env['ATRIB_MIRROR_FILE'] ??
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 = mirrorPath();
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.2.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.22.0"
48
+ "@atrib/mcp": "0.23.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^26.1.1",