@algovoi/substrate 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,98 @@
1
+ /**
2
+ * JCS RFC 8785 canonicalisation with the AlgoVoi-discipline rules.
3
+ *
4
+ * The canonicalisation discipline is the shared normative substrate for
5
+ * x402, AP2, A2A, and MPP receipts, formalised in PR #2436 in
6
+ * x402-foundation/x402 (three-voice coalition co-signed). This module
7
+ * wraps the `canonicalize` npm package (v3.0.0) with the discipline's
8
+ * pre-canonicalisation rules.
9
+ *
10
+ * Reference matrix: https://gist.github.com/chopmob-cloud/b327814c4e17ed9fc7b4f29c8bda523c
11
+ */
12
+ import canonicalizeLib from 'canonicalize';
13
+ import { createHash } from 'node:crypto';
14
+ /**
15
+ * Pin the discipline version. Receipts emitted under this substrate carry
16
+ * this string in their canon_version field so a year-five verifier can pick
17
+ * the correct re-canonicalisation rule from the retained bytes alone.
18
+ */
19
+ export const CANON_VERSION = 'jcs-rfc8785-v1';
20
+ export class CanonicalizationError extends Error {
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = 'CanonicalizationError';
24
+ }
25
+ }
26
+ /**
27
+ * Walk obj and enforce AlgoVoi-discipline pre-canonicalisation type rules:
28
+ * - object keys must be strings
29
+ * - only JSON-canonicalisable types are accepted (null, boolean, number,
30
+ * string, array, plain object)
31
+ *
32
+ * Strict-integer-fields enforcement (timestamp_ms etc.) is done at the
33
+ * per-schema layer rather than universally here.
34
+ */
35
+ function validateTypes(obj, path = '$') {
36
+ if (obj === null)
37
+ return;
38
+ if (typeof obj === 'boolean')
39
+ return;
40
+ if (typeof obj === 'number')
41
+ return;
42
+ if (typeof obj === 'string')
43
+ return;
44
+ if (Array.isArray(obj)) {
45
+ for (let i = 0; i < obj.length; i++) {
46
+ validateTypes(obj[i], `${path}[${i}]`);
47
+ }
48
+ return;
49
+ }
50
+ if (typeof obj === 'object') {
51
+ for (const [k, v] of Object.entries(obj)) {
52
+ // Object.entries always yields string keys; this guard is for safety
53
+ // around prototype-pollution edge cases in user-supplied objects.
54
+ if (typeof k !== 'string') {
55
+ throw new CanonicalizationError(`non-string object key at ${path}: ${typeof k}`);
56
+ }
57
+ validateTypes(v, `${path}.${k}`);
58
+ }
59
+ return;
60
+ }
61
+ throw new CanonicalizationError(`non-canonicalisable type at ${path}: ${typeof obj}`);
62
+ }
63
+ /**
64
+ * Return the canonical JCS string representation of obj.
65
+ *
66
+ * Applies AlgoVoi-discipline pre-canonicalisation type validation, then
67
+ * delegates to canonicalize (RFC 8785) for the canonical bytes.
68
+ *
69
+ * Throws CanonicalizationError on type-validation failure.
70
+ */
71
+ export function canonicalize(obj) {
72
+ validateTypes(obj);
73
+ const out = canonicalizeLib(obj);
74
+ if (out === undefined) {
75
+ // canonicalize() returns undefined for values that JSON.stringify would
76
+ // also drop (e.g. functions, undefined values at the root). The
77
+ // discipline does not allow those at any level; this is belt-and-braces.
78
+ throw new CanonicalizationError('object is not JCS-canonicalisable (canonicalize returned undefined)');
79
+ }
80
+ return out;
81
+ }
82
+ /**
83
+ * Return the canonical JCS UTF-8 bytes of obj as a Uint8Array.
84
+ */
85
+ export function canonicalizeBytes(obj) {
86
+ return new TextEncoder().encode(canonicalize(obj));
87
+ }
88
+ /**
89
+ * Return the lowercase hex SHA-256 of the JCS canonical bytes of obj.
90
+ *
91
+ * This is the substrate primitive used by action_ref, compliance receipts,
92
+ * audit chain entries, and the composite trust-query algorithm. The string
93
+ * is plain hex without an algorithm prefix.
94
+ */
95
+ export function sha256Jcs(obj) {
96
+ return createHash('sha256').update(canonicalize(obj)).digest('hex');
97
+ }
98
+ //# sourceMappingURL=canonicalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonicalize.js","sourceRoot":"","sources":["../src/canonicalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,eAAe,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,gBAAyB,CAAC;AAEvD,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,SAAS,aAAa,CAAC,GAAY,EAAE,IAAI,GAAG,GAAG;IAC7C,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO;IACzB,IAAI,OAAO,GAAG,KAAK,SAAS;QAAE,OAAO;IACrC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO;IACpC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO;IACpC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;QACD,OAAO;IACT,CAAC;IACD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,EAAE,CAAC;YACpE,qEAAqE;YACrE,kEAAkE;YAClE,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC1B,MAAM,IAAI,qBAAqB,CAC7B,4BAA4B,IAAI,KAAK,OAAO,CAAC,EAAE,CAChD,CAAC;YACJ,CAAC;YACD,aAAa,CAAC,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC;QACD,OAAO;IACT,CAAC;IACD,MAAM,IAAI,qBAAqB,CAC7B,+BAA+B,IAAI,KAAK,OAAO,GAAG,EAAE,CACrD,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,aAAa,CAAC,GAAG,CAAC,CAAC;IACnB,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,wEAAwE;QACxE,gEAAgE;QAChE,yEAAyE;QACzE,MAAM,IAAI,qBAAqB,CAC7B,qEAAqE,CACtE,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAY;IACpC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Compliance receipt shape matching AlgoVoi's production /compliance/screen.
3
+ *
4
+ * The categorical outcome (ALLOW / REFER / DENY) is load-bearing for the
5
+ * retention layer (UK POCA 2002 s.330 SAR distinction). A score/tier
6
+ * projection would lose that property and break year-five auditability.
7
+ */
8
+ export declare const SCREEN_RESULTS: readonly ["ALLOW", "REFER", "DENY"];
9
+ export type ScreenResult = (typeof SCREEN_RESULTS)[number];
10
+ export declare class ComplianceReceiptError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ export interface ComplianceReceipt {
14
+ payer_ref: string;
15
+ screen_result: ScreenResult;
16
+ screen_timestamp_ms: number;
17
+ screen_provider_did: string;
18
+ jurisdiction_flags: string[];
19
+ canon_version: string;
20
+ }
21
+ export interface BuildComplianceReceiptInput {
22
+ payer_ref: string;
23
+ screen_result: string;
24
+ screen_timestamp_ms: number;
25
+ screen_provider_did: string;
26
+ jurisdiction_flags: string[];
27
+ canon_version?: string;
28
+ }
29
+ /**
30
+ * Build a validated compliance receipt object.
31
+ *
32
+ * screen_result must be one of ALLOW, REFER, DENY -- the categorical
33
+ * outcome is load-bearing for downstream regulatory obligations.
34
+ *
35
+ * payer_ref is expected to be a content-addressed identity reference
36
+ * (e.g. "sha256:<hex>") rather than cleartext identity content.
37
+ *
38
+ * jurisdiction_flags is treated as ordered -- ["UK","EU"] and ["EU","UK"]
39
+ * produce distinct canonical bytes per RFC 8785 §3.2.3.
40
+ */
41
+ export declare function buildComplianceReceipt(input: BuildComplianceReceiptInput): ComplianceReceipt;
42
+ //# sourceMappingURL=compliance-receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compliance-receipt.d.ts","sourceRoot":"","sources":["../src/compliance-receipt.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,eAAO,MAAM,cAAc,qCAAsC,CAAC;AAClE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,YAAY,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA+CD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,2BAA2B,GACjC,iBAAiB,CAqBnB"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Compliance receipt shape matching AlgoVoi's production /compliance/screen.
3
+ *
4
+ * The categorical outcome (ALLOW / REFER / DENY) is load-bearing for the
5
+ * retention layer (UK POCA 2002 s.330 SAR distinction). A score/tier
6
+ * projection would lose that property and break year-five auditability.
7
+ */
8
+ import { CANON_VERSION } from './canonicalize.js';
9
+ export const SCREEN_RESULTS = ['ALLOW', 'REFER', 'DENY'];
10
+ export class ComplianceReceiptError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = 'ComplianceReceiptError';
14
+ }
15
+ }
16
+ function requireNonEmptyString(field, value) {
17
+ if (typeof value !== 'string' || value.length === 0) {
18
+ throw new ComplianceReceiptError(`${field} must be a non-empty string`);
19
+ }
20
+ return value;
21
+ }
22
+ function requireIntTimestampMs(value) {
23
+ if (typeof value !== 'number') {
24
+ throw new ComplianceReceiptError(`screen_timestamp_ms must be epoch-millisecond integer (Substrate Rule 1), got ${typeof value}`);
25
+ }
26
+ if (!Number.isFinite(value) || !Number.isInteger(value)) {
27
+ throw new ComplianceReceiptError(`screen_timestamp_ms must be epoch-millisecond integer (Substrate Rule 1), got ${value}`);
28
+ }
29
+ if (value < 0) {
30
+ throw new ComplianceReceiptError(`screen_timestamp_ms must be non-negative, got ${value}`);
31
+ }
32
+ return value;
33
+ }
34
+ function requireJurisdictionFlags(value) {
35
+ if (!Array.isArray(value)) {
36
+ throw new ComplianceReceiptError(`jurisdiction_flags must be array, got ${typeof value}`);
37
+ }
38
+ for (let i = 0; i < value.length; i++) {
39
+ const code = value[i];
40
+ if (typeof code !== 'string' || code.length === 0) {
41
+ throw new ComplianceReceiptError(`jurisdiction_flags[${i}] must be a non-empty string`);
42
+ }
43
+ }
44
+ return [...value];
45
+ }
46
+ /**
47
+ * Build a validated compliance receipt object.
48
+ *
49
+ * screen_result must be one of ALLOW, REFER, DENY -- the categorical
50
+ * outcome is load-bearing for downstream regulatory obligations.
51
+ *
52
+ * payer_ref is expected to be a content-addressed identity reference
53
+ * (e.g. "sha256:<hex>") rather than cleartext identity content.
54
+ *
55
+ * jurisdiction_flags is treated as ordered -- ["UK","EU"] and ["EU","UK"]
56
+ * produce distinct canonical bytes per RFC 8785 §3.2.3.
57
+ */
58
+ export function buildComplianceReceipt(input) {
59
+ if (!SCREEN_RESULTS.includes(input.screen_result)) {
60
+ throw new ComplianceReceiptError(`screen_result must be one of ${JSON.stringify([...SCREEN_RESULTS])}, got ${JSON.stringify(input.screen_result)}`);
61
+ }
62
+ return {
63
+ payer_ref: requireNonEmptyString('payer_ref', input.payer_ref),
64
+ screen_result: input.screen_result,
65
+ screen_timestamp_ms: requireIntTimestampMs(input.screen_timestamp_ms),
66
+ screen_provider_did: requireNonEmptyString('screen_provider_did', input.screen_provider_did),
67
+ jurisdiction_flags: requireJurisdictionFlags(input.jurisdiction_flags),
68
+ canon_version: requireNonEmptyString('canon_version', input.canon_version ?? CANON_VERSION),
69
+ };
70
+ }
71
+ //# sourceMappingURL=compliance-receipt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compliance-receipt.js","sourceRoot":"","sources":["../src/compliance-receipt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Composite trust-query algorithm (PR #2440 in x402-foundation/x402).
3
+ *
4
+ * composite_hash = SHA-256(JCS([emitter_rows sorted by source_id, sig excluded]))
5
+ *
6
+ * Aggregates trust evidence from multiple emitters into a single canonical
7
+ * hash. Properties:
8
+ * - Row ordering invariance (sort-by-source_id).
9
+ * - Set semantics (subset rows produce a distinct hash).
10
+ * - Signature exclusion (sig field dropped before serialisation).
11
+ *
12
+ * 5-implementation cross-validation:
13
+ * https://gist.github.com/chopmob-cloud/f2e9f0877b7d9fff70c8eca46e4ce636
14
+ */
15
+ export declare class CompositeTrustQueryError extends Error {
16
+ constructor(message: string);
17
+ }
18
+ export interface EmitterRow {
19
+ source_id: string;
20
+ [key: string]: unknown;
21
+ sig?: unknown;
22
+ }
23
+ /**
24
+ * Return the lowercase hex composite_hash for a set of emitter rows.
25
+ *
26
+ * Steps (per PR #2440 §composite-hash):
27
+ * 1. Strip the sig field from each row.
28
+ * 2. Sort rows by source_id (lexicographic).
29
+ * 3. JCS-canonicalise the resulting array.
30
+ * 4. SHA-256 the canonical bytes.
31
+ *
32
+ * Output is plain hex without an algorithm prefix.
33
+ */
34
+ export declare function compositeTrustQueryHash(rows: readonly EmitterRow[]): string;
35
+ //# sourceMappingURL=composite-trust-query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-trust-query.d.ts","sourceRoot":"","sources":["../src/composite-trust-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAElB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAEvB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAkBD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAwB3E"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Composite trust-query algorithm (PR #2440 in x402-foundation/x402).
3
+ *
4
+ * composite_hash = SHA-256(JCS([emitter_rows sorted by source_id, sig excluded]))
5
+ *
6
+ * Aggregates trust evidence from multiple emitters into a single canonical
7
+ * hash. Properties:
8
+ * - Row ordering invariance (sort-by-source_id).
9
+ * - Set semantics (subset rows produce a distinct hash).
10
+ * - Signature exclusion (sig field dropped before serialisation).
11
+ *
12
+ * 5-implementation cross-validation:
13
+ * https://gist.github.com/chopmob-cloud/f2e9f0877b7d9fff70c8eca46e4ce636
14
+ */
15
+ import { sha256Jcs } from './canonicalize.js';
16
+ export class CompositeTrustQueryError extends Error {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = 'CompositeTrustQueryError';
20
+ }
21
+ }
22
+ function stripSig(row) {
23
+ const { sig, ...rest } = row;
24
+ void sig;
25
+ return rest;
26
+ }
27
+ function requireSourceId(row) {
28
+ const src = row['source_id'];
29
+ if (typeof src !== 'string' || src.length === 0) {
30
+ throw new CompositeTrustQueryError('every emitter row must carry a non-empty string source_id');
31
+ }
32
+ return src;
33
+ }
34
+ /**
35
+ * Return the lowercase hex composite_hash for a set of emitter rows.
36
+ *
37
+ * Steps (per PR #2440 §composite-hash):
38
+ * 1. Strip the sig field from each row.
39
+ * 2. Sort rows by source_id (lexicographic).
40
+ * 3. JCS-canonicalise the resulting array.
41
+ * 4. SHA-256 the canonical bytes.
42
+ *
43
+ * Output is plain hex without an algorithm prefix.
44
+ */
45
+ export function compositeTrustQueryHash(rows) {
46
+ if (rows.length === 0) {
47
+ throw new CompositeTrustQueryError('composite trust-query requires at least one emitter row');
48
+ }
49
+ const stripped = [];
50
+ for (const row of rows) {
51
+ if (row === null || typeof row !== 'object' || Array.isArray(row)) {
52
+ throw new CompositeTrustQueryError(`emitter row must be an object, got ${Array.isArray(row) ? 'array' : typeof row}`);
53
+ }
54
+ requireSourceId(row);
55
+ stripped.push(stripSig(row));
56
+ }
57
+ stripped.sort((a, b) => {
58
+ const sa = a['source_id'];
59
+ const sb = b['source_id'];
60
+ return sa < sb ? -1 : sa > sb ? 1 : 0;
61
+ });
62
+ return sha256Jcs(stripped);
63
+ }
64
+ //# sourceMappingURL=composite-trust-query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-trust-query.js","sourceRoot":"","sources":["../src/composite-trust-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAUD,SAAS,QAAQ,CAAC,GAA4B;IAC5C,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC;IAC7B,KAAK,GAAG,CAAC;IACT,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,eAAe,CAAC,GAA4B;IACnD,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,wBAAwB,CAChC,2DAA2D,CAC5D,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAA2B;IACjE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,wBAAwB,CAChC,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAA8B,EAAE,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,wBAAwB,CAChC,sCAAsC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE,CAClF,CAAC;QACJ,CAAC;QACD,eAAe,CAAC,GAA8B,CAAC,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,GAA8B,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrB,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,CAAW,CAAC;QACpC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,CAAW,CAAC;QACpC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,OAAO,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @algovoi/substrate
3
+ *
4
+ * AlgoVoi agentic-payments substrate reference implementation.
5
+ *
6
+ * - JCS RFC 8785 canonicalisation with the AlgoVoi-discipline rules
7
+ * (type-validation pre-canonicalisation, in-band canon_version pin).
8
+ * - action_ref atomic primitive:
9
+ * SHA-256(JCS({agent_id, action_type, scope, timestamp_ms})).
10
+ * - Composite trust-query algorithm (PR #2440 in x402-foundation/x402).
11
+ * - Compliance receipt shape matching AlgoVoi's production
12
+ * /compliance/screen emission.
13
+ * - Audit chain primitive: monotonic per-row hash chain with content_hash +
14
+ * prev_hash linking, year-five auditability under canon_version pin.
15
+ *
16
+ * The substrate runs in production at https://api.algovoi.co.uk/compliance.
17
+ * Licensed under Apache 2.0.
18
+ */
19
+ export { CANON_VERSION, CanonicalizationError, canonicalize, canonicalizeBytes, sha256Jcs, } from './canonicalize.js';
20
+ export { ActionRefError, type ActionRefInput, type ActionRefObject, actionRef, actionRefObject, } from './action-ref.js';
21
+ export { CompositeTrustQueryError, type EmitterRow, compositeTrustQueryHash, } from './composite-trust-query.js';
22
+ export { SCREEN_RESULTS, type ScreenResult, ComplianceReceiptError, type ComplianceReceipt, type BuildComplianceReceiptInput, buildComplianceReceipt, } from './compliance-receipt.js';
23
+ export { AuditChainError, type AuditChainRow, appendToChain, verifyAuditChain, } from './audit-chain.js';
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,SAAS,EACT,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,cAAc,EACd,KAAK,YAAY,EACjB,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,2BAA2B,EAChC,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @algovoi/substrate
3
+ *
4
+ * AlgoVoi agentic-payments substrate reference implementation.
5
+ *
6
+ * - JCS RFC 8785 canonicalisation with the AlgoVoi-discipline rules
7
+ * (type-validation pre-canonicalisation, in-band canon_version pin).
8
+ * - action_ref atomic primitive:
9
+ * SHA-256(JCS({agent_id, action_type, scope, timestamp_ms})).
10
+ * - Composite trust-query algorithm (PR #2440 in x402-foundation/x402).
11
+ * - Compliance receipt shape matching AlgoVoi's production
12
+ * /compliance/screen emission.
13
+ * - Audit chain primitive: monotonic per-row hash chain with content_hash +
14
+ * prev_hash linking, year-five auditability under canon_version pin.
15
+ *
16
+ * The substrate runs in production at https://api.algovoi.co.uk/compliance.
17
+ * Licensed under Apache 2.0.
18
+ */
19
+ export { CANON_VERSION, CanonicalizationError, canonicalize, canonicalizeBytes, sha256Jcs, } from './canonicalize.js';
20
+ export { ActionRefError, actionRef, actionRefObject, } from './action-ref.js';
21
+ export { CompositeTrustQueryError, compositeTrustQueryHash, } from './composite-trust-query.js';
22
+ export { SCREEN_RESULTS, ComplianceReceiptError, buildComplianceReceipt, } from './compliance-receipt.js';
23
+ export { AuditChainError, appendToChain, verifyAuditChain, } from './audit-chain.js';
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,YAAY,EACZ,iBAAiB,EACjB,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,cAAc,EAGd,SAAS,EACT,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,wBAAwB,EAExB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,cAAc,EAEd,sBAAsB,EAGtB,sBAAsB,GACvB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,eAAe,EAEf,aAAa,EACb,gBAAgB,GACjB,MAAM,kBAAkB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@algovoi/substrate",
3
+ "version": "0.1.0",
4
+ "description": "AlgoVoi agentic-payments substrate -- JCS canonicalisation, action_ref, composite trust-query, compliance receipts, audit chain",
5
+ "keywords": [
6
+ "x402",
7
+ "ap2",
8
+ "a2a",
9
+ "mpp",
10
+ "jcs",
11
+ "rfc8785",
12
+ "canonicalisation",
13
+ "agentic-payments",
14
+ "compliance",
15
+ "audit-chain",
16
+ "trust-query"
17
+ ],
18
+ "homepage": "https://api.algovoi.co.uk",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/chopmob-cloud/algovoi-substrate.git",
22
+ "directory": "typescript"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/chopmob-cloud/algovoi-substrate/issues"
26
+ },
27
+ "license": "Apache-2.0",
28
+ "author": "AlgoVoi <chopmob@gmail.com>",
29
+ "type": "module",
30
+ "main": "./dist/index.js",
31
+ "module": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "import": "./dist/index.js",
36
+ "types": "./dist/index.d.ts"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist/**/*",
41
+ "src/**/*",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "scripts": {
46
+ "build": "tsc",
47
+ "test": "vitest run",
48
+ "test:watch": "vitest",
49
+ "clean": "rimraf dist",
50
+ "prepublishOnly": "npm run clean && npm run build && npm test"
51
+ },
52
+ "engines": {
53
+ "node": ">=18"
54
+ },
55
+ "dependencies": {
56
+ "canonicalize": "^3.0.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^20.0.0",
60
+ "rimraf": "^5.0.0",
61
+ "typescript": "^5.4.0",
62
+ "vitest": "^1.6.0"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ }
67
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * action_ref atomic primitive.
3
+ *
4
+ * action_ref = SHA-256(JCS({agent_id, action_type, scope, timestamp_ms}))
5
+ *
6
+ * The action_ref is the AlgoVoi-authored canonical action identifier used
7
+ * across the substrate. Two emitters describing the same logical action
8
+ * produce the same action_ref by construction.
9
+ *
10
+ * - timestamp_ms is enforced as an epoch-millisecond integer per Substrate
11
+ * Rule 1. Float, ISO 8601 strings, and negative values are rejected.
12
+ * - agent_id, action_type, scope are required non-empty strings.
13
+ * - The preimage shape is fixed at exactly these four fields.
14
+ */
15
+
16
+ import { sha256Jcs } from './canonicalize.js';
17
+
18
+ export class ActionRefError extends Error {
19
+ constructor(message: string) {
20
+ super(message);
21
+ this.name = 'ActionRefError';
22
+ }
23
+ }
24
+
25
+ export interface ActionRefObject {
26
+ agent_id: string;
27
+ action_type: string;
28
+ scope: string;
29
+ timestamp_ms: number;
30
+ }
31
+
32
+ function requireString(field: string, value: unknown): string {
33
+ if (typeof value !== 'string') {
34
+ throw new ActionRefError(`${field} must be string, got ${typeof value}`);
35
+ }
36
+ if (value.length === 0) {
37
+ throw new ActionRefError(`${field} must be a non-empty string`);
38
+ }
39
+ return value;
40
+ }
41
+
42
+ function requireIntTimestampMs(value: unknown): number {
43
+ if (typeof value !== 'number') {
44
+ throw new ActionRefError(
45
+ `timestamp_ms must be epoch-millisecond integer (Substrate Rule 1), got ${typeof value}`,
46
+ );
47
+ }
48
+ if (!Number.isFinite(value)) {
49
+ throw new ActionRefError(
50
+ `timestamp_ms must be a finite number, got ${value}`,
51
+ );
52
+ }
53
+ if (!Number.isInteger(value)) {
54
+ throw new ActionRefError(
55
+ `timestamp_ms must be epoch-millisecond integer (Substrate Rule 1), got float ${value}`,
56
+ );
57
+ }
58
+ if (value < 0) {
59
+ throw new ActionRefError(`timestamp_ms must be non-negative, got ${value}`);
60
+ }
61
+ return value;
62
+ }
63
+
64
+ export interface ActionRefInput {
65
+ agent_id: string;
66
+ action_type: string;
67
+ scope: string;
68
+ timestamp_ms: number;
69
+ }
70
+
71
+ /**
72
+ * Return the validated preimage object for action_ref.
73
+ *
74
+ * The four-field shape is fixed by the substrate; extending it produces
75
+ * a different primitive, not a different action_ref.
76
+ */
77
+ export function actionRefObject(input: ActionRefInput): ActionRefObject {
78
+ return {
79
+ agent_id: requireString('agent_id', input.agent_id),
80
+ action_type: requireString('action_type', input.action_type),
81
+ scope: requireString('scope', input.scope),
82
+ timestamp_ms: requireIntTimestampMs(input.timestamp_ms),
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Return the lowercase hex SHA-256 action_ref.
88
+ *
89
+ * action_ref = SHA-256(JCS({agent_id, action_type, scope, timestamp_ms}))
90
+ *
91
+ * Output is plain hex (no algorithm prefix).
92
+ */
93
+ export function actionRef(input: ActionRefInput): string {
94
+ const obj = actionRefObject(input);
95
+ return sha256Jcs(obj);
96
+ }