@atrib/verify 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.
Files changed (43) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +177 -0
  3. package/dist/calculate.d.ts +32 -0
  4. package/dist/calculate.d.ts.map +1 -0
  5. package/dist/calculate.js +495 -0
  6. package/dist/calculate.js.map +1 -0
  7. package/dist/graph-fetch.d.ts +22 -0
  8. package/dist/graph-fetch.d.ts.map +1 -0
  9. package/dist/graph-fetch.js +60 -0
  10. package/dist/graph-fetch.js.map +1 -0
  11. package/dist/index.d.ts +15 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +24 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/policy-builder.d.ts +26 -0
  16. package/dist/policy-builder.d.ts.map +1 -0
  17. package/dist/policy-builder.js +49 -0
  18. package/dist/policy-builder.js.map +1 -0
  19. package/dist/recommendation.d.ts +29 -0
  20. package/dist/recommendation.d.ts.map +1 -0
  21. package/dist/recommendation.js +91 -0
  22. package/dist/recommendation.js.map +1 -0
  23. package/dist/resolve-identity.d.ts +84 -0
  24. package/dist/resolve-identity.d.ts.map +1 -0
  25. package/dist/resolve-identity.js +121 -0
  26. package/dist/resolve-identity.js.map +1 -0
  27. package/dist/revocations.d.ts +73 -0
  28. package/dist/revocations.d.ts.map +1 -0
  29. package/dist/revocations.js +83 -0
  30. package/dist/revocations.js.map +1 -0
  31. package/dist/types.d.ts +200 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +25 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/verifier.d.ts +54 -0
  36. package/dist/verifier.d.ts.map +1 -0
  37. package/dist/verifier.js +213 -0
  38. package/dist/verifier.js.map +1 -0
  39. package/dist/verify-record.d.ts +149 -0
  40. package/dist/verify-record.d.ts.map +1 -0
  41. package/dist/verify-record.js +136 -0
  42. package/dist/verify-record.js.map +1 -0
  43. package/package.json +47 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Types for @atrib/verify. graph objects, policy documents,
