@algovoi/substrate 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,8 +66,11 @@ verifyAuditChain([row0, row1]);
66
66
 
67
67
  ## Substrate discipline
68
68
 
69
- This package enforces the AlgoVoi-discipline rules formalised in PR #2436
70
- (`x402-foundation/x402`, three-voice coalition co-signed):
69
+ This package enforces the AlgoVoi-authored substrate-discipline rules
70
+ formalised in [IETF Internet-Draft `draft-hopley-x402-canonicalisation-jcs-v1`](https://datatracker.ietf.org/doc/draft-hopley-x402-canonicalisation-jcs-v1/)
71
+ (Independent Submission, Informational; sole AlgoVoi authorship) and
72
+ proposed for inclusion in `x402-foundation/x402` via [PR #2453](https://github.com/x402-foundation/x402/pull/2453)
73
+ (replaces closed [#2436](https://github.com/x402-foundation/x402/pull/2436)):
71
74
 
72
75
  - **Rule 1.** `timestamp_ms` is an epoch-millisecond integer. Floats, ISO
73
76
  8601 strings, and negative values are rejected at the source-side.
@@ -115,10 +118,21 @@ The reference exhibit for this substrate is AlgoVoi's
115
118
 
116
119
  ## Spec references
117
120
 
118
- - [PR #2436](https://github.com/x402-foundation/x402/pull/2436) -- canonicalisation discipline
119
- - [PR #2440](https://github.com/x402-foundation/x402/pull/2440) -- composite trust-query
120
- - [PR #2434](https://github.com/x402-foundation/x402/pull/2434) -- compliance-receipt-fixture
121
- - [draft-vauban-x402-stark-receipts](https://datatracker.ietf.org/doc/draft-vauban-x402-stark-receipts/)
121
+ - [draft-hopley-x402-canonicalisation-jcs-v1](https://datatracker.ietf.org/doc/draft-hopley-x402-canonicalisation-jcs-v1/) -- IETF I-D (Independent Submission, Informational, sole AlgoVoi authorship). Specifies `urn:x402:canonicalisation:jcs-rfc8785-v1`.
122
+ - [docs.algovoi.co.uk/canonicalisation-substrate](https://docs.algovoi.co.uk/canonicalisation-substrate) -- v1 discipline reference page.
123
+ - [docs.algovoi.co.uk/canonicalisation-substrate-v2](https://docs.algovoi.co.uk/canonicalisation-substrate-v2) -- v2 (PQC-aware) additive successor.
124
+ - [PR #2453](https://github.com/x402-foundation/x402/pull/2453) -- live upstream spec PR for the canonicalisation discipline (sole AlgoVoi authorship; replaces closed #2436).
125
+ - [draft-vauban-x402-stark-receipts](https://datatracker.ietf.org/doc/draft-vauban-x402-stark-receipts/) -- third-party adopter-authored receipt format that anchors to the AlgoVoi canonicalisation discipline.
126
+
127
+ ## Conformance to the canonicalisation discipline
128
+
129
+ This package emits receipts pinned to `canon_version: jcs-rfc8785-v1` in-band. Downstream verifiers (`algovoi-audit-verifier` and any conformant third-party verifier) read the pin to select the canonicalisation rule applied at emission.
130
+
131
+ The pin is the load-bearing primitive for the [Substrate Adopters Registry](https://docs.algovoi.co.uk/adopters): adopters anchoring to this discipline pin the same `canon_version` value in their own publicly-citable artefacts. AlgoVoi maintains the registry as a neutral observer; this package is recorded there as the AlgoVoi reference implementation.
132
+
133
+ ## Substrate adopters
134
+
135
+ AlgoVoi is recorded in the [Substrate Adopters Registry](https://docs.algovoi.co.uk/adopters) as the substrate author (v1 and v2). Parties anchoring their own services or specifications to `canon_version: jcs-rfc8785-v1` are recorded in the registry via the [submission process](https://docs.algovoi.co.uk/adopters#how-to-submit-an-adoption-entry). AlgoVoi validates submissions against the artefact's canonical bytes and adds qualifying entries.
122
136
 
123
137
  ## Licence
124
138
 
package/dist/index.d.ts CHANGED
@@ -21,4 +21,5 @@ export { ActionRefError, type ActionRefInput, type ActionRefObject, actionRef, a
21
21
  export { CompositeTrustQueryError, type EmitterRow, compositeTrustQueryHash, } from './composite-trust-query.js';
22
22
  export { SCREEN_RESULTS, type ScreenResult, ComplianceReceiptError, type ComplianceReceipt, type BuildComplianceReceiptInput, buildComplianceReceipt, } from './compliance-receipt.js';
23
23
  export { AuditChainError, type AuditChainRow, appendToChain, verifyAuditChain, } from './audit-chain.js';
24
+ export { TransactionalError, type TransitionPreimage, type TransitionInput, type TransitionRecord, type TransactionalChain, type TransactionalChainInput, transitionPreimage, transitionHash, buildTransactionalActionChain, } from './transactional.js';
24
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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;AAE1B,OAAO,EACL,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,kBAAkB,EAClB,cAAc,EACd,6BAA6B,GAC9B,MAAM,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -21,4 +21,5 @@ export { ActionRefError, actionRef, actionRefObject, } from './action-ref.js';
21
21
  export { CompositeTrustQueryError, compositeTrustQueryHash, } from './composite-trust-query.js';
22
22
  export { SCREEN_RESULTS, ComplianceReceiptError, buildComplianceReceipt, } from './compliance-receipt.js';
23
23
  export { AuditChainError, appendToChain, verifyAuditChain, } from './audit-chain.js';
24
+ export { TransactionalError, transitionPreimage, transitionHash, buildTransactionalActionChain, } from './transactional.js';
24
25
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"}
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;AAE1B,OAAO,EACL,kBAAkB,EAMlB,kBAAkB,EAClB,cAAc,EACd,6BAA6B,GAC9B,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Transactional action_ref lifecycle (non-normative substrate extension).
3
+ *
4
+ * For actions that traverse multiple state transitions
5
+ * (authorisation -> settlement -> refund; issuance -> execution ->
6
+ * revocation; admission -> review -> close), the action_ref four-field
7
+ * preimage is the STABLE identity anchor across the full lifecycle, and
8
+ * per-transition lifecycle metadata sits OUTSIDE the action_ref preimage.
9
+ *
10
+ * Invariants:
11
+ *
12
+ * 1. action_ref is byte-stable across every transition of the same action.
13
+ * 2. transition_hash is byte-stable for identical preimage tuples.
14
+ * 3. transition_hash differs per state for the same action_ref + timestamps.
15
+ * 4. action_ref and any transition_hash are byte-distinct.
16
+ * 5. All timestamp fields MUST be epoch-millisecond integers per
17
+ * Substrate Rule 2. RFC 3339 string forms are rejected.
18
+ *
19
+ * State value is a non-empty string with no closed enum at the
20
+ * canonicalisation layer, consistent with the scope-field treatment.
21
+ */
22
+ export declare class TransactionalError extends Error {
23
+ constructor(message: string);
24
+ }
25
+ export interface TransitionPreimage {
26
+ action_ref: string;
27
+ state: string;
28
+ transition_timestamp_ms: number;
29
+ authority_verified_at_ms: number;
30
+ revocation_check_at_ms: number;
31
+ }
32
+ export interface TransitionInput {
33
+ state: string;
34
+ transition_timestamp_ms: number;
35
+ authority_verified_at_ms: number;
36
+ revocation_check_at_ms: number;
37
+ }
38
+ export interface TransitionRecord extends TransitionInput {
39
+ transition_hash: string;
40
+ }
41
+ export interface TransactionalChain {
42
+ action_ref: string;
43
+ transitions: TransitionRecord[];
44
+ }
45
+ export interface TransactionalChainInput {
46
+ agent_id: string;
47
+ action_type: string;
48
+ scope: string;
49
+ timestamp_ms: number;
50
+ transitions: TransitionInput[];
51
+ }
52
+ /**
53
+ * Return the validated preimage object for a single transition.
54
+ *
55
+ * The five-field shape is fixed at the substrate layer; the action_ref
56
+ * field cryptographically binds the transition to its action identity.
57
+ */
58
+ export declare function transitionPreimage(input: {
59
+ action_ref: string;
60
+ state: string;
61
+ transition_timestamp_ms: number;
62
+ authority_verified_at_ms: number;
63
+ revocation_check_at_ms: number;
64
+ }): TransitionPreimage;
65
+ /**
66
+ * Return the lowercase hex SHA-256 transition_hash.
67
+ *
68
+ * transition_hash = SHA-256(JCS({action_ref, state,
69
+ * transition_timestamp_ms,
70
+ * authority_verified_at_ms,
71
+ * revocation_check_at_ms}))
72
+ */
73
+ export declare function transitionHash(input: {
74
+ action_ref: string;
75
+ state: string;
76
+ transition_timestamp_ms: number;
77
+ authority_verified_at_ms: number;
78
+ revocation_check_at_ms: number;
79
+ }): string;
80
+ /**
81
+ * Build a transactional action chain.
82
+ *
83
+ * Returns the chain shape:
84
+ * {
85
+ * action_ref: "<lowercase 64-char hex>",
86
+ * transitions: [
87
+ * {
88
+ * state, transition_timestamp_ms,
89
+ * authority_verified_at_ms, revocation_check_at_ms,
90
+ * transition_hash
91
+ * },
92
+ * ...
93
+ * ]
94
+ * }
95
+ *
96
+ * The action_ref is computed once and is byte-stable across every
97
+ * transition. Each transition_hash is bound to the action_ref by
98
+ * including it in the transition preimage. Transitions are processed
99
+ * in the order supplied; the substrate verifies determinism, not
100
+ * lifecycle semantics.
101
+ */
102
+ export declare function buildTransactionalActionChain(input: TransactionalChainInput): TransactionalChain;
103
+ //# sourceMappingURL=transactional.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional.d.ts","sourceRoot":"","sources":["../src/transactional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC;AAgDD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,sBAAsB,EAAE,MAAM,CAAC;CAChC,GAAG,kBAAkB,CAiBrB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,sBAAsB,EAAE,MAAM,CAAC;CAChC,GAAG,MAAM,CAGT;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,uBAAuB,GAC7B,kBAAkB,CAsDpB"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Transactional action_ref lifecycle (non-normative substrate extension).
3
+ *
4
+ * For actions that traverse multiple state transitions
5
+ * (authorisation -> settlement -> refund; issuance -> execution ->
6
+ * revocation; admission -> review -> close), the action_ref four-field
7
+ * preimage is the STABLE identity anchor across the full lifecycle, and
8
+ * per-transition lifecycle metadata sits OUTSIDE the action_ref preimage.
9
+ *
10
+ * Invariants:
11
+ *
12
+ * 1. action_ref is byte-stable across every transition of the same action.
13
+ * 2. transition_hash is byte-stable for identical preimage tuples.
14
+ * 3. transition_hash differs per state for the same action_ref + timestamps.
15
+ * 4. action_ref and any transition_hash are byte-distinct.
16
+ * 5. All timestamp fields MUST be epoch-millisecond integers per
17
+ * Substrate Rule 2. RFC 3339 string forms are rejected.
18
+ *
19
+ * State value is a non-empty string with no closed enum at the
20
+ * canonicalisation layer, consistent with the scope-field treatment.
21
+ */
22
+ import { actionRef } from './action-ref.js';
23
+ import { sha256Jcs } from './canonicalize.js';
24
+ const HEX64_RE = /^[0-9a-f]{64}$/;
25
+ export class TransactionalError extends Error {
26
+ constructor(message) {
27
+ super(message);
28
+ this.name = 'TransactionalError';
29
+ }
30
+ }
31
+ function requireActionRefHex(value) {
32
+ if (typeof value !== 'string') {
33
+ throw new TransactionalError(`action_ref must be string, got ${typeof value}`);
34
+ }
35
+ if (!HEX64_RE.test(value)) {
36
+ throw new TransactionalError('action_ref must be a lowercase 64-character SHA-256 hex string');
37
+ }
38
+ return value;
39
+ }
40
+ function requireState(value) {
41
+ if (typeof value !== 'string') {
42
+ throw new TransactionalError(`state must be string, got ${typeof value}`);
43
+ }
44
+ if (value.length === 0) {
45
+ throw new TransactionalError('state must be a non-empty string');
46
+ }
47
+ return value;
48
+ }
49
+ function requireIntMs(field, value) {
50
+ if (typeof value !== 'number') {
51
+ throw new TransactionalError(`${field} must be epoch-millisecond integer (Substrate Rule 2), got ${typeof value}`);
52
+ }
53
+ if (!Number.isFinite(value)) {
54
+ throw new TransactionalError(`${field} must be a finite number, got ${value}`);
55
+ }
56
+ if (!Number.isInteger(value)) {
57
+ throw new TransactionalError(`${field} must be epoch-millisecond integer (Substrate Rule 2), got float ${value}`);
58
+ }
59
+ if (value < 0) {
60
+ throw new TransactionalError(`${field} must be non-negative, got ${value}`);
61
+ }
62
+ return value;
63
+ }
64
+ /**
65
+ * Return the validated preimage object for a single transition.
66
+ *
67
+ * The five-field shape is fixed at the substrate layer; the action_ref
68
+ * field cryptographically binds the transition to its action identity.
69
+ */
70
+ export function transitionPreimage(input) {
71
+ return {
72
+ action_ref: requireActionRefHex(input.action_ref),
73
+ state: requireState(input.state),
74
+ transition_timestamp_ms: requireIntMs('transition_timestamp_ms', input.transition_timestamp_ms),
75
+ authority_verified_at_ms: requireIntMs('authority_verified_at_ms', input.authority_verified_at_ms),
76
+ revocation_check_at_ms: requireIntMs('revocation_check_at_ms', input.revocation_check_at_ms),
77
+ };
78
+ }
79
+ /**
80
+ * Return the lowercase hex SHA-256 transition_hash.
81
+ *
82
+ * transition_hash = SHA-256(JCS({action_ref, state,
83
+ * transition_timestamp_ms,
84
+ * authority_verified_at_ms,
85
+ * revocation_check_at_ms}))
86
+ */
87
+ export function transitionHash(input) {
88
+ const obj = transitionPreimage(input);
89
+ return sha256Jcs(obj);
90
+ }
91
+ /**
92
+ * Build a transactional action chain.
93
+ *
94
+ * Returns the chain shape:
95
+ * {
96
+ * action_ref: "<lowercase 64-char hex>",
97
+ * transitions: [
98
+ * {
99
+ * state, transition_timestamp_ms,
100
+ * authority_verified_at_ms, revocation_check_at_ms,
101
+ * transition_hash
102
+ * },
103
+ * ...
104
+ * ]
105
+ * }
106
+ *
107
+ * The action_ref is computed once and is byte-stable across every
108
+ * transition. Each transition_hash is bound to the action_ref by
109
+ * including it in the transition preimage. Transitions are processed
110
+ * in the order supplied; the substrate verifies determinism, not
111
+ * lifecycle semantics.
112
+ */
113
+ export function buildTransactionalActionChain(input) {
114
+ if (!Array.isArray(input.transitions)) {
115
+ throw new TransactionalError('transitions must be an array');
116
+ }
117
+ if (input.transitions.length === 0) {
118
+ throw new TransactionalError('transitions array must be non-empty');
119
+ }
120
+ const ar = actionRef({
121
+ agent_id: input.agent_id,
122
+ action_type: input.action_type,
123
+ scope: input.scope,
124
+ timestamp_ms: input.timestamp_ms,
125
+ });
126
+ const records = input.transitions.map((t, i) => {
127
+ if (t === null || typeof t !== 'object') {
128
+ throw new TransactionalError(`transitions[${i}] must be object, got ${typeof t}`);
129
+ }
130
+ const required = [
131
+ 'state',
132
+ 'transition_timestamp_ms',
133
+ 'authority_verified_at_ms',
134
+ 'revocation_check_at_ms',
135
+ ];
136
+ for (const k of required) {
137
+ if (!(k in t)) {
138
+ throw new TransactionalError(`transitions[${i}] missing required field '${k}'`);
139
+ }
140
+ }
141
+ const th = transitionHash({
142
+ action_ref: ar,
143
+ state: t.state,
144
+ transition_timestamp_ms: t.transition_timestamp_ms,
145
+ authority_verified_at_ms: t.authority_verified_at_ms,
146
+ revocation_check_at_ms: t.revocation_check_at_ms,
147
+ });
148
+ return {
149
+ state: t.state,
150
+ transition_timestamp_ms: t.transition_timestamp_ms,
151
+ authority_verified_at_ms: t.authority_verified_at_ms,
152
+ revocation_check_at_ms: t.revocation_check_at_ms,
153
+ transition_hash: th,
154
+ };
155
+ });
156
+ return {
157
+ action_ref: ar,
158
+ transitions: records,
159
+ };
160
+ }
161
+ //# sourceMappingURL=transactional.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactional.js","sourceRoot":"","sources":["../src/transactional.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@algovoi/substrate",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "AlgoVoi agentic-payments substrate -- JCS canonicalisation, action_ref, composite trust-query, compliance receipts, audit chain",
5
5
  "keywords": [
6
6
  "x402",
package/src/index.ts CHANGED
@@ -54,3 +54,15 @@ export {
54
54
  appendToChain,
55
55
  verifyAuditChain,
56
56
  } from './audit-chain.js';
57
+
58
+ export {
59
+ TransactionalError,
60
+ type TransitionPreimage,
61
+ type TransitionInput,
62
+ type TransitionRecord,
63
+ type TransactionalChain,
64
+ type TransactionalChainInput,
65
+ transitionPreimage,
66
+ transitionHash,
67
+ buildTransactionalActionChain,
68
+ } from './transactional.js';
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Transactional action_ref lifecycle (non-normative substrate extension).
3
+ *
4
+ * For actions that traverse multiple state transitions
5
+ * (authorisation -> settlement -> refund; issuance -> execution ->
6
+ * revocation; admission -> review -> close), the action_ref four-field
7
+ * preimage is the STABLE identity anchor across the full lifecycle, and
8
+ * per-transition lifecycle metadata sits OUTSIDE the action_ref preimage.
9
+ *
10
+ * Invariants:
11
+ *
12
+ * 1. action_ref is byte-stable across every transition of the same action.
13
+ * 2. transition_hash is byte-stable for identical preimage tuples.
14
+ * 3. transition_hash differs per state for the same action_ref + timestamps.
15
+ * 4. action_ref and any transition_hash are byte-distinct.
16
+ * 5. All timestamp fields MUST be epoch-millisecond integers per
17
+ * Substrate Rule 2. RFC 3339 string forms are rejected.
18
+ *
19
+ * State value is a non-empty string with no closed enum at the
20
+ * canonicalisation layer, consistent with the scope-field treatment.
21
+ */
22
+
23
+ import { actionRef } from './action-ref.js';
24
+ import { sha256Jcs } from './canonicalize.js';
25
+
26
+ const HEX64_RE = /^[0-9a-f]{64}$/;
27
+
28
+ export class TransactionalError extends Error {
29
+ constructor(message: string) {
30
+ super(message);
31
+ this.name = 'TransactionalError';
32
+ }
33
+ }
34
+
35
+ export interface TransitionPreimage {
36
+ action_ref: string;
37
+ state: string;
38
+ transition_timestamp_ms: number;
39
+ authority_verified_at_ms: number;
40
+ revocation_check_at_ms: number;
41
+ }
42
+
43
+ export interface TransitionInput {
44
+ state: string;
45
+ transition_timestamp_ms: number;
46
+ authority_verified_at_ms: number;
47
+ revocation_check_at_ms: number;
48
+ }
49
+
50
+ export interface TransitionRecord extends TransitionInput {
51
+ transition_hash: string;
52
+ }
53
+
54
+ export interface TransactionalChain {
55
+ action_ref: string;
56
+ transitions: TransitionRecord[];
57
+ }
58
+
59
+ export interface TransactionalChainInput {
60
+ agent_id: string;
61
+ action_type: string;
62
+ scope: string;
63
+ timestamp_ms: number;
64
+ transitions: TransitionInput[];
65
+ }
66
+
67
+ function requireActionRefHex(value: unknown): string {
68
+ if (typeof value !== 'string') {
69
+ throw new TransactionalError(
70
+ `action_ref must be string, got ${typeof value}`,
71
+ );
72
+ }
73
+ if (!HEX64_RE.test(value)) {
74
+ throw new TransactionalError(
75
+ 'action_ref must be a lowercase 64-character SHA-256 hex string',
76
+ );
77
+ }
78
+ return value;
79
+ }
80
+
81
+ function requireState(value: unknown): string {
82
+ if (typeof value !== 'string') {
83
+ throw new TransactionalError(`state must be string, got ${typeof value}`);
84
+ }
85
+ if (value.length === 0) {
86
+ throw new TransactionalError('state must be a non-empty string');
87
+ }
88
+ return value;
89
+ }
90
+
91
+ function requireIntMs(field: string, value: unknown): number {
92
+ if (typeof value !== 'number') {
93
+ throw new TransactionalError(
94
+ `${field} must be epoch-millisecond integer (Substrate Rule 2), got ${typeof value}`,
95
+ );
96
+ }
97
+ if (!Number.isFinite(value)) {
98
+ throw new TransactionalError(
99
+ `${field} must be a finite number, got ${value}`,
100
+ );
101
+ }
102
+ if (!Number.isInteger(value)) {
103
+ throw new TransactionalError(
104
+ `${field} must be epoch-millisecond integer (Substrate Rule 2), got float ${value}`,
105
+ );
106
+ }
107
+ if (value < 0) {
108
+ throw new TransactionalError(`${field} must be non-negative, got ${value}`);
109
+ }
110
+ return value;
111
+ }
112
+
113
+ /**
114
+ * Return the validated preimage object for a single transition.
115
+ *
116
+ * The five-field shape is fixed at the substrate layer; the action_ref
117
+ * field cryptographically binds the transition to its action identity.
118
+ */
119
+ export function transitionPreimage(input: {
120
+ action_ref: string;
121
+ state: string;
122
+ transition_timestamp_ms: number;
123
+ authority_verified_at_ms: number;
124
+ revocation_check_at_ms: number;
125
+ }): TransitionPreimage {
126
+ return {
127
+ action_ref: requireActionRefHex(input.action_ref),
128
+ state: requireState(input.state),
129
+ transition_timestamp_ms: requireIntMs(
130
+ 'transition_timestamp_ms',
131
+ input.transition_timestamp_ms,
132
+ ),
133
+ authority_verified_at_ms: requireIntMs(
134
+ 'authority_verified_at_ms',
135
+ input.authority_verified_at_ms,
136
+ ),
137
+ revocation_check_at_ms: requireIntMs(
138
+ 'revocation_check_at_ms',
139
+ input.revocation_check_at_ms,
140
+ ),
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Return the lowercase hex SHA-256 transition_hash.
146
+ *
147
+ * transition_hash = SHA-256(JCS({action_ref, state,
148
+ * transition_timestamp_ms,
149
+ * authority_verified_at_ms,
150
+ * revocation_check_at_ms}))
151
+ */
152
+ export function transitionHash(input: {
153
+ action_ref: string;
154
+ state: string;
155
+ transition_timestamp_ms: number;
156
+ authority_verified_at_ms: number;
157
+ revocation_check_at_ms: number;
158
+ }): string {
159
+ const obj = transitionPreimage(input);
160
+ return sha256Jcs(obj);
161
+ }
162
+
163
+ /**
164
+ * Build a transactional action chain.
165
+ *
166
+ * Returns the chain shape:
167
+ * {
168
+ * action_ref: "<lowercase 64-char hex>",
169
+ * transitions: [
170
+ * {
171
+ * state, transition_timestamp_ms,
172
+ * authority_verified_at_ms, revocation_check_at_ms,
173
+ * transition_hash
174
+ * },
175
+ * ...
176
+ * ]
177
+ * }
178
+ *
179
+ * The action_ref is computed once and is byte-stable across every
180
+ * transition. Each transition_hash is bound to the action_ref by
181
+ * including it in the transition preimage. Transitions are processed
182
+ * in the order supplied; the substrate verifies determinism, not
183
+ * lifecycle semantics.
184
+ */
185
+ export function buildTransactionalActionChain(
186
+ input: TransactionalChainInput,
187
+ ): TransactionalChain {
188
+ if (!Array.isArray(input.transitions)) {
189
+ throw new TransactionalError('transitions must be an array');
190
+ }
191
+ if (input.transitions.length === 0) {
192
+ throw new TransactionalError('transitions array must be non-empty');
193
+ }
194
+
195
+ const ar = actionRef({
196
+ agent_id: input.agent_id,
197
+ action_type: input.action_type,
198
+ scope: input.scope,
199
+ timestamp_ms: input.timestamp_ms,
200
+ });
201
+
202
+ const records: TransitionRecord[] = input.transitions.map((t, i) => {
203
+ if (t === null || typeof t !== 'object') {
204
+ throw new TransactionalError(
205
+ `transitions[${i}] must be object, got ${typeof t}`,
206
+ );
207
+ }
208
+ const required = [
209
+ 'state',
210
+ 'transition_timestamp_ms',
211
+ 'authority_verified_at_ms',
212
+ 'revocation_check_at_ms',
213
+ ] as const;
214
+ for (const k of required) {
215
+ if (!(k in t)) {
216
+ throw new TransactionalError(
217
+ `transitions[${i}] missing required field '${k}'`,
218
+ );
219
+ }
220
+ }
221
+ const th = transitionHash({
222
+ action_ref: ar,
223
+ state: t.state,
224
+ transition_timestamp_ms: t.transition_timestamp_ms,
225
+ authority_verified_at_ms: t.authority_verified_at_ms,
226
+ revocation_check_at_ms: t.revocation_check_at_ms,
227
+ });
228
+ return {
229
+ state: t.state,
230
+ transition_timestamp_ms: t.transition_timestamp_ms,
231
+ authority_verified_at_ms: t.authority_verified_at_ms,
232
+ revocation_check_at_ms: t.revocation_check_at_ms,
233
+ transition_hash: th,
234
+ };
235
+ });
236
+
237
+ return {
238
+ action_ref: ar,
239
+ transitions: records,
240
+ };
241
+ }