@atrib/daemon 0.1.0 → 0.2.1

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
@@ -1,34 +1,43 @@
1
1
  # atribd
2
2
 
3
- Local daemon for atrib. Serves the seven cognitive primitives from one
4
- stateless-native process over Streamable HTTP or stdio.
5
-
6
- atribd mounts the seven primitive MCP servers (`@atrib/emit`,
7
- `@atrib/annotate`, `@atrib/revise`, `@atrib/recall`, `@atrib/trace`,
8
- `@atrib/summarize`, `@atrib/verify-mcp`) in process and exposes their fifteen
9
- physical tools as thin aliases over two internal handlers (write, read). A
10
- record signed through the daemon is byte-identical to one signed through the
11
- standalone per-primitive binary: same handler code paths, same
12
- `_local.producer` sidecar labels, same `resolveChainRoot` chain selection.
13
- The daemon is the recommended local topology; the standalone binaries keep
14
- shipping and keep working.
3
+ Local daemon for atrib. Serves the two-verb cognitive surface plus the
4
+ legacy alias tools from one stateless-native process over Streamable HTTP
5
+ or stdio.
6
+
7
+ Per the attest/recall rename
8
+ ([D164](../../DECISIONS.md#d164-attestrecall-verb-rename-and-primitive-surface-collapse)),
9
+ atribd mounts three primitives in process: the `attest` write home
10
+ (`@atrib/attest`), the `recall` read home (`@atrib/recall`), and
11
+ `@atrib/summarize`. Together they serve the seventeen-tool union: the
12
+ fifteen legacy tool names plus `attest` plus `recall`. A record signed
13
+ through the daemon is byte-identical to one signed through the standalone
14
+ per-primitive binary: same handler code paths, same `_local.producer`
15
+ sidecar labels, same `resolveChainRoot` chain selection. The daemon is the
16
+ recommended local topology; the standalone binaries keep shipping and keep
17
+ working.
15
18
 
16
19
  ## Install
17
20
 
18
- This package is a publish target; first publish is pending an operator
19
- decision on the final npm name. Until then, run it from the workspace:
21
+ Published as `@atrib/daemon`; the daemon and its binary are named `atribd`:
20
22
 
21
23
  ```sh
22
- pnpm --filter atribd... build
23
- node services/atribd/dist/index.js --help
24
+ npm install -g @atrib/daemon
25
+ atribd --help
24
26
  ```
25
27
 
26
- After first publish, the command becomes:
28
+ Or run without installing:
27
29
 
28
30
  ```sh
29
31
  npx --package @atrib/daemon atribd --help
30
32
  ```
31
33
 
34
+ From the workspace (development):
35
+
36
+ ```sh
37
+ pnpm --filter @atrib/daemon... build
38
+ node services/atribd/dist/index.js --help
39
+ ```
40
+
32
41
  ## Quick start
33
42
 
34
43
  One daemon per profile ([D120](../../DECISIONS.md#d120-local-substrate-coordinator-keeps-startup-spawn-sidecars-wrapper-owned) partition axis). Start the HTTP daemon:
@@ -69,7 +78,9 @@ instance:
69
78
  resolve through the spec [§1.5.4](../../atrib-spec.md#154-mcp-transport-params_meta) ladder with the [§1.5.3](../../atrib-spec.md#153-http-fallback-x-atrib-chain) `X-Atrib-Chain`
70
79
  fallback.
71
80
  - `tools/list` responses carry `ttlMs` and `cacheScope` (SEP-2549) so clients
72
- can cache the tool catalogue. Tune with `--tools-list-ttl-ms`.
81
+ can cache the tool catalogue. The default `ttlMs` is 5 minutes during the
82
+ alias window (operator-tunable via `--tools-list-ttl-ms` /
83
+ `ATRIBD_TOOLS_LIST_TTL_MS`).
73
84
 
74
85
  The transport binding sits behind an adapter
75
86
  (`src/transport-adapter.ts`). The current adapter runs the session-era MCP
@@ -97,13 +108,14 @@ open question. The stdio surfaces keep the ambient ladder unchanged.
97
108
 
98
109
  ## Write serialization
99
110
 
100
- The daemon serializes write-primitive calls per resolved `context_id`:
101
- read-tail, sign, append runs one writer at a time per context, so concurrent
102
- writes routed through one daemon yield a linear chain. Writers that append
103
- to the mirror corpus without routing through the daemon sit outside this
104
- boundary and can still fork a chain; the
105
- `spec/conformance/atribd/cases/concurrent-writer-serialization/` family pins
106
- both sides of that line.
111
+ The daemon serializes calls to any write-union tool name (`attest`, `emit`,
112
+ `atrib-annotate`, `atrib-revise`) per resolved `context_id`: read-tail,
113
+ sign, append runs one writer at a time per context, so concurrent writes
114
+ routed through one daemon, regardless of which tool name the caller used,
115
+ yield a linear chain. Writers that append to the mirror corpus without
116
+ routing through the daemon sit outside this boundary and can still fork a
117
+ chain; the `spec/conformance/atribd/cases/concurrent-writer-serialization/`
118
+ family pins both sides of that line.
107
119
 
108
120
  ## Degradation
109
121
 
@@ -121,9 +133,8 @@ and mirror stay on the host.
121
133
  `node scripts/update-primitives-runtime.mjs --runtime atribd`. It discovers
122
134
  `com.nader.atribd.*` LaunchAgents running this package's `dist/index.js`,
123
135
  builds the dependency closure, restarts, probes health and the direct MCP
124
- surface, and gates on the daemon health shape. The topology gate reports
125
- skipped until the operator cutover, because the topology scripts still
126
- read the legacy shape.
136
+ surface, and gates on the daemon health shape. All operator profiles now
137
+ run `@atrib/daemon`; the topology gate validates the daemon shape.
127
138
  - **Deprecated session flags.** `--session-idle-ms` and
128
139
  `ATRIB_PRIMITIVES_SESSION_IDLE_MS` are accepted and ignored with a
129
140
  one-line stderr notice, never a fatal error. The stateless daemon has no
@@ -142,8 +153,8 @@ and mirror stay on the host.
142
153
  ## Verify locally
143
154
 
144
155
  ```sh
145
- pnpm --filter atribd... build
146
- pnpm --filter atribd test
156
+ pnpm --filter @atrib/daemon... build
157
+ pnpm --filter @atrib/daemon test
147
158
  ```
148
159
 
149
160
  The test suite includes the reference tests for
package/dist/backend.js CHANGED
@@ -30,27 +30,16 @@ const HEALTH_PROBE_ABSENT_CONTEXT_ID = 'f'.repeat(32);
30
30
  const CONTEXT_ID_PATTERN = /^[0-9a-f]{32}$/;
31
31
  const runtimeRequire = createRequire(import.meta.url);
32
32
  export const PRIMITIVE_SPECS = [
33
+ // The attest/recall two-verb surface with the fifteen legacy tool names
34
+ // mounted as aliases (alias-window rule W1). Three mounts serve the
35
+ // seventeen-tool union: the write home (@atrib/attest), the read home
36
+ // (@atrib/recall), and summarize (no successor; relocates to the harness
37
+ // after the window).
33
38
  {
34
- name: 'emit',
35
- packageName: '@atrib/emit',
39
+ name: 'attest',
40
+ packageName: '@atrib/attest',
36
41
  kind: 'write',
37
- expectedTools: ['emit'],
38
- mutatesLogOnCall: true,
39
- probeMode: 'package-and-tool-surface',
40
- },
41
- {
42
- name: 'annotate',
43
- packageName: '@atrib/annotate',
44
- kind: 'write',
45
- expectedTools: ['atrib-annotate'],
46
- mutatesLogOnCall: true,
47
- probeMode: 'package-and-tool-surface',
48
- },
49
- {
50
- name: 'revise',
51
- packageName: '@atrib/revise',
52
- kind: 'write',
53
- expectedTools: ['atrib-revise'],
42
+ expectedTools: ['atrib-annotate', 'atrib-revise', 'attest', 'emit'],
54
43
  mutatesLogOnCall: true,
55
44
  probeMode: 'package-and-tool-surface',
56
45
  },
@@ -59,6 +48,8 @@ export const PRIMITIVE_SPECS = [
59
48
  packageName: '@atrib/recall',
60
49
  kind: 'read',
61
50
  expectedTools: [
51
+ 'atrib-verify',
52
+ 'recall',
62
53
  'recall_annotations',
63
54
  'recall_by_content',
64
55
  'recall_by_signer',
@@ -67,18 +58,12 @@ export const PRIMITIVE_SPECS = [
67
58
  'recall_revisions',
68
59
  'recall_session_chain',
69
60
  'recall_walk',
61
+ 'trace',
62
+ 'trace_forward',
70
63
  ],
71
64
  mutatesLogOnCall: false,
72
65
  probeMode: 'read-only-behavioral-probe',
73
66
  },
74
- {
75
- name: 'trace',
76
- packageName: '@atrib/trace',
77
- kind: 'read',
78
- expectedTools: ['trace', 'trace_forward'],
79
- mutatesLogOnCall: false,
80
- probeMode: 'package-and-tool-surface',
81
- },
82
67
  {
83
68
  name: 'summarize',
84
69
  packageName: '@atrib/summarize',
@@ -87,27 +72,15 @@ export const PRIMITIVE_SPECS = [
87
72
  mutatesLogOnCall: false,
88
73
  probeMode: 'package-and-tool-surface',
89
74
  },
90
- {
91
- name: 'verify',
92
- packageName: '@atrib/verify-mcp',
93
- kind: 'read',
94
- expectedTools: ['atrib-verify'],
95
- mutatesLogOnCall: false,
96
- probeMode: 'package-and-tool-surface',
97
- },
98
75
  ];
99
76
  /** Physical tool names served by the write handler. */
100
77
  export const WRITE_TOOL_NAMES = new Set(PRIMITIVE_SPECS.filter((spec) => spec.kind === 'write').flatMap((spec) => [
101
78
  ...spec.expectedTools,
102
79
  ]));
103
80
  const PRIMITIVES = [
104
- ['emit', async () => (await import('@atrib/emit')).createAtribEmitServer()],
105
- ['annotate', async () => (await import('@atrib/annotate')).createAtribAnnotateServer()],
106
- ['revise', async () => (await import('@atrib/revise')).createAtribReviseServer()],
81
+ ['attest', async () => (await import('@atrib/attest')).createAtribAttestServer()],
107
82
  ['recall', async () => (await import('@atrib/recall')).createAtribRecallServer()],
108
- ['trace', async () => (await import('@atrib/trace')).createAtribTraceServer()],
109
83
  ['summarize', async () => (await import('@atrib/summarize')).createAtribSummarizeServer()],
110
- ['verify', async () => (await import('@atrib/verify-mcp')).createAtribVerifyServer()],
111
84
  ];
112
85
  export function readPackageVersion() {
113
86
  try {
@@ -436,6 +409,52 @@ async function probeVerifyBehavior(primitive, spec, timeoutMs) {
436
409
  missing_required_record_rejected: true,
437
410
  });
438
411
  }
412
+ async function probeRecallVerbBehavior(primitive, spec, timeoutMs) {
413
+ const payload = await callProbeTool(primitive, 'recall', {
414
+ shape: 'walk',
415
+ direction: 'backward',
416
+ start: HEALTH_PROBE_ABSENT_HASH,
417
+ depth: 0,
418
+ compact: true,
419
+ }, timeoutMs);
420
+ if (payload.start_hash !== HEALTH_PROBE_ABSENT_HASH) {
421
+ throw new Error(`recall verb returned unexpected start_hash ${String(payload.start_hash)}`);
422
+ }
423
+ if (payload.direction !== 'backward') {
424
+ throw new Error(`recall verb returned unexpected direction ${String(payload.direction)}`);
425
+ }
426
+ const dangling = assertArray(payload.dangling, 'recall.dangling');
427
+ if (!dangling.includes(HEALTH_PROBE_ABSENT_HASH)) {
428
+ throw new Error('recall verb did not surface the absent probe hash as dangling');
429
+ }
430
+ const visited = assertArray(payload.visited, 'recall.visited');
431
+ if (visited.length !== 0) {
432
+ throw new Error('recall verb visited records for an absent probe hash');
433
+ }
434
+ return behavioralProbePassed(spec, 'read-only', {
435
+ tool: 'recall',
436
+ shape: 'walk',
437
+ absent_hash_dangling: true,
438
+ });
439
+ }
440
+ /**
441
+ * The read home mounts the whole read union (recall verb + 8 recall_* +
442
+ * trace + trace_forward + atrib-verify), so its behavioral probe runs the
443
+ * per-family probes against one mount and combines them. Any single
444
+ * failure fails the union probe.
445
+ */
446
+ async function probeReadUnionBehavior(primitive, spec, timeoutMs) {
447
+ const recallProbe = await probeRecallBehavior(primitive, spec, timeoutMs);
448
+ const traceProbe = await probeTraceBehavior(primitive, spec, timeoutMs);
449
+ const verifyProbe = await probeVerifyBehavior(primitive, spec, timeoutMs);
450
+ const verbProbe = await probeRecallVerbBehavior(primitive, spec, timeoutMs);
451
+ return behavioralProbePassed(spec, 'read-only', {
452
+ recall: recallProbe.observed ?? {},
453
+ trace: traceProbe.observed ?? {},
454
+ verify: verifyProbe.observed ?? {},
455
+ recall_verb: verbProbe.observed ?? {},
456
+ });
457
+ }
439
458
  async function inspectPrimitiveBehavioralProbes(mounted, timeoutMs) {
440
459
  const byName = new Map(mounted.map((primitive) => [primitive.name, primitive]));
441
460
  const entries = [];
@@ -454,17 +473,11 @@ async function inspectPrimitiveBehavioralProbes(mounted, timeoutMs) {
454
473
  }
455
474
  try {
456
475
  if (spec.name === 'recall') {
457
- entries.push([spec.name, await probeRecallBehavior(primitive, spec, timeoutMs)]);
458
- }
459
- else if (spec.name === 'trace') {
460
- entries.push([spec.name, await probeTraceBehavior(primitive, spec, timeoutMs)]);
476
+ entries.push([spec.name, await probeReadUnionBehavior(primitive, spec, timeoutMs)]);
461
477
  }
462
478
  else if (spec.name === 'summarize') {
463
479
  entries.push([spec.name, await probeSummarizeBehavior(primitive, spec, timeoutMs)]);
464
480
  }
465
- else if (spec.name === 'verify') {
466
- entries.push([spec.name, await probeVerifyBehavior(primitive, spec, timeoutMs)]);
467
- }
468
481
  else {
469
482
  entries.push([spec.name, behavioralProbeSkipped(spec, 'no behavioral probe defined')]);
470
483
  }
@@ -575,14 +588,18 @@ export async function createAtribdBackend(options = {}) {
575
588
  let callsTimedOut = 0;
576
589
  let callsSettledAfterTimeout = 0;
577
590
  for (const primitive of mounted) {
578
- // Unknown mount names default to the read handler; only tools served by
579
- // a registered write primitive go through the write handler's lock.
580
- const kind = specByName.get(primitive.name)?.kind ?? 'read';
591
+ // Route kind is per TOOL, not per mount: any tool name the write
592
+ // handler serves (the write union: attest plus its legacy aliases)
593
+ // goes through the write lock regardless of which mount exposes it.
594
+ // Unknown tool names on unknown mounts default to the read handler,
595
+ // unless the mount is a registered write primitive.
596
+ const mountKind = specByName.get(primitive.name)?.kind ?? 'read';
581
597
  for (const tool of primitive.tools) {
582
598
  const existing = routeByTool.get(tool.name);
583
599
  if (existing) {
584
600
  throw new Error(`duplicate atrib primitive tool ${tool.name}: ${existing.primitive} and ${primitive.name}`);
585
601
  }
602
+ const kind = WRITE_TOOL_NAMES.has(tool.name) ? 'write' : mountKind;
586
603
  routeByTool.set(tool.name, { primitive: primitive.name, kind, client: primitive.client });
587
604
  tools.push(tool);
588
605
  }
package/dist/http-host.js CHANGED
@@ -34,7 +34,14 @@ import { createSessionSdkStatelessAdapter, } from './transport-adapter.js';
34
34
  export const DEFAULT_HTTP_HOST = '127.0.0.1';
35
35
  export const DEFAULT_HTTP_PORT = 8796;
36
36
  export const DEFAULT_HTTP_PATH = '/mcp';
37
- export const DEFAULT_TOOLS_LIST_TTL_MS = 24 * 60 * 60 * 1000;
37
+ // Alias-window rule W2 (attest/recall rename): while legacy tool names are
38
+ // still being retired, every default deployment advertises a short tools/list
39
+ // ttlMs so a rename propagates on cache expiry within minutes, and a name may
40
+ // leave the default list only after the longest ttlMs the deployment ever
41
+ // advertised has elapsed. Operators tune via --tools-list-ttl-ms /
42
+ // ATRIBD_TOOLS_LIST_TTL_MS; the pre-window default was 24 hours and can
43
+ // return once retirement completes.
44
+ export const DEFAULT_TOOLS_LIST_TTL_MS = 5 * 60 * 1000;
38
45
  const MAX_JSON_BODY_BYTES = 1024 * 1024;
39
46
  function createBackendProvider(factory) {
40
47
  const startedAt = Date.now();
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  /**
4
- * atribd: the local daemon for atrib's seven cognitive primitives.
4
+ * atribd: the local daemon for atrib's cognitive surface.
5
5
  *
6
- * One host-owned process mounts the seven primitive MCP servers in process
7
- * and serves their fifteen physical tools as thin aliases over two internal
8
- * handlers (write, read). Transports: stateless Streamable HTTP (the
6
+ * One host-owned process mounts the attest write home, the recall read
7
+ * home, and summarize in process and serves the seventeen-tool alias-window
8
+ * union (fifteen legacy names plus the attest and recall verbs) as thin
9
+ * aliases over two internal handlers (write, read). Transports: stateless Streamable HTTP (the
9
10
  * recommended daemon topology), direct stdio (in-process), and a
10
11
  * stdio-to-HTTP proxy shim for startup-spawn harnesses.
11
12
  *
@@ -170,7 +171,7 @@ Options:
170
171
  --port <port> HTTP bind port. Defaults to 8796. Use 0 for ephemeral.
171
172
  --path <path> HTTP MCP path. Defaults to /mcp.
172
173
  --tool-timeout-ms <ms> Bound each primitive tool call. Defaults to 45000.
173
- --tools-list-ttl-ms <ms> SEP-2549 freshness hint on tools/list. Defaults to 24 hours.
174
+ --tools-list-ttl-ms <ms> SEP-2549 freshness hint on tools/list. Defaults to 5 minutes (alias-window W2).
174
175
  --ambient-context Opt the HTTP surface back into ambient context discovery
175
176
  (D078/D083). Default is explicit-required.
176
177
  --session-idle-ms <ms> Deprecated; ignored (the stateless daemon has no sessions).
package/package.json CHANGED
@@ -1,7 +1,18 @@
1
1
  {
2
2
  "name": "@atrib/daemon",
3
- "version": "0.1.0",
4
- "description": "Local daemon for atrib. Serves the seven cognitive primitives from one stateless-native process over Streamable HTTP or stdio.",
3
+ "version": "0.2.1",
4
+ "description": "Local daemon for atrib. Serves the attest/recall verbs plus the legacy alias tools from one stateless-native process over Streamable HTTP or stdio.",
5
+ "keywords": [
6
+ "atrib",
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "attribution",
10
+ "verifiable",
11
+ "agent",
12
+ "cognitive-primitive",
13
+ "daemon",
14
+ "ed25519"
15
+ ],
5
16
  "author": "Nader Helmy",
6
17
  "license": "Apache-2.0",
7
18
  "homepage": "https://github.com/creatornader/atrib/tree/main/services/atribd",
@@ -17,7 +28,8 @@
17
28
  ".": {
18
29
  "types": "./dist/index.d.ts",
19
30
  "import": "./dist/index.js"
20
- }
31
+ },
32
+ "./package.json": "./package.json"
21
33
  },
22
34
  "bin": {
23
35
  "atribd": "./dist/index.js"
@@ -27,14 +39,11 @@
27
39
  },
28
40
  "dependencies": {
29
41
  "@modelcontextprotocol/sdk": "^1.29.0",
42
+ "@atrib/attest": "0.1.0",
30
43
  "@atrib/mcp": "0.21.0",
31
- "@atrib/emit": "0.17.3",
32
- "@atrib/annotate": "0.2.41",
33
- "@atrib/recall": "0.14.7",
44
+ "@atrib/recall": "2.0.0",
34
45
  "@atrib/summarize": "0.4.23",
35
- "@atrib/trace": "0.5.21",
36
- "@atrib/revise": "0.2.41",
37
- "@atrib/verify-mcp": "0.2.22"
46
+ "@atrib/verify": "0.10.0"
38
47
  },
39
48
  "devDependencies": {
40
49
  "@types/node": "^25.9.3",