@getvda/witness 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,168 @@
1
+ /**
2
+ * FakeWitnessServer — a no-network Witness that speaks the REAL HTTP protocol.
3
+ *
4
+ * This is the fidelity fix: a fake that models the actual two-step
5
+ * prepare → sign → submit flow, so the REAL WitnessHttpClient can be exercised
6
+ * end-to-end against it. It is the same principle as the fake doing real signing,
7
+ * extended to the real protocol:
8
+ *
9
+ * - `prepare` returns `submit` as INSTRUCTION TEXT (exactly as real Witness
10
+ * does) — a client that mistakes it for a path will hit no route and 404.
11
+ * - the seal endpoints VERIFY the Ed25519 proof over the canonical bytes with
12
+ * the key presented at prepare, and reject a record submitted to the wrong
13
+ * record-type endpoint.
14
+ * - any unknown path 404s.
15
+ *
16
+ * That combination makes the class of bug that reached production (treating the
17
+ * `submit` field as a URL) impossible to hide in tests.
18
+ *
19
+ * Pass `.fetch` to WitnessHttpClient as its `fetch` implementation.
20
+ */
21
+ import { verifyEd25519 } from './signer.js';
22
+ import { stableStringify, sha256Hex } from './canonical.js';
23
+ const ENDPOINT_TYPE = {
24
+ '/api/witness/seal/hitl-decision': 'hitl_decision',
25
+ '/api/witness/seal/attestation': 'attestation',
26
+ '/api/witness/seal/agent-action': 'agent_action',
27
+ };
28
+ const SKILL_TYPE = {
29
+ seal_hitl_decision: 'hitl_decision',
30
+ seal_attestation: 'attestation',
31
+ seal_agent_action: 'agent_action',
32
+ };
33
+ function res(status, body) {
34
+ return {
35
+ ok: status >= 200 && status < 300,
36
+ status,
37
+ json: async () => body,
38
+ text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
39
+ };
40
+ }
41
+ export class FakeWitnessServer {
42
+ anchored;
43
+ issuedAt;
44
+ account;
45
+ seq = 0;
46
+ prevHash = null;
47
+ prepared = new Map();
48
+ stored = new Map();
49
+ constructor(config = {}) {
50
+ this.anchored = config.anchored ?? true;
51
+ this.issuedAt = config.issuedAt ?? '2026-07-18T00:00:00.000Z';
52
+ this.account = {
53
+ account_id: 'acct_FAKE0000000000000000000000',
54
+ tier: this.anchored ? 'ANCHORED' : 'SEALED',
55
+ scopes: ['seal', 'read'],
56
+ compliance: this.anchored,
57
+ key_id: 'fake',
58
+ revoked: false,
59
+ expires_at: null,
60
+ ...config.account,
61
+ };
62
+ }
63
+ /** A fetch-compatible entry point. Bind and pass to WitnessHttpClient. */
64
+ fetch = async (input, init) => {
65
+ const raw = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
66
+ const path = new URL(raw).pathname;
67
+ const method = (init?.method ?? 'GET').toUpperCase();
68
+ const body = init?.body ? JSON.parse(String(init.body)) : undefined;
69
+ if (method === 'GET' && path === '/api/witness/whoami')
70
+ return res(200, this.account);
71
+ if (method === 'POST' && path === '/api/witness/prepare')
72
+ return this.prepare(body);
73
+ if (method === 'POST' && ENDPOINT_TYPE[path])
74
+ return this.submit(ENDPOINT_TYPE[path], body);
75
+ if (method === 'POST' && path === '/api/witness/verify')
76
+ return this.verify(body);
77
+ if (method === 'GET' && path.startsWith('/api/witness/records/')) {
78
+ return this.readRecord(decodeURIComponent(path.slice('/api/witness/records/'.length)));
79
+ }
80
+ // Anything else — including a URL built from the `submit` instruction text.
81
+ return res(404, { error: 'not_found', path });
82
+ };
83
+ prepare(body) {
84
+ const recordType = body.skill ? SKILL_TYPE[body.skill] : undefined;
85
+ if (!recordType)
86
+ return res(422, { error: 'unknown_skill', skill: body.skill });
87
+ if (!body.signingPublicKeyJwk?.x)
88
+ return res(422, { error: 'missing_signingPublicKeyJwk' });
89
+ const canonical = stableStringify({ recordType, params: body.params });
90
+ const recordId = `rec_fake_${sha256Hex(canonical).slice(0, 24)}`;
91
+ const record = {
92
+ schema: 'vda.witness.record/1',
93
+ recordId,
94
+ account: this.account.account_id,
95
+ seq: this.seq,
96
+ issuedAt: this.issuedAt,
97
+ signer: {
98
+ keyId: 'did:web:acp.getvda.ai#key-2',
99
+ custody: 'customer-managed',
100
+ algorithm: 'Ed25519',
101
+ publicKeyJwk: body.signingPublicKeyJwk,
102
+ },
103
+ recordType,
104
+ prevHash: this.prevHash,
105
+ [recordType]: body.params,
106
+ };
107
+ this.prepared.set(recordId, { canonical, jwk: body.signingPublicKeyJwk, recordType });
108
+ // `submit` is STRUCTURED ROUTING {method, endpoint}, exactly like real Witness;
109
+ // the prose lives in `submitInstructions` and is never a routing value.
110
+ const endpoint = Object.keys(ENDPOINT_TYPE).find((p) => ENDPOINT_TYPE[p] === recordType);
111
+ return res(200, {
112
+ record,
113
+ canonicalBytes: canonical,
114
+ seq: this.seq,
115
+ prevHash: this.prevHash,
116
+ submit: { method: 'POST', endpoint },
117
+ submitInstructions: 'Ed25519-sign canonicalBytes with your record key; attach the proof; POST { record } to submit.endpoint.',
118
+ });
119
+ }
120
+ submit(endpointType, body) {
121
+ const record = body.record;
122
+ if (!record?.recordId)
123
+ return res(422, { error: 'missing_record' });
124
+ const prep = this.prepared.get(record.recordId);
125
+ if (!prep)
126
+ return res(404, { error: 'no_prepared_record', recordId: record.recordId });
127
+ // Fidelity: the record must be submitted to the endpoint matching its type.
128
+ if (prep.recordType !== endpointType) {
129
+ return res(400, { error: 'wrong_endpoint', expected: prep.recordType, got: endpointType });
130
+ }
131
+ if (!record.proof?.signature)
132
+ return res(422, { error: 'missing_proof' });
133
+ if (!verifyEd25519(prep.canonical, record.proof.signature, prep.jwk)) {
134
+ return res(400, { error: 'signature_invalid' });
135
+ }
136
+ this.seq += 1;
137
+ this.prevHash = sha256Hex(prep.canonical);
138
+ this.stored.set(record.recordId, { record, canonical: prep.canonical, jwk: prep.jwk });
139
+ return res(200, {
140
+ record,
141
+ bodyHash: sha256Hex(prep.canonical),
142
+ sealed: true,
143
+ anchored: this.anchored,
144
+ sealedState: this.anchored ? 'anchored' : 'not_anchored',
145
+ });
146
+ }
147
+ verify(body) {
148
+ const record = body.record;
149
+ const entry = record?.recordId ? this.stored.get(record.recordId) : undefined;
150
+ if (!entry || !record?.proof?.signature) {
151
+ return res(200, { ok: false, signatureValid: false, errors: ['unknown record or no proof'] });
152
+ }
153
+ const signatureValid = verifyEd25519(entry.canonical, record.proof.signature, entry.jwk);
154
+ return res(200, { ok: signatureValid, bodyHash: sha256Hex(entry.canonical), signatureValid });
155
+ }
156
+ readRecord(id) {
157
+ const entry = this.stored.get(id);
158
+ if (!entry)
159
+ return res(404, { error: 'not_found', recordId: id });
160
+ return res(200, {
161
+ record: entry.record,
162
+ bodyHash: sha256Hex(entry.canonical),
163
+ signatureValid: true,
164
+ anchorState: this.anchored ? 'ANCHORED_VALID' : 'not_anchored',
165
+ });
166
+ }
167
+ }
168
+ //# sourceMappingURL=fake-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-server.js","sourceRoot":"","sources":["../src/fake-server.ts"],"names":[],"mappings":"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"}
package/dist/fake.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * FakeWitnessClient — a no-network Witness for tests and the reference loop.
3
+ *
4
+ * FAITHFUL BY CONSTRUCTION: it is the REAL WitnessHttpClient wired to an
5
+ * in-process FakeWitnessServer that speaks the real two-step protocol. So every
6
+ * consumer of the fake exercises the actual prepare → sign → submit flow (with
7
+ * real Ed25519 signing and server-side signature verification) — the same
8
+ * principle as the fake doing real signing, now extended to the real protocol.
9
+ * The submit-field bug that reached production cannot hide behind this fake.
10
+ *
11
+ * It is deterministic (fixed timestamps, content-addressed record ids) and does
12
+ * no network I/O. It is NOT Witness — no external anchoring; `anchored` is a
13
+ * claim of the fake (default true, mirroring ACP's ANCHORED account). Never use
14
+ * in production.
15
+ */
16
+ import { FakeWitnessServer } from './fake-server.js';
17
+ import { stableStringify } from './canonical.js';
18
+ import type { RecordSigner } from './signer.js';
19
+ import type { AgentActionParams, AttestationParams, HitlDecisionParams, SealedRecord, VerifyResult, WhoAmI, WitnessClient, WitnessRecord } from './types.js';
20
+ export interface FakeWitnessConfig {
21
+ readonly signer?: RecordSigner;
22
+ readonly anchored?: boolean;
23
+ readonly account?: Partial<WhoAmI>;
24
+ readonly issuedAt?: string;
25
+ }
26
+ export declare class FakeWitnessClient implements WitnessClient {
27
+ /** All sealed records, for assertions in tests. */
28
+ readonly sealed: SealedRecord[];
29
+ readonly server: FakeWitnessServer;
30
+ private readonly http;
31
+ constructor(config?: FakeWitnessConfig);
32
+ sealHitlDecision(params: HitlDecisionParams): Promise<SealedRecord>;
33
+ sealAttestation(params: AttestationParams): Promise<SealedRecord>;
34
+ sealAgentAction(params: AgentActionParams): Promise<SealedRecord>;
35
+ whoami(): Promise<WhoAmI>;
36
+ verify(record: WitnessRecord): Promise<VerifyResult>;
37
+ private record;
38
+ }
39
+ export { stableStringify };
40
+ //# sourceMappingURL=fake.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake.d.ts","sourceRoot":"","sources":["../src/fake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAmB,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,aAAa,EACb,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,iBAAkB,YAAW,aAAa;IACrD,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,CAAM;IACrC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;gBAE7B,MAAM,GAAE,iBAAsB;IAgBpC,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAGnE,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;IAGjE,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;IAGjE,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAGzB,MAAM,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAI1D,OAAO,CAAC,MAAM;CAIf;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
package/dist/fake.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * FakeWitnessClient — a no-network Witness for tests and the reference loop.
3
+ *
4
+ * FAITHFUL BY CONSTRUCTION: it is the REAL WitnessHttpClient wired to an
5
+ * in-process FakeWitnessServer that speaks the real two-step protocol. So every
6
+ * consumer of the fake exercises the actual prepare → sign → submit flow (with
7
+ * real Ed25519 signing and server-side signature verification) — the same
8
+ * principle as the fake doing real signing, now extended to the real protocol.
9
+ * The submit-field bug that reached production cannot hide behind this fake.
10
+ *
11
+ * It is deterministic (fixed timestamps, content-addressed record ids) and does
12
+ * no network I/O. It is NOT Witness — no external anchoring; `anchored` is a
13
+ * claim of the fake (default true, mirroring ACP's ANCHORED account). Never use
14
+ * in production.
15
+ */
16
+ import { WitnessHttpClient } from './client.js';
17
+ import { FakeWitnessServer } from './fake-server.js';
18
+ import { ephemeralSigner, stableStringify } from './canonical.js';
19
+ export class FakeWitnessClient {
20
+ /** All sealed records, for assertions in tests. */
21
+ sealed = [];
22
+ server;
23
+ http;
24
+ constructor(config = {}) {
25
+ const issuedAt = config.issuedAt ?? '2026-07-18T00:00:00.000Z';
26
+ this.server = new FakeWitnessServer({
27
+ anchored: config.anchored ?? true,
28
+ issuedAt,
29
+ ...(config.account ? { account: config.account } : {}),
30
+ });
31
+ this.http = new WitnessHttpClient({
32
+ baseUrl: 'https://fake.witness.local',
33
+ getApiKey: () => 'wtn.fake.fake',
34
+ signer: config.signer ?? ephemeralSigner(),
35
+ fetch: this.server.fetch,
36
+ now: () => issuedAt,
37
+ });
38
+ }
39
+ async sealHitlDecision(params) {
40
+ return this.record(await this.http.sealHitlDecision(params));
41
+ }
42
+ async sealAttestation(params) {
43
+ return this.record(await this.http.sealAttestation(params));
44
+ }
45
+ async sealAgentAction(params) {
46
+ return this.record(await this.http.sealAgentAction(params));
47
+ }
48
+ async whoami() {
49
+ return this.http.whoami();
50
+ }
51
+ async verify(record) {
52
+ return this.http.verify(record);
53
+ }
54
+ record(sealed) {
55
+ this.sealed.push(sealed);
56
+ return sealed;
57
+ }
58
+ }
59
+ export { stableStringify };
60
+ //# sourceMappingURL=fake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake.js","sourceRoot":"","sources":["../src/fake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAoBlE,MAAM,OAAO,iBAAiB;IAC5B,mDAAmD;IAC1C,MAAM,GAAmB,EAAE,CAAC;IAC5B,MAAM,CAAoB;IAClB,IAAI,CAAoB;IAEzC,YAAY,SAA4B,EAAE;QACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,0BAA0B,CAAC;QAC/D,IAAI,CAAC,MAAM,GAAG,IAAI,iBAAiB,CAAC;YAClC,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;YACjC,QAAQ;YACR,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,IAAI,iBAAiB,CAAC;YAChC,OAAO,EAAE,4BAA4B;YACrC,SAAS,EAAE,GAAG,EAAE,CAAC,eAAe;YAChC,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,eAAe,EAAE;YAC1C,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;YACxB,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ;SACpB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,MAA0B;QAC/C,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/D,CAAC;IACD,KAAK,CAAC,eAAe,CAAC,MAAyB;QAC7C,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,CAAC,eAAe,CAAC,MAAyB;QAC7C,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,MAAqB;QAChC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAEO,MAAM,CAAC,MAAoB;QACjC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @getvda/witness — Witness sealing substrate for ACP.
3
+ *
4
+ * Real HTTP client (customer-managed custody: prepare → sign → submit) + a
5
+ * no-network fake (faithful: really signs, really verifies). Same
6
+ * substrate-with-a-fake pattern as the git provider, so the approval loop runs
7
+ * before ACP's Witness account exists.
8
+ */
9
+ export type { Actor, GoverningClause, Evidence, Disposition, HitlDecisionParams, AttestationParams, AgentActionParams, RecordProof, WitnessRecord, SealedRecord, WhoAmI, VerifyResult, WitnessClient, } from './types.js';
10
+ export { Ed25519RecordSigner, verifyEd25519, type RecordSigner, type PublicJwk } from './signer.js';
11
+ export { WitnessHttpClient, WitnessError, type WitnessHttpConfig, type WitnessErrorCode, } from './client.js';
12
+ export { FakeWitnessClient, stableStringify, type FakeWitnessConfig } from './fake.js';
13
+ export { FakeWitnessServer, type FakeWitnessServerConfig } from './fake-server.js';
14
+ export { renewApiKey, ControllerRenewingApiKey, type RenewedKey, type RenewOptions, } from './renew.js';
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,KAAK,EACL,eAAe,EACf,QAAQ,EACR,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,MAAM,EACN,YAAY,EACZ,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAEpG,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,KAAK,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEnF,OAAO,EACL,WAAW,EACX,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @getvda/witness — Witness sealing substrate for ACP.
3
+ *
4
+ * Real HTTP client (customer-managed custody: prepare → sign → submit) + a
5
+ * no-network fake (faithful: really signs, really verifies). Same
6
+ * substrate-with-a-fake pattern as the git provider, so the approval loop runs
7
+ * before ACP's Witness account exists.
8
+ */
9
+ export { Ed25519RecordSigner, verifyEd25519 } from './signer.js';
10
+ export { WitnessHttpClient, WitnessError, } from './client.js';
11
+ export { FakeWitnessClient, stableStringify } from './fake.js';
12
+ export { FakeWitnessServer } from './fake-server.js';
13
+ export { renewApiKey, ControllerRenewingApiKey, } from './renew.js';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkBH,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAqC,MAAM,aAAa,CAAC;AAEpG,OAAO,EACL,iBAAiB,EACjB,YAAY,GAGb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAA0B,MAAM,WAAW,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAgC,MAAM,kBAAkB,CAAC;AAEnF,OAAO,EACL,WAAW,EACX,wBAAwB,GAGzB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Renew-from-controller (challenge-response).
3
+ *
4
+ * ACP's runtime should NOT depend on a standing API key. Instead it proves
5
+ * control of the account by signing a Witness challenge with its CONTROLLER key
6
+ * and receives a fresh, short-TTL API key. `ControllerRenewingApiKey` caches that
7
+ * key and re-renews before expiry, so the Bearer used on seal calls is always
8
+ * freshly minted from the controller key — a leaked bootstrap key is not the
9
+ * thing the runtime relies on.
10
+ *
11
+ * Contract (confirmed against the live OpenAPI, 2026-07-18):
12
+ * POST /api/witness/renew/challenge { accountId } → { nonce, expiresInSec, sign }
13
+ * sign `vda.witness.renew/1|<accountId>|<nonce>` raw Ed25519 (base64url) with the controller key
14
+ * POST /api/witness/renew { accountId, nonce, signature } → { apiKey, keyId, keyExpiresAt, keyTtlSec }
15
+ */
16
+ import type { RecordSigner } from './signer.js';
17
+ export interface RenewedKey {
18
+ readonly apiKey: string;
19
+ readonly accountId: string;
20
+ readonly keyId: string;
21
+ readonly keyExpiresAt: string;
22
+ readonly keyTtlSec: number;
23
+ }
24
+ export interface RenewOptions {
25
+ readonly accountId: string;
26
+ /** ACP's controller key signer (Ed25519 over UTF-8 bytes). */
27
+ readonly controller: RecordSigner;
28
+ readonly baseUrl?: string;
29
+ readonly fetch?: typeof fetch;
30
+ }
31
+ /** One renewal round-trip: challenge → sign with controller → renew. */
32
+ export declare function renewApiKey(opts: RenewOptions): Promise<RenewedKey>;
33
+ /**
34
+ * A caching Bearer provider that renews from the controller key before expiry.
35
+ * Pass `get` (bound) as `getApiKey` to WitnessHttpClient.
36
+ */
37
+ export declare class ControllerRenewingApiKey {
38
+ private readonly opts;
39
+ private cached;
40
+ private readonly skewMs;
41
+ private inflight;
42
+ constructor(opts: RenewOptions & {
43
+ skewSec?: number;
44
+ now?: () => number;
45
+ });
46
+ private now;
47
+ get: () => Promise<string>;
48
+ private renew;
49
+ }
50
+ //# sourceMappingURL=renew.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renew.d.ts","sourceRoot":"","sources":["../src/renew.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC;IAClC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAID,wEAAwE;AACxE,wBAAsB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAsCzE;AAED;;;GAGG;AACH,qBAAa,wBAAwB;IAKvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,MAAM,CAAmD;IACjE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAA8B;gBAEjB,IAAI,EAAE,YAAY,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;KAAE;IAI1F,OAAO,CAAC,GAAG;IAIX,GAAG,QAAa,OAAO,CAAC,MAAM,CAAC,CAQ7B;YAEY,KAAK;CAQpB"}
package/dist/renew.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Renew-from-controller (challenge-response).
3
+ *
4
+ * ACP's runtime should NOT depend on a standing API key. Instead it proves
5
+ * control of the account by signing a Witness challenge with its CONTROLLER key
6
+ * and receives a fresh, short-TTL API key. `ControllerRenewingApiKey` caches that
7
+ * key and re-renews before expiry, so the Bearer used on seal calls is always
8
+ * freshly minted from the controller key — a leaked bootstrap key is not the
9
+ * thing the runtime relies on.
10
+ *
11
+ * Contract (confirmed against the live OpenAPI, 2026-07-18):
12
+ * POST /api/witness/renew/challenge { accountId } → { nonce, expiresInSec, sign }
13
+ * sign `vda.witness.renew/1|<accountId>|<nonce>` raw Ed25519 (base64url) with the controller key
14
+ * POST /api/witness/renew { accountId, nonce, signature } → { apiKey, keyId, keyExpiresAt, keyTtlSec }
15
+ */
16
+ import { WitnessError } from './client.js';
17
+ const DEFAULT_BASE = 'https://witness.getvda.ai';
18
+ /** One renewal round-trip: challenge → sign with controller → renew. */
19
+ export async function renewApiKey(opts) {
20
+ const base = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, '');
21
+ const f = opts.fetch ?? fetch;
22
+ const post = async (path, body) => {
23
+ const res = await f(`${base}${path}`, {
24
+ method: 'POST',
25
+ headers: { 'content-type': 'application/json' },
26
+ body: JSON.stringify(body),
27
+ });
28
+ if (!res.ok) {
29
+ const text = await res.text().catch(() => '');
30
+ throw new WitnessError('server_error', `witness ${path}: ${text || res.statusText}`, res.status);
31
+ }
32
+ return (await res.json());
33
+ };
34
+ const chal = await post('/api/witness/renew/challenge', { accountId: opts.accountId });
35
+ // Sign EXACTLY what the server says to (`sign_payload`), not a reconstruction.
36
+ // Fall back to the documented template only if the server omits it.
37
+ const signStr = chal.sign_payload ?? `vda.witness.renew/1|${opts.accountId}|${chal.nonce}`;
38
+ const signature = await opts.controller.sign(signStr);
39
+ const renewEndpoint = chal.renew?.endpoint ?? '/api/witness/renew';
40
+ return post(renewEndpoint, {
41
+ accountId: opts.accountId,
42
+ nonce: chal.nonce,
43
+ signature,
44
+ });
45
+ }
46
+ /**
47
+ * A caching Bearer provider that renews from the controller key before expiry.
48
+ * Pass `get` (bound) as `getApiKey` to WitnessHttpClient.
49
+ */
50
+ export class ControllerRenewingApiKey {
51
+ opts;
52
+ cached;
53
+ skewMs;
54
+ inflight;
55
+ constructor(opts) {
56
+ this.opts = opts;
57
+ this.skewMs = (opts.skewSec ?? 300) * 1000;
58
+ }
59
+ now() {
60
+ return this.opts.now ? this.opts.now() : Date.now();
61
+ }
62
+ get = async () => {
63
+ if (this.cached && this.now() < this.cached.expiresAtMs - this.skewMs)
64
+ return this.cached.key;
65
+ // Coalesce concurrent renewals.
66
+ if (this.inflight)
67
+ return this.inflight;
68
+ this.inflight = this.renew().finally(() => {
69
+ this.inflight = undefined;
70
+ });
71
+ return this.inflight;
72
+ };
73
+ async renew() {
74
+ const r = await renewApiKey(this.opts);
75
+ const expiresAtMs = r.keyExpiresAt
76
+ ? Date.parse(r.keyExpiresAt)
77
+ : this.now() + (r.keyTtlSec ?? 3600) * 1000;
78
+ this.cached = { key: r.apiKey, expiresAtMs };
79
+ return r.apiKey;
80
+ }
81
+ }
82
+ //# sourceMappingURL=renew.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renew.js","sourceRoot":"","sources":["../src/renew.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Record signing for customer-managed custody.
3
+ *
4
+ * ACP seals with its OWN record-signing key (#key-2) so a record is provable even
5
+ * against Witness (Contract-B custody). Mike generates + holds the private key;
6
+ * at runtime it arrives from Secret Manager and is passed to Ed25519RecordSigner.
7
+ * The public half is published in ACP's DID doc, which is what Witness's
8
+ * `issuer_verified` resolves against.
9
+ */
10
+ export interface PublicJwk {
11
+ readonly kty: 'OKP';
12
+ readonly crv: 'Ed25519';
13
+ readonly x: string;
14
+ }
15
+ export interface RecordSigner {
16
+ readonly publicKeyJwk: PublicJwk;
17
+ readonly keyId?: string;
18
+ /** Sign the exact canonical bytes Witness returned from `prepare`. */
19
+ sign(canonicalBytes: string): Promise<string>;
20
+ }
21
+ /** An Ed25519 signer over a PEM or JWK private key. */
22
+ export declare class Ed25519RecordSigner implements RecordSigner {
23
+ readonly publicKeyJwk: PublicJwk;
24
+ readonly keyId?: string;
25
+ private readonly privateKey;
26
+ constructor(privateKey: string | {
27
+ pem: string;
28
+ } | {
29
+ jwk: object;
30
+ }, keyId?: string);
31
+ sign(canonicalBytes: string): Promise<string>;
32
+ }
33
+ /** Verify an Ed25519 signature over canonical bytes against a public JWK. */
34
+ export declare function verifyEd25519(canonicalBytes: string, signatureB64url: string, jwk: PublicJwk): boolean;
35
+ //# sourceMappingURL=signer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/C;AAYD,uDAAuD;AACvD,qBAAa,mBAAoB,YAAW,YAAY;IACtD,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAY;gBAE3B,UAAU,EAAE,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,KAAK,CAAC,EAAE,MAAM;IAY5E,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAKpD;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAC3B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,GAAG,EAAE,SAAS,GACb,OAAO,CAYT"}
package/dist/signer.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Record signing for customer-managed custody.
3
+ *
4
+ * ACP seals with its OWN record-signing key (#key-2) so a record is provable even
5
+ * against Witness (Contract-B custody). Mike generates + holds the private key;
6
+ * at runtime it arrives from Secret Manager and is passed to Ed25519RecordSigner.
7
+ * The public half is published in ACP's DID doc, which is what Witness's
8
+ * `issuer_verified` resolves against.
9
+ */
10
+ import { createPrivateKey, createPublicKey, sign as nodeSign, verify as nodeVerify, } from 'node:crypto';
11
+ function toPublicJwk(pub) {
12
+ const jwk = pub.export({ format: 'jwk' });
13
+ if (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519' || !jwk.x) {
14
+ throw new Error(`record signer requires an Ed25519 OKP key (got kty=${jwk.kty} crv=${jwk.crv})`);
15
+ }
16
+ return { kty: 'OKP', crv: 'Ed25519', x: jwk.x };
17
+ }
18
+ /** An Ed25519 signer over a PEM or JWK private key. */
19
+ export class Ed25519RecordSigner {
20
+ publicKeyJwk;
21
+ keyId;
22
+ privateKey;
23
+ constructor(privateKey, keyId) {
24
+ if (typeof privateKey === 'string') {
25
+ this.privateKey = createPrivateKey(privateKey);
26
+ }
27
+ else if ('pem' in privateKey) {
28
+ this.privateKey = createPrivateKey(privateKey.pem);
29
+ }
30
+ else {
31
+ this.privateKey = createPrivateKey({ key: privateKey.jwk, format: 'jwk' });
32
+ }
33
+ this.publicKeyJwk = toPublicJwk(createPublicKey(this.privateKey));
34
+ if (keyId !== undefined)
35
+ this.keyId = keyId;
36
+ }
37
+ async sign(canonicalBytes) {
38
+ // Ed25519 takes no digest algorithm (null). Sign the exact UTF-8 bytes.
39
+ const sig = nodeSign(null, Buffer.from(canonicalBytes, 'utf8'), this.privateKey);
40
+ return sig.toString('base64url');
41
+ }
42
+ }
43
+ /** Verify an Ed25519 signature over canonical bytes against a public JWK. */
44
+ export function verifyEd25519(canonicalBytes, signatureB64url, jwk) {
45
+ try {
46
+ const pub = createPublicKey({ key: jwk, format: 'jwk' });
47
+ return nodeVerify(null, Buffer.from(canonicalBytes, 'utf8'), pub, Buffer.from(signatureB64url, 'base64url'));
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ //# sourceMappingURL=signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.js","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,IAAI,IAAI,QAAQ,EAChB,MAAM,IAAI,UAAU,GACrB,MAAM,aAAa,CAAC;AAgBrB,SAAS,WAAW,CAAC,GAAc;IACjC,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAA+C,CAAC;IACxF,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,sDAAsD,GAAG,CAAC,GAAG,QAAQ,GAAG,CAAC,GAAG,GAAG,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;AAClD,CAAC;AAED,uDAAuD;AACvD,MAAM,OAAO,mBAAmB;IACrB,YAAY,CAAY;IACxB,KAAK,CAAU;IACP,UAAU,CAAY;IAEvC,YAAY,UAAsD,EAAE,KAAc;QAChF,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,IAAI,CAAC,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;QACjD,CAAC;aAAM,IAAI,KAAK,IAAI,UAAU,EAAE,CAAC;YAC/B,IAAI,CAAC,UAAU,GAAG,gBAAgB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,UAAU,GAAG,gBAAgB,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,GAAY,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAClE,IAAI,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,cAAsB;QAC/B,wEAAwE;QACxE,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACjF,OAAO,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IACnC,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,UAAU,aAAa,CAC3B,cAAsB,EACtB,eAAuB,EACvB,GAAc;IAEd,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,eAAe,CAAC,EAAE,GAAG,EAAE,GAAY,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAClE,OAAO,UAAU,CACf,IAAI,EACJ,MAAM,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,EACnC,GAAG,EACH,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAC1C,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}