@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.
- package/LICENSE +202 -0
- package/README.md +129 -0
- package/dist/action-ref.d.ts +45 -0
- package/dist/action-ref.d.ts.map +1 -0
- package/dist/action-ref.js +71 -0
- package/dist/action-ref.js.map +1 -0
- package/dist/audit-chain.d.ts +35 -0
- package/dist/audit-chain.d.ts.map +1 -0
- package/dist/audit-chain.js +84 -0
- package/dist/audit-chain.js.map +1 -0
- package/dist/canonicalize.d.ts +42 -0
- package/dist/canonicalize.d.ts.map +1 -0
- package/dist/canonicalize.js +98 -0
- package/dist/canonicalize.js.map +1 -0
- package/dist/compliance-receipt.d.ts +42 -0
- package/dist/compliance-receipt.d.ts.map +1 -0
- package/dist/compliance-receipt.js +71 -0
- package/dist/compliance-receipt.js.map +1 -0
- package/dist/composite-trust-query.d.ts +35 -0
- package/dist/composite-trust-query.d.ts.map +1 -0
- package/dist/composite-trust-query.js +64 -0
- package/dist/composite-trust-query.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
- package/src/action-ref.ts +96 -0
- package/src/audit-chain.ts +118 -0
- package/src/canonicalize.ts +106 -0
- package/src/compliance-receipt.ts +119 -0
- package/src/composite-trust-query.ts +84 -0
- package/src/index.ts +56 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit chain primitive: monotonic per-row hash chain.
|
|
3
|
+
*
|
|
4
|
+
* Each row carries:
|
|
5
|
+
* - content_hash: SHA-256(JCS(payload))
|
|
6
|
+
* - prev_hash: previous row's content_hash, or null for the head row
|
|
7
|
+
* - chain_position: monotonically increasing integer starting at 0
|
|
8
|
+
*
|
|
9
|
+
* This is the structure AlgoVoi's production /compliance/attestation chain
|
|
10
|
+
* exposes (Object Lock COMPLIANCE retention to 2033-05-04).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { sha256Jcs } from './canonicalize.js';
|
|
14
|
+
|
|
15
|
+
export class AuditChainError extends Error {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'AuditChainError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AuditChainRow {
|
|
23
|
+
chain_position: number;
|
|
24
|
+
content_hash: string;
|
|
25
|
+
prev_hash: string | null;
|
|
26
|
+
payload: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build the next row in an audit chain.
|
|
31
|
+
*
|
|
32
|
+
* If prevRow is null, the new row becomes the head (chain_position = 0,
|
|
33
|
+
* prev_hash = null). Otherwise chain_position increments and prev_hash
|
|
34
|
+
* links to prevRow.content_hash.
|
|
35
|
+
*/
|
|
36
|
+
export function appendToChain(
|
|
37
|
+
payload: unknown,
|
|
38
|
+
prevRow: AuditChainRow | null,
|
|
39
|
+
): AuditChainRow {
|
|
40
|
+
let chainPosition: number;
|
|
41
|
+
let prevHash: string | null;
|
|
42
|
+
|
|
43
|
+
if (prevRow === null) {
|
|
44
|
+
chainPosition = 0;
|
|
45
|
+
prevHash = null;
|
|
46
|
+
} else {
|
|
47
|
+
if (
|
|
48
|
+
typeof prevRow.chain_position !== 'number' ||
|
|
49
|
+
!Number.isInteger(prevRow.chain_position) ||
|
|
50
|
+
prevRow.chain_position < 0
|
|
51
|
+
) {
|
|
52
|
+
throw new AuditChainError(
|
|
53
|
+
'prevRow.chain_position must be a non-negative integer',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
typeof prevRow.content_hash !== 'string' ||
|
|
58
|
+
prevRow.content_hash.length === 0
|
|
59
|
+
) {
|
|
60
|
+
throw new AuditChainError(
|
|
61
|
+
'prevRow.content_hash must be a non-empty string',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
chainPosition = prevRow.chain_position + 1;
|
|
65
|
+
prevHash = prevRow.content_hash;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
chain_position: chainPosition,
|
|
70
|
+
content_hash: sha256Jcs(payload),
|
|
71
|
+
prev_hash: prevHash,
|
|
72
|
+
payload,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Verify a chain of audit rows is well-formed and linkage-consistent.
|
|
78
|
+
*
|
|
79
|
+
* Throws AuditChainError on any violation; otherwise returns void.
|
|
80
|
+
*/
|
|
81
|
+
export function verifyAuditChain(rows: readonly AuditChainRow[]): void {
|
|
82
|
+
let expectedPosition = 0;
|
|
83
|
+
let prevContentHash: string | null = null;
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < rows.length; i++) {
|
|
86
|
+
const row = rows[i]!;
|
|
87
|
+
|
|
88
|
+
if (row.chain_position !== expectedPosition) {
|
|
89
|
+
throw new AuditChainError(
|
|
90
|
+
`row ${i}: chain_position ${row.chain_position} != expected ${expectedPosition}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const recomputed = sha256Jcs(row.payload);
|
|
95
|
+
if (row.content_hash !== recomputed) {
|
|
96
|
+
throw new AuditChainError(
|
|
97
|
+
`row ${i}: content_hash mismatch -- stored ${row.content_hash} vs recomputed ${recomputed}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (expectedPosition === 0) {
|
|
102
|
+
if (row.prev_hash !== null) {
|
|
103
|
+
throw new AuditChainError(
|
|
104
|
+
`row 0 (head): prev_hash must be null, got ${JSON.stringify(row.prev_hash)}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
if (row.prev_hash !== prevContentHash) {
|
|
109
|
+
throw new AuditChainError(
|
|
110
|
+
`row ${i}: prev_hash ${JSON.stringify(row.prev_hash)} != previous content_hash ${JSON.stringify(prevContentHash)}`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
prevContentHash = row.content_hash;
|
|
116
|
+
expectedPosition += 1;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
|
|
13
|
+
import canonicalizeLib from 'canonicalize';
|
|
14
|
+
import { createHash } from 'node:crypto';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pin the discipline version. Receipts emitted under this substrate carry
|
|
18
|
+
* this string in their canon_version field so a year-five verifier can pick
|
|
19
|
+
* the correct re-canonicalisation rule from the retained bytes alone.
|
|
20
|
+
*/
|
|
21
|
+
export const CANON_VERSION = 'jcs-rfc8785-v1' as const;
|
|
22
|
+
|
|
23
|
+
export class CanonicalizationError extends Error {
|
|
24
|
+
constructor(message: string) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'CanonicalizationError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Walk obj and enforce AlgoVoi-discipline pre-canonicalisation type rules:
|
|
32
|
+
* - object keys must be strings
|
|
33
|
+
* - only JSON-canonicalisable types are accepted (null, boolean, number,
|
|
34
|
+
* string, array, plain object)
|
|
35
|
+
*
|
|
36
|
+
* Strict-integer-fields enforcement (timestamp_ms etc.) is done at the
|
|
37
|
+
* per-schema layer rather than universally here.
|
|
38
|
+
*/
|
|
39
|
+
function validateTypes(obj: unknown, path = '$'): void {
|
|
40
|
+
if (obj === null) return;
|
|
41
|
+
if (typeof obj === 'boolean') return;
|
|
42
|
+
if (typeof obj === 'number') return;
|
|
43
|
+
if (typeof obj === 'string') 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 as Record<string, unknown>)) {
|
|
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(
|
|
56
|
+
`non-string object key at ${path}: ${typeof k}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
validateTypes(v, `${path}.${k}`);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
throw new CanonicalizationError(
|
|
64
|
+
`non-canonicalisable type at ${path}: ${typeof obj}`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Return the canonical JCS string representation of obj.
|
|
70
|
+
*
|
|
71
|
+
* Applies AlgoVoi-discipline pre-canonicalisation type validation, then
|
|
72
|
+
* delegates to canonicalize (RFC 8785) for the canonical bytes.
|
|
73
|
+
*
|
|
74
|
+
* Throws CanonicalizationError on type-validation failure.
|
|
75
|
+
*/
|
|
76
|
+
export function canonicalize(obj: unknown): string {
|
|
77
|
+
validateTypes(obj);
|
|
78
|
+
const out = canonicalizeLib(obj);
|
|
79
|
+
if (out === undefined) {
|
|
80
|
+
// canonicalize() returns undefined for values that JSON.stringify would
|
|
81
|
+
// also drop (e.g. functions, undefined values at the root). The
|
|
82
|
+
// discipline does not allow those at any level; this is belt-and-braces.
|
|
83
|
+
throw new CanonicalizationError(
|
|
84
|
+
'object is not JCS-canonicalisable (canonicalize returned undefined)',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Return the canonical JCS UTF-8 bytes of obj as a Uint8Array.
|
|
92
|
+
*/
|
|
93
|
+
export function canonicalizeBytes(obj: unknown): Uint8Array {
|
|
94
|
+
return new TextEncoder().encode(canonicalize(obj));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Return the lowercase hex SHA-256 of the JCS canonical bytes of obj.
|
|
99
|
+
*
|
|
100
|
+
* This is the substrate primitive used by action_ref, compliance receipts,
|
|
101
|
+
* audit chain entries, and the composite trust-query algorithm. The string
|
|
102
|
+
* is plain hex without an algorithm prefix.
|
|
103
|
+
*/
|
|
104
|
+
export function sha256Jcs(obj: unknown): string {
|
|
105
|
+
return createHash('sha256').update(canonicalize(obj)).digest('hex');
|
|
106
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
|
|
9
|
+
import { CANON_VERSION } from './canonicalize.js';
|
|
10
|
+
|
|
11
|
+
export const SCREEN_RESULTS = ['ALLOW', 'REFER', 'DENY'] as const;
|
|
12
|
+
export type ScreenResult = (typeof SCREEN_RESULTS)[number];
|
|
13
|
+
|
|
14
|
+
export class ComplianceReceiptError extends Error {
|
|
15
|
+
constructor(message: string) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'ComplianceReceiptError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ComplianceReceipt {
|
|
22
|
+
payer_ref: string;
|
|
23
|
+
screen_result: ScreenResult;
|
|
24
|
+
screen_timestamp_ms: number;
|
|
25
|
+
screen_provider_did: string;
|
|
26
|
+
jurisdiction_flags: string[];
|
|
27
|
+
canon_version: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BuildComplianceReceiptInput {
|
|
31
|
+
payer_ref: string;
|
|
32
|
+
screen_result: string;
|
|
33
|
+
screen_timestamp_ms: number;
|
|
34
|
+
screen_provider_did: string;
|
|
35
|
+
jurisdiction_flags: string[];
|
|
36
|
+
canon_version?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function requireNonEmptyString(field: string, value: unknown): string {
|
|
40
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
41
|
+
throw new ComplianceReceiptError(
|
|
42
|
+
`${field} must be a non-empty string`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function requireIntTimestampMs(value: unknown): number {
|
|
49
|
+
if (typeof value !== 'number') {
|
|
50
|
+
throw new ComplianceReceiptError(
|
|
51
|
+
`screen_timestamp_ms must be epoch-millisecond integer (Substrate Rule 1), got ${typeof value}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (!Number.isFinite(value) || !Number.isInteger(value)) {
|
|
55
|
+
throw new ComplianceReceiptError(
|
|
56
|
+
`screen_timestamp_ms must be epoch-millisecond integer (Substrate Rule 1), got ${value}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (value < 0) {
|
|
60
|
+
throw new ComplianceReceiptError(
|
|
61
|
+
`screen_timestamp_ms must be non-negative, got ${value}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function requireJurisdictionFlags(value: unknown): string[] {
|
|
68
|
+
if (!Array.isArray(value)) {
|
|
69
|
+
throw new ComplianceReceiptError(
|
|
70
|
+
`jurisdiction_flags must be array, got ${typeof value}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
for (let i = 0; i < value.length; i++) {
|
|
74
|
+
const code = value[i];
|
|
75
|
+
if (typeof code !== 'string' || code.length === 0) {
|
|
76
|
+
throw new ComplianceReceiptError(
|
|
77
|
+
`jurisdiction_flags[${i}] must be a non-empty string`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [...value] as string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build a validated compliance receipt object.
|
|
86
|
+
*
|
|
87
|
+
* screen_result must be one of ALLOW, REFER, DENY -- the categorical
|
|
88
|
+
* outcome is load-bearing for downstream regulatory obligations.
|
|
89
|
+
*
|
|
90
|
+
* payer_ref is expected to be a content-addressed identity reference
|
|
91
|
+
* (e.g. "sha256:<hex>") rather than cleartext identity content.
|
|
92
|
+
*
|
|
93
|
+
* jurisdiction_flags is treated as ordered -- ["UK","EU"] and ["EU","UK"]
|
|
94
|
+
* produce distinct canonical bytes per RFC 8785 §3.2.3.
|
|
95
|
+
*/
|
|
96
|
+
export function buildComplianceReceipt(
|
|
97
|
+
input: BuildComplianceReceiptInput,
|
|
98
|
+
): ComplianceReceipt {
|
|
99
|
+
if (!SCREEN_RESULTS.includes(input.screen_result as ScreenResult)) {
|
|
100
|
+
throw new ComplianceReceiptError(
|
|
101
|
+
`screen_result must be one of ${JSON.stringify([...SCREEN_RESULTS])}, got ${JSON.stringify(input.screen_result)}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
payer_ref: requireNonEmptyString('payer_ref', input.payer_ref),
|
|
107
|
+
screen_result: input.screen_result as ScreenResult,
|
|
108
|
+
screen_timestamp_ms: requireIntTimestampMs(input.screen_timestamp_ms),
|
|
109
|
+
screen_provider_did: requireNonEmptyString(
|
|
110
|
+
'screen_provider_did',
|
|
111
|
+
input.screen_provider_did,
|
|
112
|
+
),
|
|
113
|
+
jurisdiction_flags: requireJurisdictionFlags(input.jurisdiction_flags),
|
|
114
|
+
canon_version: requireNonEmptyString(
|
|
115
|
+
'canon_version',
|
|
116
|
+
input.canon_version ?? CANON_VERSION,
|
|
117
|
+
),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
|
|
16
|
+
import { sha256Jcs } from './canonicalize.js';
|
|
17
|
+
|
|
18
|
+
export class CompositeTrustQueryError extends Error {
|
|
19
|
+
constructor(message: string) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'CompositeTrustQueryError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EmitterRow {
|
|
26
|
+
source_id: string;
|
|
27
|
+
// Arbitrary additional fields (score, tier, jurisdictions, etc.)
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
// sig is optional; when present it is stripped before hashing.
|
|
30
|
+
sig?: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stripSig(row: Record<string, unknown>): Record<string, unknown> {
|
|
34
|
+
const { sig, ...rest } = row;
|
|
35
|
+
void sig;
|
|
36
|
+
return rest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function requireSourceId(row: Record<string, unknown>): string {
|
|
40
|
+
const src = row['source_id'];
|
|
41
|
+
if (typeof src !== 'string' || src.length === 0) {
|
|
42
|
+
throw new CompositeTrustQueryError(
|
|
43
|
+
'every emitter row must carry a non-empty string source_id',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return src;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Return the lowercase hex composite_hash for a set of emitter rows.
|
|
51
|
+
*
|
|
52
|
+
* Steps (per PR #2440 §composite-hash):
|
|
53
|
+
* 1. Strip the sig field from each row.
|
|
54
|
+
* 2. Sort rows by source_id (lexicographic).
|
|
55
|
+
* 3. JCS-canonicalise the resulting array.
|
|
56
|
+
* 4. SHA-256 the canonical bytes.
|
|
57
|
+
*
|
|
58
|
+
* Output is plain hex without an algorithm prefix.
|
|
59
|
+
*/
|
|
60
|
+
export function compositeTrustQueryHash(rows: readonly EmitterRow[]): string {
|
|
61
|
+
if (rows.length === 0) {
|
|
62
|
+
throw new CompositeTrustQueryError(
|
|
63
|
+
'composite trust-query requires at least one emitter row',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stripped: Record<string, unknown>[] = [];
|
|
68
|
+
for (const row of rows) {
|
|
69
|
+
if (row === null || typeof row !== 'object' || Array.isArray(row)) {
|
|
70
|
+
throw new CompositeTrustQueryError(
|
|
71
|
+
`emitter row must be an object, got ${Array.isArray(row) ? 'array' : typeof row}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
requireSourceId(row as Record<string, unknown>);
|
|
75
|
+
stripped.push(stripSig(row as Record<string, unknown>));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
stripped.sort((a, b) => {
|
|
79
|
+
const sa = a['source_id'] as string;
|
|
80
|
+
const sb = b['source_id'] as string;
|
|
81
|
+
return sa < sb ? -1 : sa > sb ? 1 : 0;
|
|
82
|
+
});
|
|
83
|
+
return sha256Jcs(stripped);
|
|
84
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
|
|
20
|
+
export {
|
|
21
|
+
CANON_VERSION,
|
|
22
|
+
CanonicalizationError,
|
|
23
|
+
canonicalize,
|
|
24
|
+
canonicalizeBytes,
|
|
25
|
+
sha256Jcs,
|
|
26
|
+
} from './canonicalize.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
ActionRefError,
|
|
30
|
+
type ActionRefInput,
|
|
31
|
+
type ActionRefObject,
|
|
32
|
+
actionRef,
|
|
33
|
+
actionRefObject,
|
|
34
|
+
} from './action-ref.js';
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
CompositeTrustQueryError,
|
|
38
|
+
type EmitterRow,
|
|
39
|
+
compositeTrustQueryHash,
|
|
40
|
+
} from './composite-trust-query.js';
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
SCREEN_RESULTS,
|
|
44
|
+
type ScreenResult,
|
|
45
|
+
ComplianceReceiptError,
|
|
46
|
+
type ComplianceReceipt,
|
|
47
|
+
type BuildComplianceReceiptInput,
|
|
48
|
+
buildComplianceReceipt,
|
|
49
|
+
} from './compliance-receipt.js';
|
|
50
|
+
|
|
51
|
+
export {
|
|
52
|
+
AuditChainError,
|
|
53
|
+
type AuditChainRow,
|
|
54
|
+
appendToChain,
|
|
55
|
+
verifyAuditChain,
|
|
56
|
+
} from './audit-chain.js';
|