@attestplane/attestplane 0.0.1-alpha.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 ADDED
@@ -0,0 +1,114 @@
1
+ # @attestplane/attestplane — TypeScript SDK
2
+
3
+ Apache-2.0 attestation and audit substrate for AI agent evidence chains.
4
+
5
+ > **Status: alpha (v0.0.1).** Wire format is byte-locked against the Python
6
+ > SDK's [`vectors.json`](../python/tests/conformance/vectors.json) — see
7
+ > [ADR-0002][adr2]. APIs may still change before v0.1.0.
8
+
9
+ See the [project README][project-readme] for background, governance, and
10
+ trademark policy.
11
+
12
+ [project-readme]: https://github.com/attestplane/attestplane
13
+ [adr2]: https://github.com/attestplane/attestplane/blob/main/docs/adr/0002-substrate-data-model-and-hash-chain-v0.md
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ # Pin the alpha explicitly; v0.0.1 is on the 'alpha' dist-tag.
19
+ npm install @attestplane/attestplane@alpha
20
+ # or, equivalent:
21
+ npm install @attestplane/attestplane@0.0.1
22
+ ```
23
+
24
+ `npm install @attestplane/attestplane` without a tag also resolves to
25
+ 0.0.1 today (because it is the only published version) but treat the
26
+ alpha tag as the authoritative pre-release channel until v0.1.0 ships.
27
+
28
+ Requires Node.js ≥ 22.
29
+
30
+ ## Quickstart
31
+
32
+ ```typescript
33
+ import {
34
+ AttestSubstrate,
35
+ makeEventDraft,
36
+ makeSubjectRef,
37
+ } from '@attestplane/attestplane';
38
+
39
+ const sub = new AttestSubstrate();
40
+
41
+ sub.append(
42
+ makeEventDraft({
43
+ event_type: 'ai_decision',
44
+ actor: 'agent://recsys/v1',
45
+ payload: { outcome: 'approved', confidence_bp: 9120 },
46
+ session_id: 'session-2026-05-17-abc',
47
+ subject_ref: makeSubjectRef('sha256_salted', '2c1b...e9'),
48
+ }),
49
+ );
50
+
51
+ console.log(sub.tip()); // { seq: 0, event_hash: Uint8Array(32) [...] }
52
+ console.log(sub.verify().ok); // true
53
+ ```
54
+
55
+ ## Cross-language conformance
56
+
57
+ This SDK is validated against the same `vectors.json` as the Python SDK on
58
+ every CI run. Identical input ⇒ identical `event_hash`. If you compute an
59
+ event hash in this SDK and store it, the Python SDK (and any future Rust SDK)
60
+ will reproduce the exact byte value from the same input.
61
+
62
+ ## What this SDK gives you
63
+
64
+ - An **append-only** audit log with cryptographic integrity (SHA-256 hash chain).
65
+ - Built-in fields designed toward **EU AI Act Art. 12(2)(a)** auditability from day one.
66
+ - Byte-identical canonical format compatible with the Python SDK.
67
+ - Strong **GDPR pseudonymization typing** via `SubjectRef`.
68
+
69
+ ## API conventions
70
+
71
+ Field names use `snake_case` (e.g., `event_type`, `prev_hash`, `subject_ref`)
72
+ to match the canonical wire format exactly. This is a deliberate choice for
73
+ cross-language conformance: the canonical form embeds the field names, so any
74
+ rename here would break the conformance contract. See [`src/types.ts`][types].
75
+
76
+ [types]: src/types.ts
77
+
78
+ ## Restricted JSON profile
79
+
80
+ Payloads must satisfy the restricted profile of ADR-0002:
81
+
82
+ | Allowed in `payload` | Forbidden |
83
+ |---|---|
84
+ | `string` (NFC-normalized UTF-8) | non-NFC strings |
85
+ | `number` (integer, safe range) | floats, `NaN`, `Infinity` |
86
+ | `bigint` (within signed 64-bit range) | bigint outside int64 |
87
+ | `true` / `false` / `null` | — |
88
+ | plain objects (string keys) | non-string keys; duplicate keys |
89
+ | arrays | — |
90
+ | `Uint8Array` (emits as base64url no padding) | other byte types |
91
+ | `Date` (UTC, encoded as RFC 3339 µs `Z`) | invalid Date |
92
+
93
+ Violations throw `CanonicalizationError` at append time.
94
+
95
+ For sub-millisecond precision, encode the timestamp as a string (JS `Date`
96
+ only stores millisecond resolution).
97
+
98
+ ## Development
99
+
100
+ ```bash
101
+ git clone https://github.com/attestplane/attestplane
102
+ cd attestplane/sdk/typescript
103
+ npm install
104
+
105
+ npm run lint # Biome lint + format check
106
+ npm run typecheck # tsc --noEmit strict
107
+ npm test # vitest, includes cross-language conformance
108
+ npm run build # emit dist/
109
+ ```
110
+
111
+ ## License
112
+
113
+ Apache License 2.0. See [LICENSE](https://github.com/attestplane/attestplane/blob/main/LICENSE)
114
+ and [NOTICE](https://github.com/attestplane/attestplane/blob/main/NOTICE).
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Restricted-JCS canonicalization for audit-event hashing (TypeScript SDK).
3
+ *
4
+ * Implements the same restricted JSON profile as `sdk/python/src/attestplane/
5
+ * canonical.py`. Both SDKs MUST produce byte-identical output for the same
6
+ * input; correctness is validated continuously by the cross-language
7
+ * conformance test that replays `sdk/python/tests/conformance/vectors.json`.
8
+ *
9
+ * Restricted profile (per ADR-0002):
10
+ * - Strings are UTF-8 and must be NFC-normalized.
11
+ * - Integers (number or bigint) are limited to the signed 64-bit range.
12
+ * - Floats / NaN / Infinity are forbidden.
13
+ * - Object keys are strings, emitted in code-point order, no duplicates.
14
+ * - Datetimes are RFC 3339 UTC microsecond strings with a `Z` suffix.
15
+ * - Uint8Array is encoded as base64url without padding.
16
+ */
17
+ export declare class CanonicalizationError extends Error {
18
+ constructor(message: string);
19
+ }
20
+ export declare function canonicalize(value: unknown): Uint8Array;
21
+ //# sourceMappingURL=canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AAgBH,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAIvD"}
@@ -0,0 +1,165 @@
1
+ // SPDX-FileCopyrightText: 2026 The Attestplane Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Restricted-JCS canonicalization for audit-event hashing (TypeScript SDK).
5
+ *
6
+ * Implements the same restricted JSON profile as `sdk/python/src/attestplane/
7
+ * canonical.py`. Both SDKs MUST produce byte-identical output for the same
8
+ * input; correctness is validated continuously by the cross-language
9
+ * conformance test that replays `sdk/python/tests/conformance/vectors.json`.
10
+ *
11
+ * Restricted profile (per ADR-0002):
12
+ * - Strings are UTF-8 and must be NFC-normalized.
13
+ * - Integers (number or bigint) are limited to the signed 64-bit range.
14
+ * - Floats / NaN / Infinity are forbidden.
15
+ * - Object keys are strings, emitted in code-point order, no duplicates.
16
+ * - Datetimes are RFC 3339 UTC microsecond strings with a `Z` suffix.
17
+ * - Uint8Array is encoded as base64url without padding.
18
+ */
19
+ const ASCII_CONTROL_LIMIT = 0x20;
20
+ const INT64_MIN_BIGINT = -(2n ** 63n);
21
+ const INT64_MAX_BIGINT = 2n ** 63n - 1n;
22
+ const ESCAPES = new Map([
23
+ [0x08, '\\b'],
24
+ [0x09, '\\t'],
25
+ [0x0a, '\\n'],
26
+ [0x0c, '\\f'],
27
+ [0x0d, '\\r'],
28
+ [0x22, '\\"'],
29
+ [0x5c, '\\\\'],
30
+ ]);
31
+ export class CanonicalizationError extends Error {
32
+ constructor(message) {
33
+ super(message);
34
+ this.name = 'CanonicalizationError';
35
+ }
36
+ }
37
+ export function canonicalize(value) {
38
+ const out = [];
39
+ emit(value, out, '$');
40
+ return new TextEncoder().encode(out.join(''));
41
+ }
42
+ function emit(value, out, path) {
43
+ if (value === null || value === undefined) {
44
+ out.push('null');
45
+ return;
46
+ }
47
+ if (typeof value === 'boolean') {
48
+ out.push(value ? 'true' : 'false');
49
+ return;
50
+ }
51
+ if (typeof value === 'number') {
52
+ if (!Number.isFinite(value)) {
53
+ throw new CanonicalizationError(`${path}: non-finite numbers (NaN/Infinity) are forbidden in canonical payloads`);
54
+ }
55
+ if (!Number.isInteger(value)) {
56
+ throw new CanonicalizationError(`${path}: float values are forbidden in canonical payloads (use integers, base64-encoded bytes, or string representations)`);
57
+ }
58
+ // JS Number safely represents integers up to 2^53 - 1. Anything outside
59
+ // the signed 64-bit range would be expressed as bigint by the caller.
60
+ out.push(value.toString());
61
+ return;
62
+ }
63
+ if (typeof value === 'bigint') {
64
+ if (value < INT64_MIN_BIGINT || value > INT64_MAX_BIGINT) {
65
+ throw new CanonicalizationError(`${path}: integer ${value} outside signed 64-bit range`);
66
+ }
67
+ out.push(value.toString());
68
+ return;
69
+ }
70
+ if (typeof value === 'string') {
71
+ emitString(value, out, path);
72
+ return;
73
+ }
74
+ if (value instanceof Uint8Array) {
75
+ emitString(toBase64UrlNoPad(value), out, path);
76
+ return;
77
+ }
78
+ if (value instanceof Date) {
79
+ emitDate(value, out, path);
80
+ return;
81
+ }
82
+ if (Array.isArray(value)) {
83
+ emitArray(value, out, path);
84
+ return;
85
+ }
86
+ if (typeof value === 'object') {
87
+ emitObject(value, out, path);
88
+ return;
89
+ }
90
+ throw new CanonicalizationError(`${path}: unsupported type ${typeof value} in canonical payload`);
91
+ }
92
+ function emitString(value, out, path) {
93
+ if (value.normalize('NFC') !== value) {
94
+ throw new CanonicalizationError(`${path}: string is not Unicode-NFC normalized; normalize before passing to the substrate`);
95
+ }
96
+ out.push('"');
97
+ for (const ch of value) {
98
+ const code = ch.codePointAt(0);
99
+ if (code === undefined)
100
+ continue;
101
+ const mapped = ESCAPES.get(code);
102
+ if (mapped !== undefined) {
103
+ out.push(mapped);
104
+ }
105
+ else if (code < ASCII_CONTROL_LIMIT) {
106
+ out.push(`\\u${code.toString(16).padStart(4, '0')}`);
107
+ }
108
+ else {
109
+ out.push(ch);
110
+ }
111
+ }
112
+ out.push('"');
113
+ }
114
+ function emitDate(value, out, path) {
115
+ const ms = value.getTime();
116
+ if (Number.isNaN(ms)) {
117
+ throw new CanonicalizationError(`${path}: invalid Date (NaN time value)`);
118
+ }
119
+ // JS Date is always interpreted as UTC milliseconds since epoch, so timezone
120
+ // mismatch is not possible at the API boundary. We zero-pad milliseconds out
121
+ // to six digits to match Python's microsecond formatting; values supplied at
122
+ // sub-millisecond precision must be encoded as strings until a typed
123
+ // Timestamp type is introduced.
124
+ const yyyy = value.getUTCFullYear().toString().padStart(4, '0');
125
+ const mm = (value.getUTCMonth() + 1).toString().padStart(2, '0');
126
+ const dd = value.getUTCDate().toString().padStart(2, '0');
127
+ const hh = value.getUTCHours().toString().padStart(2, '0');
128
+ const mi = value.getUTCMinutes().toString().padStart(2, '0');
129
+ const ss = value.getUTCSeconds().toString().padStart(2, '0');
130
+ const milli = value.getUTCMilliseconds().toString().padStart(3, '0');
131
+ const iso = `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}.${milli}000Z`;
132
+ emitString(iso, out, path);
133
+ }
134
+ function emitObject(value, out, path) {
135
+ out.push('{');
136
+ const keys = Object.keys(value).sort();
137
+ const seen = new Set();
138
+ for (let i = 0; i < keys.length; i++) {
139
+ const key = keys[i];
140
+ if (seen.has(key)) {
141
+ throw new CanonicalizationError(`${path}: duplicate object key ${JSON.stringify(key)}`);
142
+ }
143
+ seen.add(key);
144
+ if (i > 0)
145
+ out.push(',');
146
+ emitString(key, out, `${path}.${key}`);
147
+ out.push(':');
148
+ emit(value[key], out, `${path}.${key}`);
149
+ }
150
+ out.push('}');
151
+ }
152
+ function emitArray(value, out, path) {
153
+ out.push('[');
154
+ for (let i = 0; i < value.length; i++) {
155
+ if (i > 0)
156
+ out.push(',');
157
+ emit(value[i], out, `${path}[${i}]`);
158
+ }
159
+ out.push(']');
160
+ }
161
+ function toBase64UrlNoPad(bytes) {
162
+ const b64 = Buffer.from(bytes).toString('base64');
163
+ return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
164
+ }
165
+ //# sourceMappingURL=canonical.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Pure-function hash-chain primitives for the Attestplane substrate.
3
+ *
4
+ * Mirrors `sdk/python/src/attestplane/hashchain.py`. Pure: no I/O, no global
5
+ * state, no time reads. Container responsibility lives in `substrate.ts`.
6
+ */
7
+ import type { AuditEvent, ChainHead, ChainedEvent, EventDraft } from './types.js';
8
+ export declare const SCHEMA_VERSION = 1;
9
+ export declare const GENESIS_HASH: Uint8Array;
10
+ export interface VerificationResult {
11
+ readonly ok: boolean;
12
+ readonly first_bad_index: number | null;
13
+ readonly reason: string | null;
14
+ }
15
+ export declare function genesisHead(): ChainHead;
16
+ export declare function hashEvent(event: AuditEvent): Uint8Array;
17
+ export interface ChainExtendOptions {
18
+ readonly now: Date;
19
+ readonly event_id?: string;
20
+ }
21
+ export declare function chainExtend(tip: ChainHead, draft: EventDraft, options: ChainExtendOptions): ChainedEvent;
22
+ export declare function verifyChain(events: readonly ChainedEvent[]): VerificationResult;
23
+ export declare function headOf(events: readonly ChainedEvent[]): ChainHead;
24
+ //# sourceMappingURL=hashchain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashchain.d.ts","sourceRoot":"","sources":["../src/hashchain.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAElF,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,YAAY,EAAE,UAA+B,CAAC;AAE3D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,wBAAgB,WAAW,IAAI,SAAS,CAEvC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,CAEvD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC;IACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,WAAW,CACzB,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,kBAAkB,GAC1B,YAAY,CAyBd;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,kBAAkB,CA6B/E;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,SAAS,CAIjE"}
@@ -0,0 +1,91 @@
1
+ // SPDX-FileCopyrightText: 2026 The Attestplane Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Pure-function hash-chain primitives for the Attestplane substrate.
5
+ *
6
+ * Mirrors `sdk/python/src/attestplane/hashchain.py`. Pure: no I/O, no global
7
+ * state, no time reads. Container responsibility lives in `substrate.ts`.
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ import { v7 as uuidv7 } from 'uuid';
11
+ import { canonicalize } from './canonical.js';
12
+ export const SCHEMA_VERSION = 1;
13
+ export const GENESIS_HASH = new Uint8Array(32);
14
+ export function genesisHead() {
15
+ return { seq: -1, event_hash: GENESIS_HASH };
16
+ }
17
+ export function hashEvent(event) {
18
+ return new Uint8Array(createHash('sha256').update(canonicalize(event)).digest());
19
+ }
20
+ export function chainExtend(tip, draft, options) {
21
+ if (Number.isNaN(options.now.getTime())) {
22
+ throw new Error('chainExtend requires a valid Date for now');
23
+ }
24
+ const event_id = options.event_id ?? uuidv7();
25
+ const event = {
26
+ schema_version: SCHEMA_VERSION,
27
+ event_id,
28
+ timestamp: options.now,
29
+ event_type: draft.event_type,
30
+ actor: draft.actor,
31
+ payload: draft.payload,
32
+ subject_ref: draft.subject_ref,
33
+ session_id: draft.session_id,
34
+ reference_db_ref: draft.reference_db_ref,
35
+ matched_input_ref: draft.matched_input_ref,
36
+ human_verifier: draft.human_verifier,
37
+ };
38
+ const event_hash = hashEvent(event);
39
+ return {
40
+ seq: tip.seq + 1,
41
+ prev_hash: tip.event_hash,
42
+ event_hash,
43
+ event,
44
+ };
45
+ }
46
+ export function verifyChain(events) {
47
+ let expectedTip = genesisHead();
48
+ for (let i = 0; i < events.length; i++) {
49
+ const item = events[i];
50
+ if (item.seq !== i) {
51
+ return {
52
+ ok: false,
53
+ first_bad_index: i,
54
+ reason: `seq mismatch at index ${i}: got ${item.seq}, expected ${i}`,
55
+ };
56
+ }
57
+ if (!bytesEqual(item.prev_hash, expectedTip.event_hash)) {
58
+ return {
59
+ ok: false,
60
+ first_bad_index: i,
61
+ reason: `prev_hash mismatch at seq ${i}`,
62
+ };
63
+ }
64
+ const recomputed = hashEvent(item.event);
65
+ if (!bytesEqual(recomputed, item.event_hash)) {
66
+ return {
67
+ ok: false,
68
+ first_bad_index: i,
69
+ reason: `event_hash mismatch at seq ${i}`,
70
+ };
71
+ }
72
+ expectedTip = { seq: item.seq, event_hash: item.event_hash };
73
+ }
74
+ return { ok: true, first_bad_index: null, reason: null };
75
+ }
76
+ export function headOf(events) {
77
+ if (events.length === 0)
78
+ return genesisHead();
79
+ const last = events[events.length - 1];
80
+ return { seq: last.seq, event_hash: last.event_hash };
81
+ }
82
+ function bytesEqual(a, b) {
83
+ if (a.length !== b.length)
84
+ return false;
85
+ for (let i = 0; i < a.length; i++) {
86
+ if (a[i] !== b[i])
87
+ return false;
88
+ }
89
+ return true;
90
+ }
91
+ //# sourceMappingURL=hashchain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hashchain.js","sourceRoot":"","sources":["../src/hashchain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Attestplane — verifiable audit substrate for AI agents.
3
+ *
4
+ * Designed toward EU AI Act Article 12 auditability. Apache-2.0 licensed.
5
+ * See https://github.com/attestplane/attestplane and
6
+ * docs/adr/0002-substrate-data-model-and-hash-chain-v0.md for the design.
7
+ */
8
+ export { CanonicalizationError, canonicalize } from './canonical.js';
9
+ export { GENESIS_HASH, SCHEMA_VERSION, chainExtend, genesisHead, hashEvent, headOf, verifyChain, type ChainExtendOptions, type VerificationResult, } from './hashchain.js';
10
+ export { AttestSubstrate, type AppendOptions } from './substrate.js';
11
+ export { makeEventDraft, makeSubjectRef, type AuditEvent, type ChainHead, type ChainedEvent, type EventDraft, type EventDraftInput, type SubjectRef, type SubjectScheme, } from './types.js';
12
+ export declare const VERSION = "0.0.1-alpha.1";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,MAAM,EACN,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EACL,cAAc,EACd,cAAc,EACd,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,OAAO,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // SPDX-FileCopyrightText: 2026 The Attestplane Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Attestplane — verifiable audit substrate for AI agents.
5
+ *
6
+ * Designed toward EU AI Act Article 12 auditability. Apache-2.0 licensed.
7
+ * See https://github.com/attestplane/attestplane and
8
+ * docs/adr/0002-substrate-data-model-and-hash-chain-v0.md for the design.
9
+ */
10
+ export { CanonicalizationError, canonicalize } from './canonical.js';
11
+ export { GENESIS_HASH, SCHEMA_VERSION, chainExtend, genesisHead, hashEvent, headOf, verifyChain, } from './hashchain.js';
12
+ export { AttestSubstrate } from './substrate.js';
13
+ export { makeEventDraft, makeSubjectRef, } from './types.js';
14
+ export const VERSION = '0.0.1-alpha.1';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,sCAAsC;AACtC;;;;;;GAMG;AAEH,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,MAAM,EACN,WAAW,GAGZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,eAAe,EAAsB,MAAM,gBAAgB,CAAC;AACrE,OAAO,EACL,cAAc,EACd,cAAc,GAQf,MAAM,YAAY,CAAC;AAEpB,MAAM,CAAC,MAAM,OAAO,GAAG,eAAe,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * `AttestSubstrate` — append-only audit-event container.
3
+ *
4
+ * Mirrors `sdk/python/src/attestplane/substrate.py`. Single-threaded by
5
+ * Node's event-loop model; no explicit locking is needed for in-process
6
+ * use. Durable storage and multi-process coordination are out of scope at
7
+ * v0.0.1 (anticipated ADR-0004).
8
+ */
9
+ import { type VerificationResult } from './hashchain.js';
10
+ import type { ChainHead, ChainedEvent, EventDraft } from './types.js';
11
+ export interface AppendOptions {
12
+ readonly now?: Date;
13
+ }
14
+ export declare class AttestSubstrate {
15
+ #private;
16
+ append(draft: EventDraft, options?: AppendOptions): ChainedEvent;
17
+ tip(): ChainHead;
18
+ verify(): VerificationResult;
19
+ get length(): number;
20
+ snapshot(): ChainedEvent[];
21
+ [Symbol.iterator](): IterableIterator<ChainedEvent>;
22
+ static fromEvents(events: readonly ChainedEvent[]): AttestSubstrate;
23
+ }
24
+ //# sourceMappingURL=substrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"substrate.d.ts","sourceRoot":"","sources":["../src/substrate.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EACL,KAAK,kBAAkB,EAKxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEtE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;CACrB;AAED,qBAAa,eAAe;;IAI1B,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY;IAQpE,GAAG,IAAI,SAAS;IAIhB,MAAM,IAAI,kBAAkB;IAI5B,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,QAAQ,IAAI,YAAY,EAAE;IAIzB,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,YAAY,CAAC;IAIpD,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,eAAe;CAUpE"}
@@ -0,0 +1,48 @@
1
+ // SPDX-FileCopyrightText: 2026 The Attestplane Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * `AttestSubstrate` — append-only audit-event container.
5
+ *
6
+ * Mirrors `sdk/python/src/attestplane/substrate.py`. Single-threaded by
7
+ * Node's event-loop model; no explicit locking is needed for in-process
8
+ * use. Durable storage and multi-process coordination are out of scope at
9
+ * v0.0.1 (anticipated ADR-0004).
10
+ */
11
+ import { chainExtend, genesisHead, headOf, verifyChain, } from './hashchain.js';
12
+ export class AttestSubstrate {
13
+ #events = [];
14
+ #tip = genesisHead();
15
+ append(draft, options = {}) {
16
+ const now = options.now ?? new Date();
17
+ const chained = chainExtend(this.#tip, draft, { now });
18
+ this.#events.push(chained);
19
+ this.#tip = { seq: chained.seq, event_hash: chained.event_hash };
20
+ return chained;
21
+ }
22
+ tip() {
23
+ return this.#tip;
24
+ }
25
+ verify() {
26
+ return verifyChain(this.#events);
27
+ }
28
+ get length() {
29
+ return this.#events.length;
30
+ }
31
+ snapshot() {
32
+ return [...this.#events];
33
+ }
34
+ *[Symbol.iterator]() {
35
+ yield* this.#events;
36
+ }
37
+ static fromEvents(events) {
38
+ const result = verifyChain(events);
39
+ if (!result.ok) {
40
+ throw new Error(`cannot rehydrate substrate: ${result.reason ?? 'unknown'}`);
41
+ }
42
+ const instance = new AttestSubstrate();
43
+ instance.#events = [...events];
44
+ instance.#tip = headOf(events);
45
+ return instance;
46
+ }
47
+ }
48
+ //# sourceMappingURL=substrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"substrate.js","sourceRoot":"","sources":["../src/substrate.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,sCAAsC;AACtC;;;;;;;GAOG;AAEH,OAAO,EAEL,WAAW,EACX,WAAW,EACX,MAAM,EACN,WAAW,GACZ,MAAM,gBAAgB,CAAC;AAOxB,MAAM,OAAO,eAAe;IAC1B,OAAO,GAAmB,EAAE,CAAC;IAC7B,IAAI,GAAc,WAAW,EAAE,CAAC;IAEhC,MAAM,CAAC,KAAiB,EAAE,UAAyB,EAAE;QACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC;QACjE,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,GAAG;QACD,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;IAED,MAAM;QACJ,OAAO,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC;QAChB,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,UAAU,CAAC,MAA+B;QAC/C,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC;QACvC,QAAQ,CAAC,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;QAC/B,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Core data types for the Attestplane substrate (TypeScript SDK).
3
+ *
4
+ * Field names are deliberately `snake_case` to match the Python SDK's wire
5
+ * format byte-for-byte. The canonical event-hash includes the field names
6
+ * (per RFC 8785 / restricted JSON profile of ADR-0002), so any rename here
7
+ * would break cross-language conformance against the frozen
8
+ * `sdk/python/tests/conformance/vectors.json`.
9
+ *
10
+ * This is a load-bearing convention. Do not switch to camelCase.
11
+ */
12
+ export type SubjectScheme = 'sha256_salted' | 'opaque' | 'none';
13
+ export interface SubjectRef {
14
+ readonly scheme: SubjectScheme;
15
+ readonly value: string;
16
+ }
17
+ export declare function makeSubjectRef(scheme: SubjectScheme, value: string): SubjectRef;
18
+ export interface EventDraft {
19
+ readonly event_type: string;
20
+ readonly actor: string;
21
+ readonly payload: Record<string, unknown>;
22
+ readonly subject_ref: SubjectRef | null;
23
+ readonly session_id: string | null;
24
+ readonly reference_db_ref: string | null;
25
+ readonly matched_input_ref: string | null;
26
+ readonly human_verifier: SubjectRef | null;
27
+ }
28
+ export interface EventDraftInput {
29
+ event_type: string;
30
+ actor: string;
31
+ payload?: Record<string, unknown>;
32
+ subject_ref?: SubjectRef | null;
33
+ session_id?: string | null;
34
+ reference_db_ref?: string | null;
35
+ matched_input_ref?: string | null;
36
+ human_verifier?: SubjectRef | null;
37
+ }
38
+ export declare function makeEventDraft(input: EventDraftInput): EventDraft;
39
+ export interface AuditEvent {
40
+ readonly schema_version: number;
41
+ readonly event_id: string;
42
+ readonly timestamp: Date;
43
+ readonly event_type: string;
44
+ readonly actor: string;
45
+ readonly payload: Record<string, unknown>;
46
+ readonly subject_ref: SubjectRef | null;
47
+ readonly session_id: string | null;
48
+ readonly reference_db_ref: string | null;
49
+ readonly matched_input_ref: string | null;
50
+ readonly human_verifier: SubjectRef | null;
51
+ }
52
+ export interface ChainedEvent {
53
+ readonly seq: number;
54
+ readonly prev_hash: Uint8Array;
55
+ readonly event_hash: Uint8Array;
56
+ readonly event: AuditEvent;
57
+ }
58
+ export interface ChainHead {
59
+ readonly seq: number;
60
+ readonly event_hash: Uint8Array;
61
+ }
62
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEhE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAQ/E;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,cAAc,EAAE,UAAU,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,WAAW,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,cAAc,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;CACpC;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,UAAU,CAiBjE;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,cAAc,EAAE,UAAU,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,UAAU,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC"}
package/dist/types.js ADDED
@@ -0,0 +1,41 @@
1
+ // SPDX-FileCopyrightText: 2026 The Attestplane Authors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Core data types for the Attestplane substrate (TypeScript SDK).
5
+ *
6
+ * Field names are deliberately `snake_case` to match the Python SDK's wire
7
+ * format byte-for-byte. The canonical event-hash includes the field names
8
+ * (per RFC 8785 / restricted JSON profile of ADR-0002), so any rename here
9
+ * would break cross-language conformance against the frozen
10
+ * `sdk/python/tests/conformance/vectors.json`.
11
+ *
12
+ * This is a load-bearing convention. Do not switch to camelCase.
13
+ */
14
+ export function makeSubjectRef(scheme, value) {
15
+ if (scheme === 'none' && value !== '') {
16
+ throw new Error("SubjectRef scheme 'none' requires empty value");
17
+ }
18
+ if (scheme !== 'none' && value.length === 0) {
19
+ throw new Error(`SubjectRef scheme '${scheme}' requires non-empty value`);
20
+ }
21
+ return { scheme, value };
22
+ }
23
+ export function makeEventDraft(input) {
24
+ if (!input.event_type) {
25
+ throw new Error('EventDraft.event_type must be non-empty');
26
+ }
27
+ if (!input.actor) {
28
+ throw new Error('EventDraft.actor must be non-empty');
29
+ }
30
+ return {
31
+ event_type: input.event_type,
32
+ actor: input.actor,
33
+ payload: input.payload ?? {},
34
+ subject_ref: input.subject_ref ?? null,
35
+ session_id: input.session_id ?? null,
36
+ reference_db_ref: input.reference_db_ref ?? null,
37
+ matched_input_ref: input.matched_input_ref ?? null,
38
+ human_verifier: input.human_verifier ?? null,
39
+ };
40
+ }
41
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,sCAAsC;AACtC;;;;;;;;;;GAUG;AASH,MAAM,UAAU,cAAc,CAAC,MAAqB,EAAE,KAAa;IACjE,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,IAAI,MAAM,KAAK,MAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,4BAA4B,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC;AAwBD,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,IAAI;QACtC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,IAAI;QACpC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,IAAI,IAAI;QAChD,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,IAAI,IAAI;QAClD,cAAc,EAAE,KAAK,CAAC,cAAc,IAAI,IAAI;KAC7C,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@attestplane/attestplane",
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "Apache-2.0 attestation and audit substrate for AI agent evidence chains.",
5
+ "keywords": [
6
+ "audit",
7
+ "compliance",
8
+ "eu-ai-act",
9
+ "dora",
10
+ "gdpr",
11
+ "hash-chain",
12
+ "provenance",
13
+ "transparency"
14
+ ],
15
+ "license": "Apache-2.0",
16
+ "homepage": "https://attestplane.io",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/attestplane/attestplane"
20
+ },
21
+ "bugs": "https://github.com/attestplane/attestplane/issues",
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE",
35
+ "NOTICE"
36
+ ],
37
+ "engines": {
38
+ "node": ">=22"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "lint": "biome check src test",
43
+ "lint:fix": "biome check --write src test",
44
+ "format": "biome format --write src test",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "typecheck": "tsc --noEmit"
48
+ },
49
+ "dependencies": {
50
+ "uuid": "^11.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@biomejs/biome": "^1.9.4",
54
+ "@types/node": "^22.0.0",
55
+ "@types/uuid": "^10.0.0",
56
+ "typescript": "^5.6.0",
57
+ "vitest": "^4.1.6"
58
+ }
59
+ }