@atrib/sdk 0.1.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.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Client configuration for the consolidated atrib SDK.
3
+ *
4
+ * Topology per the SDK session brief: the local primitives runtime (the
5
+ * daemon) is the default peer; in-process signing via `@atrib/emit` is the
6
+ * fallback. Anchors are the D138 anchor-plurality surface from `@atrib/mcp`
7
+ * (spec §2.11.7-§2.11.13): the config takes an anchor SET which is
8
+ * normalized into an `AnchorSetConfig` and fanned out through
9
+ * `createAnchorFanout` on the in-process attest path. When no anchors are
10
+ * configured, the `BUILT_IN_DEFAULT_ANCHOR_SET` (two independent anchors)
11
+ * applies.
12
+ */
13
+ import { type AnchorDescriptor, type AnchorSetConfig } from '@atrib/mcp';
14
+ import type { ResolvedKey } from '@atrib/emit';
15
+ export type DaemonMode = 'prefer' | 'require' | 'off';
16
+ export interface DaemonConfig {
17
+ /**
18
+ * MCP Streamable HTTP endpoint of the local primitives runtime.
19
+ * Default: $ATRIB_PRIMITIVES_HTTP_ENDPOINT, then http://127.0.0.1:8796/mcp.
20
+ */
21
+ endpoint?: string;
22
+ /**
23
+ * 'prefer' (default): try the daemon, fall back in-process.
24
+ * 'require': daemon only; operational failure degrades to a warning
25
+ * result (never a throw) per §5.8.
26
+ * 'off': in-process only.
27
+ */
28
+ mode?: DaemonMode;
29
+ /** Connect/call timeout in ms. Default 1500 (connect) / 10000 (call). */
30
+ connectTimeoutMs?: number;
31
+ callTimeoutMs?: number;
32
+ /** Cooldown before re-probing an unreachable daemon. Default 30000. */
33
+ retryCooldownMs?: number;
34
+ }
35
+ /**
36
+ * One anchor in the anchor set (D138, spec §2.11.12). A bare string is an
37
+ * atrib-log §2.6.1 endpoint (normalized to `{ url }`). The object form is
38
+ * `AnchorDescriptor` from `@atrib/mcp`: `anchor_type` absent means
39
+ * `'atrib-log'`; `url` wins over `endpoint` when both are set; the
40
+ * registered types are `ANCHOR_TYPES` (§2.11.8 v1 registry).
41
+ */
42
+ export type AnchorSpec = string | AnchorDescriptor;
43
+ export interface AtribClientConfig {
44
+ daemon?: DaemonConfig;
45
+ /**
46
+ * Anchor set (D138, §2.11.12). When omitted, the two-anchor
47
+ * `BUILT_IN_DEFAULT_ANCHOR_SET` from `@atrib/mcp` applies and the emit
48
+ * pipeline keeps its own env/default atrib-log endpoint. Hostile or
49
+ * malformed entries warn-and-skip, never error (§5.8).
50
+ */
51
+ anchors?: AnchorSpec[];
52
+ /**
53
+ * Opt-in acknowledgment that a sub-plurality (< 2) anchor set is
54
+ * deliberate (§2.11.12 rule 3) — the anchor analog of
55
+ * `allow_unresolved_informed_by` (D113). Maps to
56
+ * `AnchorSetConfig.allow_single_anchor`.
57
+ */
58
+ allowSingleAnchor?: boolean;
59
+ /**
60
+ * Opt-in parsing of `dev.atrib/attribution` attestation receipts from
61
+ * daemon tool results' `_meta` (D141). Default false. Receipts are
62
+ * advisory; trust still derives from verifying signed records. Parsed
63
+ * blocks are additionally run through `verifyAttributionReceipt`.
64
+ */
65
+ attributionReceipts?: boolean;
66
+ /**
67
+ * Pre-resolved signing key for the in-process path. Default: the
68
+ * `@atrib/emit` `resolveKey()` ladder (ATRIB_PRIVATE_KEY env, key file,
69
+ * Keychain, 1Password). `null` disables in-process signing (pass-through
70
+ * per §5.8 rule 5).
71
+ */
72
+ key?: ResolvedKey | null;
73
+ /**
74
+ * Explicit context_id (32 lowercase hex). Stateless-MCP-native posture:
75
+ * context identity is an explicit per-request value; this is only the
76
+ * per-client default. Default: resolveEnvContextId() at call time.
77
+ */
78
+ contextId?: string;
79
+ /** `_local.producer` mirror sidecar label. Default 'atrib-sdk'. */
80
+ producer?: string;
81
+ }
82
+ export declare const DEFAULT_DAEMON_ENDPOINT = "http://127.0.0.1:8796/mcp";
83
+ export declare const DEFAULT_CONNECT_TIMEOUT_MS = 1500;
84
+ export declare const DEFAULT_CALL_TIMEOUT_MS = 10000;
85
+ export declare const DEFAULT_RETRY_COOLDOWN_MS = 30000;
86
+ export declare const DEFAULT_PRODUCER = "atrib-sdk";
87
+ export declare function resolveDaemonEndpoint(config?: DaemonConfig): string;
88
+ export interface ResolvedAnchorSet {
89
+ /**
90
+ * Canonical §2.11.12 anchor-set config for `createAnchorFanout`. `{}`
91
+ * (no `anchors` key) when the caller configured nothing, so the
92
+ * built-in default set applies downstream.
93
+ */
94
+ config: AnchorSetConfig;
95
+ /**
96
+ * First effective `'atrib-log'` descriptor's `url ?? endpoint`, passed
97
+ * to the emit pipeline as its §2.6.1 log endpoint. Undefined when the
98
+ * config is empty (the built-in default set applies and emitInProcess
99
+ * keeps its own env/default endpoint).
100
+ */
101
+ primaryLogEndpoint: string | undefined;
102
+ warnings: string[];
103
+ }
104
+ /**
105
+ * Normalize the caller's anchor set into a D138 `AnchorSetConfig`
106
+ * (§2.11.12). Hostile/malformed entries — null, non-object/non-string,
107
+ * missing string `url`/`endpoint`, unparsable URL, unregistered
108
+ * `anchor_type` — warn-and-skip, never throw (§5.8). Plurality posture
109
+ * (warn on < 2 anchors without `allow_single_anchor`) is resolved by the
110
+ * fan-out via `resolveAnchorPosture`, not here.
111
+ */
112
+ export declare function resolveAnchorSet(anchors: AnchorSpec[] | undefined, allowSingleAnchor?: boolean): ResolvedAnchorSet;
113
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAgB,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAmB,MAAM,YAAY,CAAA;AACvG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAA;AAErD,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,gBAAgB,CAAA;AAElD,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,GAAG,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,eAAO,MAAM,uBAAuB,8BAA8B,CAAA;AAClE,eAAO,MAAM,0BAA0B,OAAO,CAAA;AAC9C,eAAO,MAAM,uBAAuB,QAAS,CAAA;AAC7C,eAAO,MAAM,yBAAyB,QAAS,CAAA;AAC/C,eAAO,MAAM,gBAAgB,cAAc,CAAA;AAI3C,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAMnE;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,EAAE,eAAe,CAAA;IACvB;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,UAAU,EAAE,GAAG,SAAS,EACjC,iBAAiB,CAAC,EAAE,OAAO,GAC1B,iBAAiB,CAuDnB"}
package/dist/config.js ADDED
@@ -0,0 +1,91 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Client configuration for the consolidated atrib SDK.
4
+ *
5
+ * Topology per the SDK session brief: the local primitives runtime (the
6
+ * daemon) is the default peer; in-process signing via `@atrib/emit` is the
7
+ * fallback. Anchors are the D138 anchor-plurality surface from `@atrib/mcp`
8
+ * (spec §2.11.7-§2.11.13): the config takes an anchor SET which is
9
+ * normalized into an `AnchorSetConfig` and fanned out through
10
+ * `createAnchorFanout` on the in-process attest path. When no anchors are
11
+ * configured, the `BUILT_IN_DEFAULT_ANCHOR_SET` (two independent anchors)
12
+ * applies.
13
+ */
14
+ import { ANCHOR_TYPES } from '@atrib/mcp';
15
+ export const DEFAULT_DAEMON_ENDPOINT = 'http://127.0.0.1:8796/mcp';
16
+ export const DEFAULT_CONNECT_TIMEOUT_MS = 1500;
17
+ export const DEFAULT_CALL_TIMEOUT_MS = 10_000;
18
+ export const DEFAULT_RETRY_COOLDOWN_MS = 30_000;
19
+ export const DEFAULT_PRODUCER = 'atrib-sdk';
20
+ const ANCHOR_TYPE_SET = new Set(ANCHOR_TYPES);
21
+ export function resolveDaemonEndpoint(config) {
22
+ return (config?.endpoint ??
23
+ process.env['ATRIB_PRIMITIVES_HTTP_ENDPOINT'] ??
24
+ DEFAULT_DAEMON_ENDPOINT);
25
+ }
26
+ /**
27
+ * Normalize the caller's anchor set into a D138 `AnchorSetConfig`
28
+ * (§2.11.12). Hostile/malformed entries — null, non-object/non-string,
29
+ * missing string `url`/`endpoint`, unparsable URL, unregistered
30
+ * `anchor_type` — warn-and-skip, never throw (§5.8). Plurality posture
31
+ * (warn on < 2 anchors without `allow_single_anchor`) is resolved by the
32
+ * fan-out via `resolveAnchorPosture`, not here.
33
+ */
34
+ export function resolveAnchorSet(anchors, allowSingleAnchor) {
35
+ const warnings = [];
36
+ if (anchors === undefined) {
37
+ // No anchor config at all: the BUILT_IN_DEFAULT_ANCHOR_SET applies
38
+ // (§2.11.12 rule 1) and the emit pipeline keeps its own env/default
39
+ // atrib-log endpoint.
40
+ return { config: {}, primaryLogEndpoint: undefined, warnings };
41
+ }
42
+ const descriptors = [];
43
+ let primaryLogEndpoint;
44
+ for (const spec of anchors) {
45
+ // Hostile/malformed entries warn-and-skip, never throw (§5.8).
46
+ let descriptor;
47
+ if (typeof spec === 'string') {
48
+ descriptor = { url: spec };
49
+ }
50
+ else if (typeof spec === 'object' && spec !== null && !Array.isArray(spec)) {
51
+ descriptor = spec;
52
+ }
53
+ else {
54
+ warnings.push(`atrib: anchor entry ${String(spec)} is not a string or object; skipping`);
55
+ continue;
56
+ }
57
+ const anchorType = descriptor.anchor_type;
58
+ if (anchorType !== undefined && (typeof anchorType !== 'string' || !ANCHOR_TYPE_SET.has(anchorType))) {
59
+ const named = (descriptor.url ?? descriptor.endpoint);
60
+ warnings.push(`atrib: anchor_type '${String(anchorType)}'${typeof named === 'string' ? ` (${named})` : ''} is not in the §2.11.8 registry (${ANCHOR_TYPES.join(', ')}); skipping this anchor`);
61
+ continue;
62
+ }
63
+ const effectiveType = anchorType ?? 'atrib-log';
64
+ // `url` wins over `endpoint` when both are set (the §2.11.12 sample
65
+ // config spells the field `url`; both spellings are accepted).
66
+ const endpoint = descriptor.url ?? descriptor.endpoint;
67
+ if (effectiveType === 'atrib-log' || endpoint !== undefined) {
68
+ if (typeof endpoint !== 'string') {
69
+ warnings.push('atrib: anchor entry without a string url/endpoint; skipping');
70
+ continue;
71
+ }
72
+ try {
73
+ new URL(endpoint);
74
+ }
75
+ catch {
76
+ warnings.push(`atrib: anchor endpoint '${endpoint}' is not a valid URL; skipping`);
77
+ continue;
78
+ }
79
+ if (effectiveType === 'atrib-log' && primaryLogEndpoint === undefined) {
80
+ primaryLogEndpoint = endpoint;
81
+ }
82
+ }
83
+ descriptors.push(descriptor);
84
+ }
85
+ const config = {
86
+ anchors: descriptors,
87
+ ...(allowSingleAnchor === true ? { allow_single_anchor: true } : {}),
88
+ };
89
+ return { config, primaryLogEndpoint, warnings };
90
+ }
91
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { type VerifiedAttributionReceipt } from './attribution.js';
2
+ import { type DaemonConfig } from './config.js';
3
+ export type DaemonCallOutcome = {
4
+ ok: true;
5
+ value: unknown;
6
+ attribution?: VerifiedAttributionReceipt;
7
+ } | {
8
+ ok: false;
9
+ reason: string;
10
+ };
11
+ export declare class DaemonClient {
12
+ private readonly endpoint;
13
+ private readonly connectTimeoutMs;
14
+ private readonly callTimeoutMs;
15
+ private readonly retryCooldownMs;
16
+ private readonly parseReceipts;
17
+ private client;
18
+ private connecting;
19
+ private lastFailureAt;
20
+ constructor(config?: DaemonConfig, options?: {
21
+ attributionReceipts?: boolean;
22
+ });
23
+ /**
24
+ * Call one MCP tool on the daemon. Tool results carrying a single JSON
25
+ * text block (the atrib primitive convention) are parsed; other shapes
26
+ * are returned raw.
27
+ */
28
+ callTool(name: string, args: Record<string, unknown>): Promise<DaemonCallOutcome>;
29
+ close(): Promise<void>;
30
+ private ensureClient;
31
+ }
32
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAqBA,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,aAAa,CAAA;AAEpB,MAAM,MAAM,iBAAiB,GACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,0BAA0B,CAAA;CAAE,GACtE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAyCjC,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,UAAU,CAAsC;IACxD,OAAO,CAAC,aAAa,CAAI;gBAEb,MAAM,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAE;IAQ9E;;;;OAIG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0CjF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAYd,YAAY;CAuC3B"}
package/dist/daemon.js ADDED
@@ -0,0 +1,175 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Daemon transport: MCP Streamable HTTP client for the local primitives
4
+ * runtime.
5
+ *
6
+ * Semantically stateless per the SDK brief: nothing session-scoped carries
7
+ * meaning — context_id and chain tokens travel as explicit tool arguments
8
+ * on every call. The MCP protocol session (initialize handshake +
9
+ * Mcp-Session-Id) is a transport detail of the CURRENT runtime that the
10
+ * official client manages; when the post-2026-07-28 stateless transport
11
+ * ships, this class swaps transports without changing the SDK surface.
12
+ *
13
+ * Degradation (§5.8): every operational failure is caught, logged with the
14
+ * `atrib:` prefix, and reported as an unavailable outcome — never thrown.
15
+ */
16
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
17
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
18
+ import { verifyAttributionReceipt } from '@atrib/mcp';
19
+ import { ATTRIBUTION_EXTENSION_KEY, parseAttributionReceiptBlock, } from './attribution.js';
20
+ import { DEFAULT_CALL_TIMEOUT_MS, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_RETRY_COOLDOWN_MS, resolveDaemonEndpoint, } from './config.js';
21
+ /**
22
+ * Parse + verify the `dev.atrib/attribution` block on a tool result's
23
+ * `_meta`. The lenient parser extracts the block; `verifyAttributionReceipt`
24
+ * runs over the RAW block (extension spec §6.2). §5.8-safe: any exception
25
+ * degrades to a `malformed` verification, never a throw.
26
+ */
27
+ function extractAttribution(meta) {
28
+ const block = parseAttributionReceiptBlock(meta);
29
+ if (block === null)
30
+ return null;
31
+ let verification;
32
+ try {
33
+ const raw = typeof meta === 'object' && meta !== null
34
+ ? meta[ATTRIBUTION_EXTENSION_KEY]
35
+ : undefined;
36
+ verification = verifyAttributionReceipt(raw);
37
+ }
38
+ catch (error) {
39
+ console.warn(`atrib: attribution receipt verification failed: ${String(error)}`);
40
+ verification = { valid: false, mismatched: ['malformed'] };
41
+ }
42
+ return { block, verification };
43
+ }
44
+ const SDK_CLIENT_INFO = { name: 'atrib-sdk', version: '0.1.0' };
45
+ async function withTimeout(promise, ms, label) {
46
+ let timer;
47
+ try {
48
+ return await Promise.race([
49
+ promise,
50
+ new Promise((_, reject) => {
51
+ timer = setTimeout(() => reject(new Error(`atrib: ${label} timed out after ${ms}ms`)), ms);
52
+ }),
53
+ ]);
54
+ }
55
+ finally {
56
+ if (timer !== undefined)
57
+ clearTimeout(timer);
58
+ }
59
+ }
60
+ export class DaemonClient {
61
+ endpoint;
62
+ connectTimeoutMs;
63
+ callTimeoutMs;
64
+ retryCooldownMs;
65
+ parseReceipts;
66
+ client = null;
67
+ connecting = null;
68
+ lastFailureAt = 0;
69
+ constructor(config, options) {
70
+ this.endpoint = resolveDaemonEndpoint(config);
71
+ this.connectTimeoutMs = config?.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
72
+ this.callTimeoutMs = config?.callTimeoutMs ?? DEFAULT_CALL_TIMEOUT_MS;
73
+ this.retryCooldownMs = config?.retryCooldownMs ?? DEFAULT_RETRY_COOLDOWN_MS;
74
+ this.parseReceipts = options?.attributionReceipts === true;
75
+ }
76
+ /**
77
+ * Call one MCP tool on the daemon. Tool results carrying a single JSON
78
+ * text block (the atrib primitive convention) are parsed; other shapes
79
+ * are returned raw.
80
+ */
81
+ async callTool(name, args) {
82
+ const client = await this.ensureClient();
83
+ if (!client) {
84
+ return { ok: false, reason: `daemon unreachable at ${this.endpoint}` };
85
+ }
86
+ try {
87
+ const result = await withTimeout(client.callTool({ name, arguments: args }), this.callTimeoutMs, `tools/call ${name}`);
88
+ const content = result.content;
89
+ const isError = result.isError === true;
90
+ const text = Array.isArray(content) && content[0]?.type === 'text' ? content[0].text : undefined;
91
+ if (isError) {
92
+ return { ok: false, reason: `daemon tool ${name} errored: ${text ?? 'unknown error'}` };
93
+ }
94
+ const attribution = this.parseReceipts
95
+ ? extractAttribution(result._meta)
96
+ : null;
97
+ const withAttribution = (value) => attribution !== null ? { ok: true, value, attribution } : { ok: true, value };
98
+ if (text === undefined) {
99
+ return withAttribution(result);
100
+ }
101
+ try {
102
+ return withAttribution(JSON.parse(text));
103
+ }
104
+ catch {
105
+ return withAttribution(text);
106
+ }
107
+ }
108
+ catch (error) {
109
+ // A failed call may mean the transport session died; drop the client
110
+ // so the next call reconnects (after cooldown).
111
+ await this.close();
112
+ this.lastFailureAt = Date.now();
113
+ const reason = error instanceof Error ? error.message : String(error);
114
+ console.warn(`atrib: daemon call ${name} failed: ${reason}`);
115
+ return { ok: false, reason };
116
+ }
117
+ }
118
+ async close() {
119
+ const client = this.client;
120
+ this.client = null;
121
+ if (client) {
122
+ try {
123
+ await client.close();
124
+ }
125
+ catch {
126
+ // Best-effort close per §5.8.
127
+ }
128
+ }
129
+ }
130
+ async ensureClient() {
131
+ if (this.client)
132
+ return this.client;
133
+ if (this.connecting)
134
+ return this.connecting;
135
+ if (this.lastFailureAt > 0 && Date.now() - this.lastFailureAt < this.retryCooldownMs) {
136
+ return null;
137
+ }
138
+ this.connecting = (async () => {
139
+ let client = null;
140
+ try {
141
+ // URL parsing stays INSIDE the try: a garbage endpoint is an
142
+ // operational failure that must degrade (§5.8), never throw.
143
+ const url = new URL(this.endpoint);
144
+ // The SDK's concrete transport declares `sessionId: string | undefined`
145
+ // while the Transport interface under exactOptionalPropertyTypes wants
146
+ // an optional property; the runtime shapes are identical.
147
+ const transport = new StreamableHTTPClientTransport(url);
148
+ client = new Client(SDK_CLIENT_INFO);
149
+ await withTimeout(client.connect(transport), this.connectTimeoutMs, 'daemon connect');
150
+ this.client = client;
151
+ this.lastFailureAt = 0;
152
+ return client;
153
+ }
154
+ catch (error) {
155
+ this.lastFailureAt = Date.now();
156
+ if (client) {
157
+ try {
158
+ await client.close();
159
+ }
160
+ catch {
161
+ // Ignore close failures on a connection that never established.
162
+ }
163
+ }
164
+ const reason = error instanceof Error ? error.message : String(error);
165
+ console.warn(`atrib: daemon connect failed (${this.endpoint}): ${reason}`);
166
+ return null;
167
+ }
168
+ finally {
169
+ this.connecting = null;
170
+ }
171
+ })();
172
+ return this.connecting;
173
+ }
174
+ }
175
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Evidence envelope production and validation (D137, spec §5.5.7).
3
+ *
4
+ * The real envelope engine lives in `@atrib/verify`, which is an OPTIONAL
5
+ * peer dependency of the SDK (same pattern as `@atrib/recall` /
6
+ * `@atrib/verify-mcp` in client.ts): the peer module is loaded lazily and
7
+ * its absence degrades to a `null` envelope with a warning naming the
8
+ * package — never a throw (§5.8). The structural envelope TYPES stay in
9
+ * ./evidence.ts so the emitted .d.ts never references the optional peer.
10
+ *
11
+ * Throw-vs-degrade split (the SDK-wide contract): contradictory INPUT
12
+ * (both `hash` and `material`; `hash_rule` without `material`; neither
13
+ * commitment source) throws TypeError — programmer error, thrown before
14
+ * the peer is consulted. Everything operational (peer missing, envelope
15
+ * failing §5.5.7 validation) degrades into the returned `warnings`.
16
+ */
17
+ import type { EvidenceConstraint, EvidenceEnvelope, EvidencePayloadRef, EvidenceTier } from './evidence.js';
18
+ interface VerifyEnvelopeModule {
19
+ validateEnvelope: (envelope: unknown) => {
20
+ valid: boolean;
21
+ reasons: string[];
22
+ };
23
+ jcsSha256: (value: unknown) => string;
24
+ rawSha256: (text: string) => string;
25
+ }
26
+ /** Test seam: swap the peer loader. Pass undefined to restore the default. */
27
+ export declare function __setVerifyEnvelopeLoaderForTests(loader: (() => Promise<VerifyEnvelopeModule | null>) | undefined): void;
28
+ /** Payload input for {@link buildEvidenceEnvelope}. */
29
+ export interface BuildEvidenceEnvelopePayloadInput {
30
+ media_type?: string;
31
+ /** Retrievability ref. Default: `{ kind: 'inline' }` when `inline` is set, else `{ kind: 'withheld' }`. */
32
+ ref?: EvidencePayloadRef;
33
+ /** Raw payload carried inline (local-only, never public). */
34
+ inline?: unknown;
35
+ /** Pre-computed `sha256:<64-hex>` commitment. Mutually exclusive with `material`. */
36
+ hash?: string;
37
+ /** Evidence material to hash with `hash_rule`. Mutually exclusive with `hash`. */
38
+ material?: unknown;
39
+ /** §5.5.7 hash rule: 'jcs' (JSON media types, default) or 'raw' (UTF-8 text). */
40
+ hash_rule?: 'jcs' | 'raw';
41
+ }
42
+ export interface BuildEvidenceEnvelopeInput {
43
+ /** Absolute HTTPS profile type URI. */
44
+ profile: string;
45
+ /** Semver of the profile document. */
46
+ profile_version: string;
47
+ tier: EvidenceTier;
48
+ payload: BuildEvidenceEnvelopePayloadInput;
49
+ facts?: Record<string, unknown>;
50
+ result?: {
51
+ valid?: boolean;
52
+ constraints?: EvidenceConstraint[];
53
+ errors?: string[];
54
+ warnings?: string[];
55
+ };
56
+ verifier?: {
57
+ name?: string;
58
+ version?: string;
59
+ checked_at_ms?: number;
60
+ };
61
+ }
62
+ export interface BuildEvidenceEnvelopeResult {
63
+ /** The validated envelope, or null when degraded (see warnings). */
64
+ envelope: EvidenceEnvelope | null;
65
+ /** The peer's `validateEnvelope` outcome, or null when the peer is missing. */
66
+ validation: unknown | null;
67
+ warnings: string[];
68
+ }
69
+ /**
70
+ * Build a §5.5.7 evidence envelope and validate it through the optional
71
+ * `@atrib/verify` peer.
72
+ *
73
+ * - `payload.hash` absent: the commitment is computed from
74
+ * `payload.material` via the stated `hash_rule` ('jcs' default; 'raw'
75
+ * requires string material) using the peer's `jcsSha256`/`rawSha256` —
76
+ * the same functions the conformance corpus pins.
77
+ * - Contradictory input throws TypeError before the peer loads: both
78
+ * `hash` and `material`, `hash_rule` without `material`, or neither
79
+ * `hash` nor `material` (no commitment source).
80
+ * - A structurally invalid RESULT (per the peer's `validateEnvelope`)
81
+ * yields `envelope: null` with the reject reasons in `warnings`.
82
+ * - Peer missing yields `envelope: null` plus a warning naming
83
+ * '@atrib/verify' (§5.8 degrade, never a throw).
84
+ */
85
+ export declare function buildEvidenceEnvelope(input: BuildEvidenceEnvelopeInput): Promise<BuildEvidenceEnvelopeResult>;
86
+ export interface ValidateEvidenceEnvelopeResult {
87
+ /** The peer's `validateEnvelope` outcome, or null when the peer is missing. */
88
+ validation: unknown | null;
89
+ warnings: string[];
90
+ }
91
+ /**
92
+ * Validate an envelope against the normative §5.5.7 shape rules via the
93
+ * optional `@atrib/verify` peer. Peer missing degrades to
94
+ * `validation: null` with a warning (§5.8); never throws.
95
+ */
96
+ export declare function validateEvidenceEnvelope(envelope: unknown): Promise<ValidateEvidenceEnvelopeResult>;
97
+ export {};
98
+ //# sourceMappingURL=evidence-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-envelope.d.ts","sourceRoot":"","sources":["../src/evidence-envelope.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACb,MAAM,eAAe,CAAA;AAItB,UAAU,oBAAoB;IAC5B,gBAAgB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IAC9E,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IACrC,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;CACpC;AAWD,8EAA8E;AAC9E,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,CAAC,MAAM,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC,GAAG,SAAS,GAC/D,IAAI,CAEN;AAKD,uDAAuD;AACvD,MAAM,WAAW,iCAAiC;IAChD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2GAA2G;IAC3G,GAAG,CAAC,EAAE,kBAAkB,CAAA;IACxB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,iFAAiF;IACjF,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAA;CAC1B;AAED,MAAM,WAAW,0BAA0B;IACzC,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAA;IACf,sCAAsC;IACtC,eAAe,EAAE,MAAM,CAAA;IACvB,IAAI,EAAE,YAAY,CAAA;IAClB,OAAO,EAAE,iCAAiC,CAAA;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAA;QAClC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;QACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KACpB,CAAA;IACD,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;CACF;AAED,MAAM,WAAW,2BAA2B;IAC1C,oEAAoE;IACpE,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACjC,+EAA+E;IAC/E,UAAU,EAAE,OAAO,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC,CAwEtC;AAED,MAAM,WAAW,8BAA8B;IAC7C,+EAA+E;IAC/E,UAAU,EAAE,OAAO,GAAG,IAAI,CAAA;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,8BAA8B,CAAC,CAazC"}
@@ -0,0 +1,108 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ let verifyEnvelopeModulePromise = null;
3
+ function loadVerifyEnvelopeModule() {
4
+ verifyEnvelopeModulePromise ??= import('@atrib/verify').then((mod) => mod, () => null);
5
+ return verifyEnvelopeModulePromise;
6
+ }
7
+ /** Test seam: swap the peer loader. Pass undefined to restore the default. */
8
+ export function __setVerifyEnvelopeLoaderForTests(loader) {
9
+ verifyEnvelopeModulePromise = loader ? loader() : null;
10
+ }
11
+ const PEER_MISSING_WARNING = "atrib: evidence-envelope support unavailable — install the optional peer '@atrib/verify'";
12
+ /**
13
+ * Build a §5.5.7 evidence envelope and validate it through the optional
14
+ * `@atrib/verify` peer.
15
+ *
16
+ * - `payload.hash` absent: the commitment is computed from
17
+ * `payload.material` via the stated `hash_rule` ('jcs' default; 'raw'
18
+ * requires string material) using the peer's `jcsSha256`/`rawSha256` —
19
+ * the same functions the conformance corpus pins.
20
+ * - Contradictory input throws TypeError before the peer loads: both
21
+ * `hash` and `material`, `hash_rule` without `material`, or neither
22
+ * `hash` nor `material` (no commitment source).
23
+ * - A structurally invalid RESULT (per the peer's `validateEnvelope`)
24
+ * yields `envelope: null` with the reject reasons in `warnings`.
25
+ * - Peer missing yields `envelope: null` plus a warning naming
26
+ * '@atrib/verify' (§5.8 degrade, never a throw).
27
+ */
28
+ export async function buildEvidenceEnvelope(input) {
29
+ const payload = input.payload;
30
+ if (payload.hash !== undefined && payload.material !== undefined) {
31
+ throw new TypeError('atrib: buildEvidenceEnvelope payload carries both hash and material; provide exactly one commitment source');
32
+ }
33
+ if (payload.hash_rule !== undefined && payload.material === undefined) {
34
+ throw new TypeError('atrib: buildEvidenceEnvelope payload has hash_rule without material; hash_rule only applies to material');
35
+ }
36
+ if (payload.hash === undefined && payload.material === undefined) {
37
+ throw new TypeError('atrib: buildEvidenceEnvelope payload needs a hash or material to commit to');
38
+ }
39
+ const hashRule = payload.hash_rule ?? 'jcs';
40
+ if (hashRule === 'raw' && payload.material !== undefined && typeof payload.material !== 'string') {
41
+ throw new TypeError("atrib: buildEvidenceEnvelope hash_rule 'raw' requires string material (UTF-8 text)");
42
+ }
43
+ const warnings = [];
44
+ const mod = await loadVerifyEnvelopeModule();
45
+ if (mod === null) {
46
+ return { envelope: null, validation: null, warnings: [PEER_MISSING_WARNING] };
47
+ }
48
+ try {
49
+ const hash = payload.hash ??
50
+ (hashRule === 'raw'
51
+ ? mod.rawSha256(payload.material)
52
+ : mod.jcsSha256(payload.material));
53
+ const ref = payload.ref ?? (payload.inline !== undefined ? { kind: 'inline' } : { kind: 'withheld' });
54
+ const candidate = {
55
+ envelope: 1,
56
+ profile: input.profile,
57
+ profile_version: input.profile_version,
58
+ tier: input.tier,
59
+ payload: {
60
+ hash,
61
+ ...(payload.media_type !== undefined ? { media_type: payload.media_type } : {}),
62
+ ref,
63
+ ...(payload.inline !== undefined ? { inline: payload.inline } : {}),
64
+ },
65
+ ...(input.facts !== undefined ? { facts: input.facts } : {}),
66
+ result: {
67
+ valid: input.result?.valid ?? true,
68
+ constraints: input.result?.constraints ?? [],
69
+ errors: input.result?.errors ?? [],
70
+ warnings: input.result?.warnings ?? [],
71
+ },
72
+ ...(input.verifier !== undefined ? { verifier: input.verifier } : {}),
73
+ };
74
+ const validation = mod.validateEnvelope(candidate);
75
+ if (!validation.valid) {
76
+ warnings.push(`atrib: built envelope failed §5.5.7 validation (${validation.reasons.join(', ')}); dropping the envelope`);
77
+ return { envelope: null, validation, warnings };
78
+ }
79
+ return { envelope: candidate, validation, warnings };
80
+ }
81
+ catch (error) {
82
+ // Operational failure inside the peer (e.g. canonicalization of an
83
+ // uncanonicalizable material value): degrade per §5.8, never throw.
84
+ warnings.push(`atrib: evidence envelope construction failed: ${String(error)}`);
85
+ return { envelope: null, validation: null, warnings };
86
+ }
87
+ }
88
+ /**
89
+ * Validate an envelope against the normative §5.5.7 shape rules via the
90
+ * optional `@atrib/verify` peer. Peer missing degrades to
91
+ * `validation: null` with a warning (§5.8); never throws.
92
+ */
93
+ export async function validateEvidenceEnvelope(envelope) {
94
+ const mod = await loadVerifyEnvelopeModule();
95
+ if (mod === null) {
96
+ return { validation: null, warnings: [PEER_MISSING_WARNING] };
97
+ }
98
+ try {
99
+ return { validation: mod.validateEnvelope(envelope), warnings: [] };
100
+ }
101
+ catch (error) {
102
+ return {
103
+ validation: null,
104
+ warnings: [`atrib: evidence envelope validation failed: ${String(error)}`],
105
+ };
106
+ }
107
+ }
108
+ //# sourceMappingURL=evidence-envelope.js.map