3
+ * and settlement recommendation documents.
4
+ *
5
+ * Mirrors §3.5 (graph response schema) and §4.7 (recommendation document).
6
+ */
7
+ /**
8
+ * Graph-layer event_type. Short labels for the three primitives the v1
9
+ * graph + calculation algorithm operate on, plus the catch-all `extension`
10
+ * for records carrying an event_type URI not in atrib normative set
11
+ * (extension URIs are graph nodes but are NOT participants in §3.2.4 edge
12
+ * derivation in v1; see spec §3.2.1).
13
+ *
14
+ * Records arriving at the graph builder carry an absolute URI in
15
+ * `record.event_type` per §1.2.4. The builder normalizes URIs to these
16
+ * short labels via `graphLabelFromEventTypeUri()`. Extension URIs are
17
+ * preserved verbatim on `GraphNode.event_type_uri` for graph clients that
18
+ * want the original URI.
19
+ */
20
+ export type EventType = 'tool_call' | 'transaction' | 'observation' | 'directory_anchor' | 'annotation' | 'revision' | 'gap_node' | 'dangling_node' | 'extension';
21
+ /**
22
+ * Map an attribution record's event_type URI to a graph-layer short label.
23
+ * Atrib normative URIs map to their canonical short label; everything else
24
+ * collapses to `'extension'` (the graph-layer opaque-typed category).
25
+ */
26
+ export declare function graphLabelFromEventTypeUri(uri: string): EventType;
27
+ /** An unsigned hop: a tool call with no attribution record in response (§1.6, §3.2.5). */
28
+ export interface GapNode {
29
+ type: 'gap_node';
30
+ tool_url: string;
31
+ tool_name: string;
32
+ context_id: string;
33
+ timestamp: number;
34
+ signed: false;
35
+ }
36
+ export type EdgeType = 'CHAIN_PRECEDES' | 'SESSION_PRECEDES' | 'SESSION_PARALLEL' | 'CONVERGES_ON' | 'CROSS_SESSION' | 'INFORMED_BY' | 'PROVENANCE_OF' | 'ANNOTATES' | 'REVISES';
37
+ export type VerificationState = 'unsigned' | 'signature_valid' | 'log_committed' | 'witnessed'
38
+ /**
39
+ * Per spec §1.9.3: a record signed by a key that was retired before
40
+ * the record's log_index. The signature is still cryptographically
41
+ * valid, but the key was revoked at the moment of signing, so the
42
+ * record MUST NOT contribute to attribution calculations (§4.6).
43
+ * Verifiers carrying a revocation registry annotate records with this
44
+ * state when (creator_key, log_index) falls after a key_revocation.
45
+ */
46
+ | 'revoked_after_revocation';
47
+ /** A node in the attribution graph (§3.5.2). */
48
+ export interface GraphNode {
49
+ id: string;
50
+ event_type: EventType;
51
+ /**
52
+ * The original event_type URI from the underlying record (§1.2.4), or
53
+ * null for synthetic nodes (gap_node). Atrib normative URIs are present
54
+ * as their canonical strings; extension URIs are preserved verbatim.
55
+ * Graph clients that need to filter by URI rather than short label use
56
+ * this field.
57
+ */
58
+ event_type_uri: string | null;
59
+ content_id: string | null;
60
+ creator_key: string | null;
61
+ chain_root: string | null;
62
+ context_id: string;
63
+ timestamp: number;
64
+ log_index: number | null;
65
+ verification_state: VerificationState;
66
+ is_genesis: boolean;
67
+ }
68
+ /** An edge in the attribution graph (§3.5.3). */
69
+ export interface GraphEdge {
70
+ type: EdgeType;
71
+ source: string;
72
+ target: string;
73
+ directed: boolean;
74
+ /**
75
+ * True when the edge target is a synthetic dangling node — the agent's
76
+ * declared informed_by or provenance_token reference did not resolve to a
77
+ * record in the resolved set. Per spec §3.2.4 steps 6 + 7, dangling
78
+ * references MUST be surfaced as edges (not silently dropped) so verifiers
79
+ * can see what the agent claimed even when the upstream isn't accessible.
80
+ * Only INFORMED_BY and PROVENANCE_OF edges can be dangling.
81
+ */
82
+ dangling?: boolean;
83
+ /**
84
+ * Optional reason annotation. Currently used by PROVENANCE_OF dangling
85
+ * edges with the value `"no_token_source_in_record_set"` per spec §3.2.4
86
+ * step 7.
87
+ */
88
+ reason?: string;
89
+ }
90
+ /** A graph response object (§3.5.1). */
91
+ export interface GraphResponse {
92
+ spec_version: 'atrib/1.0';
93
+ context_id: string;
94
+ generated_at: number;
95
+ node_count: number;
96
+ edge_count: number;
97
+ has_transaction: boolean;
98
+ cross_session_count: number;
99
+ nodes: GraphNode[];
100
+ edges: GraphEdge[];
101
+ }
102
+ export type DistributionMethod = 'proportional' | 'equal' | 'weighted';
103
+ export interface EdgeWeights {
104
+ CHAIN_PRECEDES?: number;
105
+ SESSION_PRECEDES?: number;
106
+ SESSION_PARALLEL?: number;
107
+ CONVERGES_ON?: number;
108
+ CROSS_SESSION?: number;
109
+ unsigned?: number;
110
+ }
111
+ export type Modifier = {
112
+ type: 'temporal_decay';
113
+ half_life_ms: number;
114
+ } | {
115
+ type: 'chain_depth_penalty';
116
+ penalty_per_level: number;
117
+ } | {
118
+ type: 'call_count_boost';
119
+ multiplier_per_call: number;
120
+ cap: number;
121
+ } | {
122
+ type: string;
123
+ [key: string]: unknown;
124
+ };
125
+ export interface PolicyConstraints {
126
+ minimum_share?: number;
127
+ maximum_share?: number;
128
+ minimum_own_share?: number;
129
+ maximum_total_share?: number;
130
+ }
131
+ /** A policy document (§4.2). */
132
+ export interface PolicyDocument {
133
+ spec_version: 'atrib/1.0';
134
+ policy_id?: string;
135
+ role?: 'merchant' | 'creator' | 'default';
136
+ edge_weights?: EdgeWeights;
137
+ modifiers?: Modifier[];
138
+ distribution?: DistributionMethod;
139
+ constraints?: PolicyConstraints;
140
+ }
141
+ /** Minimal policy shape for creator policy entries. Accepts any spec_version. */
142
+ export interface CreatorPolicySnapshot {
143
+ spec_version: string;
144
+ constraints?: {
145
+ minimum_share?: number;
146
+ maximum_share?: number;
147
+ minimum_own_share?: number;
148
+ maximum_total_share?: number;
149
+ };
150
+ [key: string]: unknown;
151
+ }
152
+ /** Creator policy entry in the session policy record (§4.5.3). */
153
+ export interface CreatorPolicyEntry {
154
+ server_url: string;
155
+ policy_url: string;
156
+ status: 'compatible' | 'floor_scaled' | 'conflict_defaulted' | 'not_found';
157
+ /** The fetched policy document snapshot, if available. */
158
+ policy?: CreatorPolicySnapshot | undefined;
159
+ }
160
+ /** Session policy record (§4.5.3). Full record as created by the agent. */
161
+ export interface SessionPolicyRecord {
162
+ spec_version: 'atrib/1.0';
163
+ record_id: string;
164
+ context_id: string;
165
+ created_at: number;
166
+ merchant_policy: string;
167
+ creator_policies: CreatorPolicyEntry[];
168
+ agreed_policy: string;
169
+ applied_constraints: {
170
+ minimum_floors: Record<string, number>;
171
+ };
172
+ warnings: string[];
173
+ }
174
+ /** A creator key → share fraction map. May contain "__unsigned__" sentinel. */
175
+ export type Distribution = Record<string, number>;
176
+ export interface RecommendationDocument {
177
+ spec_version: 'atrib/1.0';
178
+ document_type: 'settlement_recommendation';
179
+ context_id: string;
180
+ transaction_id: string;
181
+ policy_record_id: string;
182
+ graph_checkpoint: string;
183
+ graph_tree_size: number;
184
+ calculated_at: number;
185
+ calculated_by: string;
186
+ distribution: Distribution;
187
+ maximum_total_share: number | null;
188
+ warnings: string[];
189
+ signature: string;
190
+ }
191
+ /** Verification result for a recommendation document (§5.5.2). */
192
+ export interface VerificationResult {
193
+ valid: boolean;
194
+ signatureOk: boolean;
195
+ calcMatch: boolean;
196
+ distribution: Distribution;
197
+ warnings: string[];
198
+ graph_node_count: number;
199
+ }
200
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAMH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,aAAa,GAAG,aAAa,GAAG,kBAAkB,GAAG,YAAY,GAAG,UAAU,GAAG,UAAU,GAAG,eAAe,GAAG,WAAW,CAAA;AAEjK;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAiBjE;AAED,0FAA0F;AAC1F,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,KAAK,CAAA;CACd;AAED,MAAM,MAAM,QAAQ,GAChB,gBAAgB,GAChB,kBAAkB,GAClB,kBAAkB,GAClB,cAAc,GACd,eAAe,GACf,aAAa,GACb,eAAe,GACf,WAAW,GACX,SAAS,CAAA;AAEb,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,iBAAiB,GACjB,eAAe,GACf,WAAW;AACb;;;;;;;GAOG;GACD,0BAA0B,CAAA;AAE9B,gDAAgD;AAChD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,SAAS,CAAA;IACrB;;;;;;OAMG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,kBAAkB,EAAE,iBAAiB,CAAA;IACrC,UAAU,EAAE,OAAO,CAAA;CACpB;AAED,iDAAiD;AACjD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,OAAO,CAAA;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wCAAwC;AACxC,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,WAAW,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,OAAO,CAAA;IACxB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AAMD,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,OAAO,GAAG,UAAU,CAAA;AAEtE,MAAM,WAAW,WAAW;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,qBAAqB,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAA;AAE5C,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,gCAAgC;AAChC,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,WAAW,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAA;IACzC,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAA;IACtB,YAAY,CAAC,EAAE,kBAAkB,CAAA;IACjC,WAAW,CAAC,EAAE,iBAAiB,CAAA;CAChC;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE;QACZ,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,iBAAiB,CAAC,EAAE,MAAM,CAAA;QAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAC7B,CAAA;IACD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,YAAY,GAAG,cAAc,GAAG,oBAAoB,GAAG,WAAW,CAAA;IAC1E,0DAA0D;IAC1D,MAAM,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAA;CAC3C;AAED,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,WAAW,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,gBAAgB,EAAE,kBAAkB,EAAE,CAAA;IACtC,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE;QACnB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KACvC,CAAA;IACD,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAMD,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAEjD,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,WAAW,CAAA;IACzB,aAAa,EAAE,2BAA2B,CAAA;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,CAAA;IACxB,gBAAgB,EAAE,MAAM,CAAA;IACxB,eAAe,EAAE,MAAM,CAAA;IACvB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;IACrB,YAAY,EAAE,YAAY,CAAA;IAC1B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,kEAAkE;AAClE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,OAAO,CAAA;IACpB,SAAS,EAAE,OAAO,CAAA;IAClB,YAAY,EAAE,YAAY,CAAA;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,gBAAgB,EAAE,MAAM,CAAA;CACzB"}
package/dist/types.js ADDED
@@ -0,0 +1,25 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * Map an attribution record's event_type URI to a graph-layer short label.
4
+ * Atrib normative URIs map to their canonical short label; everything else
5
+ * collapses to `'extension'` (the graph-layer opaque-typed category).
6
+ */
7
+ export function graphLabelFromEventTypeUri(uri) {
8
+ switch (uri) {
9
+ case 'https://atrib.dev/v1/types/tool_call':
10
+ return 'tool_call';
11
+ case 'https://atrib.dev/v1/types/transaction':
12
+ return 'transaction';
13
+ case 'https://atrib.dev/v1/types/observation':
14
+ return 'observation';
15
+ case 'https://atrib.dev/v1/types/directory_anchor':
16
+ return 'directory_anchor';
17
+ case 'https://atrib.dev/v1/types/annotation':
18
+ return 'annotation';
19
+ case 'https://atrib.dev/v1/types/revision':
20
+ return 'revision';
21
+ default:
22
+ return 'extension';
23
+ }
24
+ }
25
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sCAAsC;AA4BtC;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,GAAW;IACpD,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,sCAAsC;YACzC,OAAO,WAAW,CAAA;QACpB,KAAK,wCAAwC;YAC3C,OAAO,aAAa,CAAA;QACtB,KAAK,wCAAwC;YAC3C,OAAO,aAAa,CAAA;QACtB,KAAK,6CAA6C;YAChD,OAAO,kBAAkB,CAAA;QAC3B,KAAK,uCAAuC;YAC1C,OAAO,YAAY,CAAA;QACrB,KAAK,qCAAqC;YACxC,OAAO,UAAU,CAAA;QACnB;YACE,OAAO,WAAW,CAAA;IACtB,CAAC;AACH,CAAC"}
@@ -0,0 +1,54 @@
1
+ import type { PolicyDocument, RecommendationDocument, VerificationResult } from './types.js';
2
+ /** Init options for AtribVerifier (§5.5.1). */
3
+ export interface AtribVerifierOptions {
4
+ logEndpoint?: string;
5
+ graphEndpoint?: string;
6
+ resolveEndpoint?: string;
7
+ /** Base64url Ed25519 32-byte seed. Optional. verify() works without it. */
8
+ merchantKey?: string;
9
+ }
10
+ /** Options for post-hoc calculation (§5.5.3). */
11
+ export interface CalculateOptions {
12
+ context_id: string;
13
+ /** A policy document, or "default" to use the §4.3 default policy. */
14
+ policy: PolicyDocument | 'default';
15
+ /** Whether to sign the result with merchantKey. */
16
+ signWith?: 'merchant';
17
+ /** Optional pin to a specific log tree size. */
18
+ treeSize?: number;
19
+ }
20
+ export declare class AtribVerifier {
21
+ private readonly logEndpoint;
22
+ private readonly graphEndpoint;
23
+ private readonly merchantPrivateKey;
24
+ constructor(options?: AtribVerifierOptions);
25
+ /**
26
+ * Verify a settlement recommendation (§5.5.2).
27
+ *
28
+ * Steps:
29
+ * 1. Verify Ed25519 signature using calculated_by's public key (looked up
30
+ * via the resolve service when calculated_by is a known service URL,
31
+ * or assumed to be the merchant key when calculated_by is "local").
32
+ * 2. Fetch the graph at graph_tree_size.
33
+ * 3. Fetch the session policy record (or use default if "default").
34
+ * 4. Run calculate() locally and compare distributions within 1e-9.
35
+ */
36
+ verify(doc: RecommendationDocument): Promise<VerificationResult>;
37
+ /**
38
+ * Post-hoc calculation when no agent SDK was present (§5.5.3).
39
+ *
40
+ * Always returns a recommendation document. If signWith === "merchant" but
41
+ * no merchantKey is set, the document is returned UNSIGNED with a warning
42
+ * (degradation contract §5.8: never throws due to a missing key).
43
+ */
44
+ calculate(options: CalculateOptions): Promise<RecommendationDocument>;
45
+ /**
46
+ * Resolve the public key for a `calculated_by` URL.
47
+ *
48
+ * For v1: only "local" (returns null. caller must supply key separately or
49
+ * accept signatureOk=false) and the well-known atrib resolve endpoint are
50
+ * supported. Future revisions may add a key directory.
51
+ */
52
+ private resolveCalculatedByPublicKey;
53
+ }
54
+ //# sourceMappingURL=verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.d.ts","sourceRoot":"","sources":["../src/verifier.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,cAAc,EACd,sBAAsB,EAEtB,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAMnB,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,2EAA2E;IAC3E,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,MAAM,EAAE,cAAc,GAAG,SAAS,CAAA;IAClC,mDAAmD;IACnD,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAmB;gBAE1C,OAAO,GAAE,oBAAyB;IAsB9C;;;;;;;;;;OAUG;IACG,MAAM,CAAC,GAAG,EAAE,sBAAsB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiEtE;;;;;;OAMG;IACG,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IA2D3E;;;;;;OAMG;YACW,4BAA4B;CAuB3C"}
@@ -0,0 +1,213 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ /**
3
+ * AtribVerifier. the merchant verification class (§5.5).
4
+ *
5
+ * Provides:
6
+ * - verify(recommendationDoc) → independently reproduces calculation (§5.5.2)
7
+ * - calculate({ context_id, policy, signWith }) → post-hoc calculation (§5.5.3)
8
+ *
9
+ * Per §5.8 degradation contract: never throws on missing keys, returns warnings instead.
10
+ */
11
+ import { base64urlDecode } from '@atrib/mcp';
12
+ import { calculate, DEFAULT_POLICY } from './calculate.js';
13
+ import { fetchGraph, fetchSessionPolicyRecord, fetchPolicyDocument } from './graph-fetch.js';
14
+ import { signRecommendation, verifyRecommendationSignature, distributionsMatch, } from './recommendation.js';
15
+ const DEFAULT_LOG_ENDPOINT = 'https://log.atrib.dev/v1';
16
+ const DEFAULT_GRAPH_ENDPOINT = 'https://graph.atrib.dev/v1';
17
+ const DEFAULT_RESOLVE_ENDPOINT = 'https://resolve.atrib.dev/v1';
18
+ export class AtribVerifier {
19
+ logEndpoint;
20
+ graphEndpoint;
21
+ merchantPrivateKey;
22
+ constructor(options = {}) {
23
+ this.logEndpoint = options.logEndpoint ?? DEFAULT_LOG_ENDPOINT;
24
+ this.graphEndpoint = options.graphEndpoint ?? DEFAULT_GRAPH_ENDPOINT;
25
+ // resolveEndpoint reserved for v2 remote-calculation API
26
+ void (options.resolveEndpoint ?? DEFAULT_RESOLVE_ENDPOINT);
27
+ let key = null;
28
+ if (options.merchantKey) {
29
+ try {
30
+ const decoded = base64urlDecode(options.merchantKey);
31
+ if (decoded.length === 32) {
32
+ key = decoded;
33
+ }
34
+ else {
35
+ console.warn('atrib: merchantKey must be 32 bytes; recommendations will be unsigned');
36
+ }
37
+ }
38
+ catch {
39
+ console.warn('atrib: failed to decode merchantKey; recommendations will be unsigned');
40
+ }
41
+ }
42
+ this.merchantPrivateKey = key;
43
+ }
44
+ /**
45
+ * Verify a settlement recommendation (§5.5.2).
46
+ *
47
+ * Steps:
48
+ * 1. Verify Ed25519 signature using calculated_by's public key (looked up
49
+ * via the resolve service when calculated_by is a known service URL,
50
+ * or assumed to be the merchant key when calculated_by is "local").
51
+ * 2. Fetch the graph at graph_tree_size.
52
+ * 3. Fetch the session policy record (or use default if "default").
53
+ * 4. Run calculate() locally and compare distributions within 1e-9.
54
+ */
55
+ async verify(doc) {
56
+ const warnings = [];
57
+ let signatureOk = false;
58
+ let calcMatch = false;
59
+ let localDistribution = doc.distribution;
60
+ let nodeCount = 0;
61
+ // Step 1: signature
62
+ try {
63
+ const pubKey = await this.resolveCalculatedByPublicKey(doc.calculated_by);
64
+ if (!pubKey) {
65
+ warnings.push(`unknown calculated_by; cannot verify signature: ${doc.calculated_by}`);
66
+ }
67
+ else {
68
+ signatureOk = await verifyRecommendationSignature(doc, pubKey);
69
+ if (!signatureOk)
70
+ warnings.push('signature verification failed');
71
+ }
72
+ }
73
+ catch (err) {
74
+ warnings.push(`signature verification error: ${err.message}`);
75
+ }
76
+ // Step 2 & 3 & 4: re-run calculation
77
+ try {
78
+ const graph = await fetchGraph(this.graphEndpoint, doc.context_id, doc.graph_tree_size);
79
+ nodeCount = graph.node_count;
80
+ let policyRecord = null;
81
+ let policy = DEFAULT_POLICY;
82
+ if (doc.policy_record_id !== 'default') {
83
+ try {
84
+ policyRecord = await fetchSessionPolicyRecord(this.graphEndpoint, doc.policy_record_id);
85
+ if (policyRecord.agreed_policy && policyRecord.agreed_policy !== 'default') {
86
+ try {
87
+ policy = await fetchPolicyDocument(policyRecord.agreed_policy);
88
+ }
89
+ catch {
90
+ warnings.push(`failed to fetch agreed policy ${policyRecord.agreed_policy}, falling back to default`);
91
+ }
92
+ }
93
+ }
94
+ catch {
95
+ warnings.push(`failed to fetch session policy record ${doc.policy_record_id}, falling back to default`);
96
+ }
97
+ }
98
+ localDistribution = calculate(graph, policy, policyRecord);
99
+ calcMatch = distributionsMatch(localDistribution, doc.distribution);
100
+ if (!calcMatch) {
101
+ warnings.push('local recalculation does not match document distribution');
102
+ }
103
+ }
104
+ catch (err) {
105
+ warnings.push(`graph fetch or calculation error: ${err.message}`);
106
+ }
107
+ return {
108
+ valid: signatureOk && calcMatch,
109
+ signatureOk,
110
+ calcMatch,
111
+ distribution: localDistribution,
112
+ warnings,
113
+ graph_node_count: nodeCount,
114
+ };
115
+ }
116
+ /**
117
+ * Post-hoc calculation when no agent SDK was present (§5.5.3).
118
+ *
119
+ * Always returns a recommendation document. If signWith === "merchant" but
120
+ * no merchantKey is set, the document is returned UNSIGNED with a warning
121
+ * (degradation contract §5.8: never throws due to a missing key).
122
+ */
123
+ async calculate(options) {
124
+ const warnings = [];
125
+ const policy = options.policy === 'default' ? DEFAULT_POLICY : options.policy;
126
+ // graph_checkpoint records the log origin, not a full signed checkpoint.
127
+ // A full checkpoint would require fetching GET /v1/checkpoint from the log,
128
+ // which is not available in the post-hoc path. The origin string is
129
+ // sufficient for identifying which log the data came from.
130
+ const graphCheckpoint = this.logEndpoint;
131
+ let treeSize = options.treeSize ?? 0;
132
+ let distribution = {};
133
+ let transactionId = '';
134
+ let maxTotalShare = null;
135
+ try {
136
+ const graph = await fetchGraph(this.graphEndpoint, options.context_id, options.treeSize);
137
+ treeSize = options.treeSize ?? 0; // 0 = unpinned; caller should supply treeSize for reproducible verification
138
+ const txNode = graph.nodes.find((n) => n.event_type === 'transaction');
139
+ if (!txNode) {
140
+ warnings.push('no transaction node found in graph; distribution is empty');
141
+ }
142
+ else {
143
+ transactionId = txNode.id;
144
+ }
145
+ distribution = calculate(graph, policy, null);
146
+ maxTotalShare = policy.constraints?.maximum_total_share ?? null;
147
+ }
148
+ catch (err) {
149
+ warnings.push(`graph fetch or calculation error: ${err.message}`);
150
+ }
151
+ const unsigned = {
152
+ spec_version: 'atrib/1.0',
153
+ document_type: 'settlement_recommendation',
154
+ context_id: options.context_id,
155
+ transaction_id: transactionId,
156
+ policy_record_id: options.policy === 'default' ? 'default' : (policy.policy_id ?? 'default'),
157
+ graph_checkpoint: graphCheckpoint,
158
+ graph_tree_size: treeSize,
159
+ calculated_at: Date.now(),
160
+ calculated_by: 'local',
161
+ distribution,
162
+ maximum_total_share: maxTotalShare,
163
+ warnings,
164
+ };
165
+ if (options.signWith === 'merchant') {
166
+ if (!this.merchantPrivateKey) {
167
+ warnings.push('merchantKey not set. Recommendation unsigned');
168
+ return { ...unsigned, signature: '' };
169
+ }
170
+ try {
171
+ return await signRecommendation(unsigned, this.merchantPrivateKey);
172
+ }
173
+ catch (err) {
174
+ warnings.push(`signing failed: ${err.message}`);
175
+ return { ...unsigned, signature: '' };
176
+ }
177
+ }
178
+ return { ...unsigned, signature: '' };
179
+ }
180
+ /**
181
+ * Resolve the public key for a `calculated_by` URL.
182
+ *
183
+ * For v1: only "local" (returns null. caller must supply key separately or
184
+ * accept signatureOk=false) and the well-known atrib resolve endpoint are
185
+ * supported. Future revisions may add a key directory.
186
+ */
187
+ async resolveCalculatedByPublicKey(calculatedBy) {
188
+ if (calculatedBy === 'local') {
189
+ // For "local", the merchant signed it themselves. but we have no way
190
+ // to know which merchant. Caller would need to supply the public key
191
+ // out-of-band. Return null to surface this.
192
+ return null;
193
+ }
194
+ // atrib resolution service publishes its key at /pubkey.
195
+ // Validate hostname to prevent SSRF via crafted calculated_by URLs.
196
+ try {
197
+ const parsed = new URL(calculatedBy);
198
+ if (parsed.protocol !== 'https:' || parsed.hostname !== 'resolve.atrib.dev') {
199
+ return null;
200
+ }
201
+ const url = `${calculatedBy.replace(/\/$/, '')}/pubkey`;
202
+ const res = await fetch(url);
203
+ if (!res.ok)
204
+ return null;
205
+ const text = (await res.text()).trim();
206
+ return text;
207
+ }
208
+ catch {
209
+ return null;
210
+ }
211
+ }
212
+ }
213
+ //# sourceMappingURL=verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.js","sourceRoot":"","sources":["../src/verifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Per-record verification (single AtribRecord).
3
+ *
4
+ * Distinct from `AtribVerifier.verify(recommendationDoc)` which verifies a
5
+ * settlement recommendation document by re-running the §4.6 calculation.
6
+ * This module verifies one signed record at a time and surfaces the
7
+ * per-record annotations defined in the package README.
8
+ *
9
+ * Implemented annotations (this file):
10
+ * - provenance: { token, upstream_record_hash, upstream_resolved } (D044 / §1.2.6)
11
+ * - informed_by_resolution: { resolved: string[], dangling: string[] } (D041 / §1.2.5, §3.2.4)
12
+ * - posture: { timestamp_granularity, timestamp_consistent } (D045 / §8.4)
13
+ *
14
+ * Pending annotations (tracked in DECISIONS.md P005):
15
+ * - capability_check: D051 / §6.7 (needs @atrib/directory integration)
16
+ * - cross_attestation: D052 / §1.7.6 (needs `signers[]` type addition + transaction-record signing variant in @atrib/mcp)
17
+ * - cross_log_*: D050 / §2.11 (needs multi-log proof-bundle infrastructure)
18
+ * - tool_name_form / args_commitment_form: §8.2 / §8.3 (needs tool_name + args_hash fields on the record shape — currently only content_id is exposed)
19
+ */
20
+ import { base64urlDecode, type AtribRecord } from '@atrib/mcp';
21
+ /**
22
+ * Provenance surfacing for a record carrying `provenance_token` (D044 /
23
+ * spec §1.2.6).
24
+ */
25
+ export interface ProvenanceAnnotation {
26
+ token: string;
27
+ /**
28
+ * Full sha256:<64hex> record_hash of the upstream record, or null when
29
+ * no candidate was supplied or the candidate did not match.
30
+ */
31
+ upstream_record_hash: string | null;
32
+ /**
33
+ * True iff a candidate upstream record was supplied AND the first 16
34
+ * bytes of its canonical-form SHA-256 match the decoded token.
35
+ */
36
+ upstream_resolved: boolean;
37
+ }
38
+ /**
39
+ * Informed-by surfacing for a record carrying `informed_by[]` (D041 /
40
+ * spec §1.2.5). Splits the entries into resolved (caller supplied a
41
+ * candidate whose canonical-form SHA-256 matches the entry) and dangling
42
+ * (no candidate matched). Dangling references are flagged but do not
43
+ * fail verification — they're a signal that the verifier hasn't seen
44
+ * upstream context, not that the record is invalid.
45
+ */
46
+ export interface InformedByAnnotation {
47
+ resolved: string[];
48
+ dangling: string[];
49
+ }
50
+ /**
51
+ * Posture surfacing for a record. Currently exposes only the timing
52
+ * posture (§8.4); tool_name_form (§8.2) and args_commitment_form (§8.3)
53
+ * require fields that aren't on the current AtribRecord shape.
54
+ *
55
+ * `timestamp_granularity` is the declared coarsening level (or 'ms' by
56
+ * default when absent). `timestamp_consistent` is true iff the timestamp
57
+ * value matches the granularity's trailing-zero invariant per spec §8.4
58
+ * (e.g., 'min' requires `timestamp % 60000 == 0`). A consistent posture
59
+ * means the record's coarsening claim is structurally honest; an
60
+ * inconsistent posture means the implementation declared a granularity
61
+ * that the timestamp doesn't satisfy, which validators and verifiers
62
+ * MUST reject per §8.4.
63
+ */
64
+ export interface PostureAnnotation {
65
+ timestamp_granularity: 'ms' | 's' | 'min' | 'h' | 'd';
66
+ /**
67
+ * True iff the timestamp value matches the declared granularity's
68
+ * trailing-zero pattern. 'ms' is always consistent (no constraint).
69
+ */
70
+ timestamp_consistent: boolean;
71
+ /**
72
+ * True iff the granularity field was explicitly present on the record,
73
+ * false iff it defaulted to 'ms' because the field was absent.
74
+ * Surfaced separately because absence affects JCS canonical form per §1.3.
75
+ */
76
+ timestamp_granularity_explicit: boolean;
77
+ }
78
+ export interface RecordVerificationResult {
79
+ /** True iff signatureOk AND no fatal warnings. */
80
+ valid: boolean;
81
+ /** Ed25519 signature check over JCS-canonical bytes (§1.4.3). */
82
+ signatureOk: boolean;
83
+ /**
84
+ * Provenance annotation. Populated only when the record carries a
85
+ * `provenance_token` field. Genesis-record-only per spec §1.2.6;
86
+ * verifyRecord does NOT enforce that invariant here (it's the
87
+ * validator + verifier-of-the-chain's job per the spec) — callers that
88
+ * want the strict check should also confirm the record's chain_root
89
+ * equals genesisChainRoot(record.context_id).
90
+ */
91
+ provenance?: ProvenanceAnnotation;
92
+ /**
93
+ * Informed-by annotation. Populated only when the record carries a
94
+ * non-empty `informed_by[]`. `resolved` lists the entries that match
95
+ * a caller-supplied candidate; `dangling` lists the entries that did
96
+ * not match any candidate. With no candidates supplied, all entries
97
+ * land in `dangling` (verification continues regardless).
98
+ */
99
+ informed_by_resolution?: InformedByAnnotation;
100
+ /**
101
+ * Posture annotation. Always populated (every record has a timing
102
+ * posture, even if it defaults to 'ms'). Surfaces the declared
103
+ * granularity and whether the timestamp value structurally matches it.
104
+ */
105
+ posture: PostureAnnotation;
106
+ warnings: string[];
107
+ }
108
+ export interface VerifyRecordOptions {
109
+ /**
110
+ * Candidate upstream record for provenance_token resolution. If
111
+ * supplied and the candidate's canonical SHA-256[:16] matches the
112
+ * record's provenance_token, the verifier surfaces
113
+ * upstream_record_hash and sets upstream_resolved=true.
114
+ */
115
+ upstreamCandidate?: AtribRecord;
116
+ /**
117
+ * Candidate records for informed_by[] resolution. The verifier hashes
118
+ * each candidate (canonical form) and tries to match each
119
+ * informed_by entry against the candidate set. Entries that match a
120
+ * candidate land in `resolved`; entries that do not land in `dangling`.
121
+ * Pass an empty array (or omit) when the verifier has no upstream
122
+ * context — every entry will land in `dangling`, which is informational
123
+ * not invalidating.
124
+ */
125
+ informedByCandidates?: AtribRecord[];
126
+ }
127
+ /**
128
+ * Verify one signed record.
129
+ *
130
+ * Always returns a result; never throws. Per spec §5.8 graceful-
131
+ * degradation: invalid inputs (malformed signature, malformed
132
+ * provenance_token, etc.) are surfaced via warnings + signatureOk=false
133
+ * rather than thrown errors.
134
+ */
135
+ export declare function verifyRecord(record: AtribRecord, options?: VerifyRecordOptions): Promise<RecordVerificationResult>;
136
+ declare function resolveProvenance(token: string, upstreamCandidate: AtribRecord | undefined): ProvenanceAnnotation;
137
+ declare function resolveInformedBy(entries: string[], candidates: AtribRecord[], warnings: string[]): InformedByAnnotation;
138
+ declare function detectPosture(record: AtribRecord, warnings: string[]): PostureAnnotation;
139
+ export declare const __test_only__: {
140
+ PROVENANCE_TOKEN_PATTERN: RegExp;
141
+ SHA256_REF_PATTERN: RegExp;
142
+ GRANULARITY_MULTIPLIER: Record<"s" | "ms" | "min" | "h" | "d", number>;
143
+ resolveProvenance: typeof resolveProvenance;
144
+ resolveInformedBy: typeof resolveInformedBy;
145
+ detectPosture: typeof detectPosture;
146
+ base64urlDecode: typeof base64urlDecode;
147
+ };
148
+ export {};
149
+ //# sourceMappingURL=verify-record.d.ts.map