@appforge-ci/core 0.2.3 → 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.
Files changed (125) hide show
  1. package/dist/agent-readiness.d.ts +240 -1
  2. package/dist/agent-readiness.d.ts.map +1 -1
  3. package/dist/agent-readiness.js +304 -1
  4. package/dist/agent-readiness.js.map +1 -1
  5. package/dist/agent-readiness.test.js +277 -19
  6. package/dist/agent-readiness.test.js.map +1 -1
  7. package/dist/api-client.d.ts +80 -3
  8. package/dist/api-client.d.ts.map +1 -1
  9. package/dist/api-client.js +79 -0
  10. package/dist/api-client.js.map +1 -1
  11. package/dist/api-client.test.js +22 -0
  12. package/dist/api-client.test.js.map +1 -1
  13. package/dist/appforge-verify.test.d.ts +2 -0
  14. package/dist/appforge-verify.test.d.ts.map +1 -0
  15. package/dist/appforge-verify.test.js +958 -0
  16. package/dist/appforge-verify.test.js.map +1 -0
  17. package/dist/attestation-keys.d.ts +20 -0
  18. package/dist/attestation-keys.d.ts.map +1 -0
  19. package/dist/attestation-keys.js +27 -0
  20. package/dist/attestation-keys.js.map +1 -0
  21. package/dist/attestation-keys.test.d.ts +2 -0
  22. package/dist/attestation-keys.test.d.ts.map +1 -0
  23. package/dist/attestation-keys.test.js +45 -0
  24. package/dist/attestation-keys.test.js.map +1 -0
  25. package/dist/attestation-signing.d.ts +226 -0
  26. package/dist/attestation-signing.d.ts.map +1 -0
  27. package/dist/attestation-signing.js +276 -0
  28. package/dist/attestation-signing.js.map +1 -0
  29. package/dist/attestation-signing.test.d.ts +2 -0
  30. package/dist/attestation-signing.test.d.ts.map +1 -0
  31. package/dist/attestation-signing.test.js +250 -0
  32. package/dist/attestation-signing.test.js.map +1 -0
  33. package/dist/attestation-workerd.test.d.ts +2 -0
  34. package/dist/attestation-workerd.test.d.ts.map +1 -0
  35. package/dist/attestation-workerd.test.js +183 -0
  36. package/dist/attestation-workerd.test.js.map +1 -0
  37. package/dist/attestation.d.ts +412 -0
  38. package/dist/attestation.d.ts.map +1 -0
  39. package/dist/attestation.js +353 -0
  40. package/dist/attestation.js.map +1 -0
  41. package/dist/attestation.test.d.ts +2 -0
  42. package/dist/attestation.test.d.ts.map +1 -0
  43. package/dist/attestation.test.js +218 -0
  44. package/dist/attestation.test.js.map +1 -0
  45. package/dist/build-vpn.d.ts +20 -0
  46. package/dist/build-vpn.d.ts.map +1 -0
  47. package/dist/build-vpn.js +50 -0
  48. package/dist/build-vpn.js.map +1 -0
  49. package/dist/fleet-vpn.d.ts +64 -0
  50. package/dist/fleet-vpn.d.ts.map +1 -0
  51. package/dist/fleet-vpn.js +88 -0
  52. package/dist/fleet-vpn.js.map +1 -0
  53. package/dist/fleet-vpn.test.d.ts +2 -0
  54. package/dist/fleet-vpn.test.d.ts.map +1 -0
  55. package/dist/fleet-vpn.test.js +69 -0
  56. package/dist/fleet-vpn.test.js.map +1 -0
  57. package/dist/index.d.ts +11 -0
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +11 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/schemas-repo-url.test.d.ts +2 -0
  62. package/dist/schemas-repo-url.test.d.ts.map +1 -0
  63. package/dist/schemas-repo-url.test.js +67 -0
  64. package/dist/schemas-repo-url.test.js.map +1 -0
  65. package/dist/schemas.d.ts +360 -7
  66. package/dist/schemas.d.ts.map +1 -1
  67. package/dist/schemas.js +148 -12
  68. package/dist/schemas.js.map +1 -1
  69. package/dist/ssh-deploy-key.d.ts +59 -0
  70. package/dist/ssh-deploy-key.d.ts.map +1 -0
  71. package/dist/ssh-deploy-key.js +124 -0
  72. package/dist/ssh-deploy-key.js.map +1 -0
  73. package/dist/ssh-deploy-key.test.d.ts +2 -0
  74. package/dist/ssh-deploy-key.test.d.ts.map +1 -0
  75. package/dist/ssh-deploy-key.test.js +117 -0
  76. package/dist/ssh-deploy-key.test.js.map +1 -0
  77. package/dist/types.d.ts +204 -0
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js +0 -5
  80. package/dist/types.js.map +1 -1
  81. package/dist/vpn-attestation-workerd.test.d.ts +2 -0
  82. package/dist/vpn-attestation-workerd.test.d.ts.map +1 -0
  83. package/dist/vpn-attestation-workerd.test.js +184 -0
  84. package/dist/vpn-attestation-workerd.test.js.map +1 -0
  85. package/dist/vpn-attestation.d.ts +1015 -0
  86. package/dist/vpn-attestation.d.ts.map +1 -0
  87. package/dist/vpn-attestation.js +963 -0
  88. package/dist/vpn-attestation.js.map +1 -0
  89. package/dist/vpn-attestation.test.d.ts +2 -0
  90. package/dist/vpn-attestation.test.d.ts.map +1 -0
  91. package/dist/vpn-attestation.test.js +613 -0
  92. package/dist/vpn-attestation.test.js.map +1 -0
  93. package/dist/vpn-egress-presets.d.ts +142 -0
  94. package/dist/vpn-egress-presets.d.ts.map +1 -0
  95. package/dist/vpn-egress-presets.js +318 -0
  96. package/dist/vpn-egress-presets.js.map +1 -0
  97. package/dist/vpn-egress-presets.test.d.ts +2 -0
  98. package/dist/vpn-egress-presets.test.d.ts.map +1 -0
  99. package/dist/vpn-egress-presets.test.js +210 -0
  100. package/dist/vpn-egress-presets.test.js.map +1 -0
  101. package/dist/vpn-egress.d.ts +57 -0
  102. package/dist/vpn-egress.d.ts.map +1 -0
  103. package/dist/vpn-egress.js +126 -0
  104. package/dist/vpn-egress.js.map +1 -0
  105. package/dist/vpn-egress.test.d.ts +2 -0
  106. package/dist/vpn-egress.test.d.ts.map +1 -0
  107. package/dist/vpn-egress.test.js +133 -0
  108. package/dist/vpn-egress.test.js.map +1 -0
  109. package/dist/vpn-sandbox.d.ts +67 -0
  110. package/dist/vpn-sandbox.d.ts.map +1 -0
  111. package/dist/vpn-sandbox.js +22 -0
  112. package/dist/vpn-sandbox.js.map +1 -0
  113. package/dist/vpn-sandbox.test.d.ts +2 -0
  114. package/dist/vpn-sandbox.test.d.ts.map +1 -0
  115. package/dist/vpn-sandbox.test.js +34 -0
  116. package/dist/vpn-sandbox.test.js.map +1 -0
  117. package/dist/vpn-ssh.d.ts +138 -0
  118. package/dist/vpn-ssh.d.ts.map +1 -0
  119. package/dist/vpn-ssh.js +320 -0
  120. package/dist/vpn-ssh.js.map +1 -0
  121. package/dist/vpn-ssh.test.d.ts +2 -0
  122. package/dist/vpn-ssh.test.d.ts.map +1 -0
  123. package/dist/vpn-ssh.test.js +274 -0
  124. package/dist/vpn-ssh.test.js.map +1 -0
  125. package/package.json +1 -1
@@ -0,0 +1,183 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { spawn } from "node:child_process";
4
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
5
+ import { createRequire } from "node:module";
6
+ import { createServer } from "node:net";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ /**
11
+ * Ed25519 signing and verification in the real Workers runtime (workerd, the
12
+ * engine production runs on), not only in Node. PR-J only had this from the
13
+ * documentation; this runs the actual signing module inside workerd, with the
14
+ * compatibility date apps/admin-api deploys with.
15
+ *
16
+ * What it proves, in workerd: WebCrypto `Ed25519` import of a PKCS#8 key (the
17
+ * form `ATTESTATION_SIGNING_KEY` takes), public-key derivation through JWK
18
+ * export (how the signer learns its own key id), signing, verification, and
19
+ * that the bytes equal the pinned vectors an independent Node implementation
20
+ * produced (Ed25519 is deterministic, so any runtime must agree exactly).
21
+ *
22
+ * The workerd binary and esbuild are not direct dependencies: they come with
23
+ * `wrangler` (a root devDependency) and are resolved from there, so the
24
+ * lockfile does not change. workerd is run directly rather than through
25
+ * Miniflare, whose 5.x alpha configuration API is still moving. If they cannot
26
+ * be loaded this skips locally, and FAILS when CI is set, so the check cannot
27
+ * silently disappear from the pipeline.
28
+ */
29
+ const COMPAT_DATE = "2025-04-01"; // apps/admin-api/wrangler.jsonc
30
+ async function loadTooling() {
31
+ try {
32
+ const req = createRequire(import.meta.url);
33
+ const fromWrangler = createRequire(req.resolve("wrangler/package.json"));
34
+ const workerdMod = (await import(fromWrangler.resolve("workerd")));
35
+ const esbuild = (await import(fromWrangler.resolve("esbuild")));
36
+ const build = esbuild.build ?? esbuild.default?.build;
37
+ if (!build || typeof workerdMod.default !== "string")
38
+ return "workerd or esbuild did not expose the expected entry points";
39
+ return { workerd: workerdMod.default, build };
40
+ }
41
+ catch (err) {
42
+ return `wrangler's workerd/esbuild could not be loaded (${err instanceof Error ? err.message : String(err)})`;
43
+ }
44
+ }
45
+ function freePort() {
46
+ return new Promise((resolve, reject) => {
47
+ const srv = createServer();
48
+ srv.once("error", reject);
49
+ srv.listen(0, "127.0.0.1", () => {
50
+ const { port } = srv.address();
51
+ srv.close(() => resolve(port));
52
+ });
53
+ });
54
+ }
55
+ const WORKER_ENTRY = `
56
+ import { ed25519KeyId, ed25519SeedToPkcs8, bytesToBase64, auditCheckpointStatement, signAuditCheckpoint, createEd25519Signer, verifyEd25519, hexToBytes, bytesToHex } from ${JSON.stringify(fileURLToPath(new URL("./attestation.ts", import.meta.url)))};
57
+ import { ATTESTATION_DOMAIN, buildAttestationKeySet, loadAttestationSigner, signWithDomain, verifyAuditCheckpoint, verifyWithDomain } from ${JSON.stringify(fileURLToPath(new URL("./attestation-signing.ts", import.meta.url)))};
58
+
59
+ export default {
60
+ async fetch() {
61
+ const out = {};
62
+ // The same deterministic test key the Node tests pin (derived from a label, never a production key).
63
+ const seed = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode("appforge-test-vector-seed-v1")));
64
+ const pkcs8 = ed25519SeedToPkcs8(seed);
65
+ const secret = bytesToBase64(pkcs8);
66
+
67
+ // RFC 8032 test vector 1: the runtime's Ed25519 is the standard algorithm.
68
+ const rfcSigner = await createEd25519Signer("k", ed25519SeedToPkcs8(hexToBytes("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60")));
69
+ const rfcSig = await rfcSigner.sign(new Uint8Array());
70
+ out.rfc8032 = bytesToHex(rfcSig) === "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
71
+ && (await verifyEd25519(hexToBytes("d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"), new Uint8Array(), rfcSig));
72
+
73
+ // Derive the public key from the PKCS#8 secret exactly as the production signer does (JWK export), and publish it.
74
+ const probe = await loadAttestationSigner(secret, [], new Date("2026-10-02T00:00:00Z"));
75
+ out.unpublishedKeyId = probe.state === "unpublished" ? probe.keyId : probe.state;
76
+ const pub = await crypto.subtle.importKey("pkcs8", pkcs8, { name: "Ed25519" }, true, ["sign"]);
77
+ const jwk = await crypto.subtle.exportKey("jwk", pub);
78
+ const x = jwk.x.replace(/-/g, "+").replace(/_/g, "/");
79
+ const entry = { kid: probe.keyId, alg: "Ed25519", publicKey: x + "=".repeat((4 - (x.length % 4)) % 4), notBefore: "2026-01-01T00:00:00Z", status: "active" };
80
+
81
+ const state = await loadAttestationSigner(secret, [entry], new Date("2026-10-02T00:00:00Z"));
82
+ out.state = state.state;
83
+ out.keyId = state.keyId;
84
+ const keys = await buildAttestationKeySet([entry]);
85
+ out.keySetOk = keys.ok;
86
+
87
+ const stmt = auditCheckpointStatement({ orgId: "org_acme", seq: 7, hash: "ab".repeat(32), issuedAt: "2026-10-02T14:03:50Z" });
88
+ out.checkpointSignature = await signAuditCheckpoint(state.signer, stmt);
89
+ out.checkpointVerifies = (await verifyAuditCheckpoint(keys.keys, { orgId: "org_acme", seq: 7, hash: "ab".repeat(32), issuedAt: "2026-10-02T14:03:50Z", keyId: state.keyId, signature: out.checkpointSignature })).ok;
90
+ out.tamperRejected = (await verifyAuditCheckpoint(keys.keys, { orgId: "org_acme", seq: 8, hash: "ab".repeat(32), issuedAt: "2026-10-02T14:03:50Z", keyId: state.keyId, signature: out.checkpointSignature })).ok === false;
91
+
92
+ const doc = { issuedAt: "2026-10-02T14:03:50Z", subject: { buildId: "bld_1", orgId: "org_acme" } };
93
+ out.attestationSignature = await signWithDomain(state.signer, ATTESTATION_DOMAIN, doc);
94
+ out.attestationVerifies = (await verifyWithDomain(keys.keys, { domain: ATTESTATION_DOMAIN, value: doc, keyId: state.keyId, signature: out.attestationSignature, issuedAt: doc.issuedAt })).ok;
95
+
96
+ // A non-Ed25519 / garbage secret is refused in this runtime too.
97
+ out.garbageRefused = (await loadAttestationSigner("AAAA", [entry])).state === "invalid";
98
+ return Response.json(out);
99
+ },
100
+ };
101
+ `;
102
+ test("Ed25519 signing, key derivation and verification work in workerd and match the pinned vectors", async (t) => {
103
+ const tooling = await loadTooling();
104
+ if (typeof tooling === "string") {
105
+ if (process.env.CI)
106
+ assert.fail(tooling);
107
+ t.skip(tooling);
108
+ return;
109
+ }
110
+ const built = await tooling.build({
111
+ stdin: { contents: WORKER_ENTRY, resolveDir: fileURLToPath(new URL(".", import.meta.url)), sourcefile: "worker-entry.js", loader: "js" },
112
+ bundle: true,
113
+ format: "esm",
114
+ platform: "neutral",
115
+ target: "es2022",
116
+ write: false,
117
+ logLevel: "silent",
118
+ // The sources import each other with ".js" specifiers that name their ".ts" files (NodeNext style).
119
+ plugins: [
120
+ {
121
+ name: "ts-js-specifiers",
122
+ setup(b) {
123
+ const src = fileURLToPath(new URL(".", import.meta.url));
124
+ b.onResolve({ filter: /^\.\.?\/.*\.js$/ }, (args) => {
125
+ if (!args.resolveDir.startsWith(src.replace(/\/$/, "")))
126
+ return undefined; // only this package's own sources (zod etc. resolve normally)
127
+ return { path: fileURLToPath(new URL(args.path.replace(/\.js$/, ".ts"), `file://${args.resolveDir}/`)) };
128
+ });
129
+ },
130
+ },
131
+ ],
132
+ });
133
+ const dir = mkdtempSync(join(tmpdir(), "appforge-workerd-"));
134
+ let child;
135
+ try {
136
+ writeFileSync(join(dir, "worker.mjs"), built.outputFiles[0].text);
137
+ const port = await freePort();
138
+ writeFileSync(join(dir, "config.capnp"), `using Workerd = import "/workerd/workerd.capnp";
139
+ const config :Workerd.Config = (
140
+ services = [(name = "main", worker = .w)],
141
+ sockets = [(name = "http", address = "127.0.0.1:${port}", http = (), service = "main")]
142
+ );
143
+ const w :Workerd.Worker = (
144
+ modules = [(name = "worker", esModule = embed "worker.mjs")],
145
+ compatibilityDate = "${COMPAT_DATE}",
146
+ );
147
+ `);
148
+ child = spawn(tooling.workerd, ["serve", "config.capnp"], { cwd: dir, stdio: ["ignore", "pipe", "pipe"] });
149
+ let log = "";
150
+ child.stdout?.on("data", (d) => (log += d.toString()));
151
+ child.stderr?.on("data", (d) => (log += d.toString()));
152
+ let res;
153
+ for (let i = 0; i < 100 && !res; i++) {
154
+ try {
155
+ res = await fetch(`http://127.0.0.1:${port}/`);
156
+ }
157
+ catch {
158
+ if (child.exitCode !== null)
159
+ assert.fail(`workerd exited early: ${log}`);
160
+ await new Promise((r) => setTimeout(r, 100));
161
+ }
162
+ }
163
+ assert.ok(res, `workerd never answered: ${log}`);
164
+ assert.equal(res.status, 200, log);
165
+ const out = (await res.json());
166
+ assert.equal(out.rfc8032, true, "RFC 8032 vector 1 reproduces in workerd");
167
+ assert.equal(out.state, "ready");
168
+ assert.equal(out.keyId, "fa0e09426c7aabe2", "public-key derivation (JWK export) in workerd yields the same key id as Node");
169
+ assert.equal(out.unpublishedKeyId, "fa0e09426c7aabe2");
170
+ assert.equal(out.keySetOk, true);
171
+ assert.equal(out.checkpointSignature, "WaJ3iUH1znI8fEAV3o/zvtO6LaP9VIpOTcuuzE0PVqFb/DHJ636nUZXal7I1qME9CJcxGiQMb9S28CsmLHO5DQ==", "byte-identical to the signature Node produces");
172
+ assert.equal(out.attestationSignature, "5SPJuifSVWrD32lYkGeUQUGi9WZ9IP4axfjWSj0kDmaz3J9UUiBN99/zApL0zCtXr8cxnDRaSLwcAQuphHfvBA==");
173
+ assert.equal(out.checkpointVerifies, true);
174
+ assert.equal(out.attestationVerifies, true);
175
+ assert.equal(out.tamperRejected, true);
176
+ assert.equal(out.garbageRefused, true);
177
+ }
178
+ finally {
179
+ child?.kill("SIGKILL");
180
+ rmSync(dir, { recursive: true, force: true });
181
+ }
182
+ });
183
+ //# sourceMappingURL=attestation-workerd.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestation-workerd.test.js","sourceRoot":"","sources":["../src/attestation-workerd.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,412 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Tamper-evident audit chain primitives for the regulated-VPN program
4
+ * (PR-J). Pure and runtime-neutral: WebCrypto only (`globalThis.crypto`,
5
+ * present in Cloudflare Workers, Node 20+ and browsers), no `node:crypto`,
6
+ * so the Worker sealer (`@appforge/db`'s `audit-chain.ts`), the future
7
+ * signing module and the offline verifier share one implementation of
8
+ * what is hashed and signed.
9
+ *
10
+ * What the chain is: one append-only hash chain per org. Each entry holds
11
+ * the exact canonical JSON that was hashed (`payload`), so verification
12
+ * never has to re-read a mutable source row.
13
+ *
14
+ * hash(seq 1) = sha256("appforge-audit-v1\n" + genesis(orgId) + "\n" + canonicalJson(payload))
15
+ * hash(seq n) = sha256("appforge-audit-v1\n" + hash(n-1) + "\n" + canonicalJson(payload))
16
+ * genesis(org) = sha256("appforge-audit-genesis-v1:" + orgId)
17
+ *
18
+ * What it is not: on its own the chain proves nothing against someone
19
+ * who can write to the database, because they can recompute every hash.
20
+ * The protection comes from signed checkpoints (`AuditCheckpoint`, signed
21
+ * by a key the database does not hold) and from copies of a head hash
22
+ * that the customer keeps. Until checkpoints are signed and issued
23
+ * (PR-K) this is hash-chaining only.
24
+ */
25
+ /** Prefix of every chain-entry hash input. */
26
+ export declare const AUDIT_ENTRY_DOMAIN = "appforge-audit-v1\n";
27
+ /** `genesis(orgId)` input prefix: one org's chain cannot be spliced into another's. */
28
+ export declare const AUDIT_GENESIS_PREFIX = "appforge-audit-genesis-v1:";
29
+ /** Prefix of every checkpoint signature input: a checkpoint signature can never validate as anything else. */
30
+ export declare const AUDIT_CHECKPOINT_DOMAIN = "appforge-checkpoint-v1\n";
31
+ /**
32
+ * Canonical JSON: the RFC 8785 (JCS) subset this product needs. Object keys
33
+ * sorted by UTF-16 code unit, no whitespace, strings as `JSON.stringify`
34
+ * writes them (identical to RFC 8785's string rules for well-formed text).
35
+ * Numbers must be safe integers: the audit schema has no floats (timestamps
36
+ * are strings, sizes are integer bytes), which removes the one place two
37
+ * implementations could disagree. Throws on `undefined`, floats, NaN,
38
+ * bigint, functions, non-plain objects, sparse arrays, lone surrogates and
39
+ * absurd nesting, so a value that cannot be hashed identically everywhere
40
+ * is rejected rather than hashed differently.
41
+ */
42
+ export declare function canonicalJson(value: unknown): string;
43
+ export declare function bytesToHex(bytes: Uint8Array): string;
44
+ export declare function hexToBytes(hex: string): Uint8Array;
45
+ export declare function bytesToBase64(bytes: Uint8Array): string;
46
+ export declare function base64ToBytes(b64: string): Uint8Array;
47
+ export declare function sha256Hex(data: string | Uint8Array): Promise<string>;
48
+ /** Hash a chain starts from, so `prevHash` of seq 1 is org-specific. */
49
+ export declare function auditGenesisHash(orgId: string): Promise<string>;
50
+ /**
51
+ * Hash of one chain entry. `payload` is the value (or its already-canonical
52
+ * JSON text) that is stored verbatim next to the hash.
53
+ */
54
+ export declare function auditEntryHash(prevHash: string, payload: unknown): Promise<string>;
55
+ /** Snapshot of a `VpnEvent` row, taken when it is sealed. Later changes to the row (e.g. `tunnelId` going NULL when a tunnel is deleted) never change a hashed value. */
56
+ export declare const auditVpnEventDataSchema: z.ZodObject<{
57
+ type: z.ZodString;
58
+ tunnelId: z.ZodNullable<z.ZodString>;
59
+ buildId: z.ZodNullable<z.ZodString>;
60
+ agentId: z.ZodNullable<z.ZodString>;
61
+ detail: z.ZodNullable<z.ZodString>;
62
+ }, "strict", z.ZodTypeAny, {
63
+ type: string;
64
+ tunnelId: string | null;
65
+ buildId: string | null;
66
+ agentId: string | null;
67
+ detail: string | null;
68
+ }, {
69
+ type: string;
70
+ tunnelId: string | null;
71
+ buildId: string | null;
72
+ agentId: string | null;
73
+ detail: string | null;
74
+ }>;
75
+ /**
76
+ * A chain entry that registers a per-build attestation (PR-M): "this exact document, by this key (or by none),
77
+ * was issued". `docSha256` is the sha256 of the document's canonical JSON, which is what ties the entry to the
78
+ * signed document; the offline verifier requires the entry right after the document's own chain head to carry
79
+ * the document's hash and build id. `keyId` is null for an attestation issued while signing was unavailable.
80
+ */
81
+ export declare const auditAttestationDataSchema: z.ZodObject<{
82
+ buildId: z.ZodString;
83
+ docSha256: z.ZodString;
84
+ keyId: z.ZodNullable<z.ZodString>;
85
+ completeness: z.ZodEnum<["full", "partial", "server_observed_only"]>;
86
+ }, "strict", z.ZodTypeAny, {
87
+ buildId: string;
88
+ docSha256: string;
89
+ keyId: string | null;
90
+ completeness: "full" | "partial" | "server_observed_only";
91
+ }, {
92
+ buildId: string;
93
+ docSha256: string;
94
+ keyId: string | null;
95
+ completeness: "full" | "partial" | "server_observed_only";
96
+ }>;
97
+ export type AuditAttestationData = z.infer<typeof auditAttestationDataSchema>;
98
+ /**
99
+ * A chain entry that records evidence which was expected and is missing, so the absence itself is part of the
100
+ * tamper-evident history (PR-M: a regulated build whose agent never reported).
101
+ */
102
+ export declare const auditGapDataSchema: z.ZodObject<{
103
+ buildId: z.ZodString;
104
+ reason: z.ZodString;
105
+ detail: z.ZodString;
106
+ }, "strict", z.ZodTypeAny, {
107
+ buildId: string;
108
+ detail: string;
109
+ reason: string;
110
+ }, {
111
+ buildId: string;
112
+ detail: string;
113
+ reason: string;
114
+ }>;
115
+ export type AuditGapData = z.infer<typeof auditGapDataSchema>;
116
+ /**
117
+ * What a chain entry's `payload` column holds. The known `kind`s are `vpn_event`, `attestation` and `gap`;
118
+ * a verifier must reject a kind it does not know rather than skip it, and `scripts/appforge-verify.mjs`
119
+ * (KNOWN_KINDS) is kept to this list by a test.
120
+ */
121
+ export declare const auditEntryPayloadSchema: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
122
+ kind: z.ZodLiteral<"vpn_event">;
123
+ data: z.ZodObject<{
124
+ type: z.ZodString;
125
+ tunnelId: z.ZodNullable<z.ZodString>;
126
+ buildId: z.ZodNullable<z.ZodString>;
127
+ agentId: z.ZodNullable<z.ZodString>;
128
+ detail: z.ZodNullable<z.ZodString>;
129
+ }, "strict", z.ZodTypeAny, {
130
+ type: string;
131
+ tunnelId: string | null;
132
+ buildId: string | null;
133
+ agentId: string | null;
134
+ detail: string | null;
135
+ }, {
136
+ type: string;
137
+ tunnelId: string | null;
138
+ buildId: string | null;
139
+ agentId: string | null;
140
+ detail: string | null;
141
+ }>;
142
+ v: z.ZodLiteral<1>;
143
+ seq: z.ZodNumber;
144
+ orgId: z.ZodString;
145
+ /** The source row's id (`VpnEvent.id`), or the attestation / gap id this entry registers. */
146
+ ref: z.ZodString;
147
+ /** When the source event was recorded (UTC). */
148
+ at: z.ZodString;
149
+ }, "strict", z.ZodTypeAny, {
150
+ kind: "vpn_event";
151
+ at: string;
152
+ orgId: string;
153
+ data: {
154
+ type: string;
155
+ tunnelId: string | null;
156
+ buildId: string | null;
157
+ agentId: string | null;
158
+ detail: string | null;
159
+ };
160
+ v: 1;
161
+ seq: number;
162
+ ref: string;
163
+ }, {
164
+ kind: "vpn_event";
165
+ at: string;
166
+ orgId: string;
167
+ data: {
168
+ type: string;
169
+ tunnelId: string | null;
170
+ buildId: string | null;
171
+ agentId: string | null;
172
+ detail: string | null;
173
+ };
174
+ v: 1;
175
+ seq: number;
176
+ ref: string;
177
+ }>, z.ZodObject<{
178
+ kind: z.ZodLiteral<"attestation">;
179
+ data: z.ZodObject<{
180
+ buildId: z.ZodString;
181
+ docSha256: z.ZodString;
182
+ keyId: z.ZodNullable<z.ZodString>;
183
+ completeness: z.ZodEnum<["full", "partial", "server_observed_only"]>;
184
+ }, "strict", z.ZodTypeAny, {
185
+ buildId: string;
186
+ docSha256: string;
187
+ keyId: string | null;
188
+ completeness: "full" | "partial" | "server_observed_only";
189
+ }, {
190
+ buildId: string;
191
+ docSha256: string;
192
+ keyId: string | null;
193
+ completeness: "full" | "partial" | "server_observed_only";
194
+ }>;
195
+ v: z.ZodLiteral<1>;
196
+ seq: z.ZodNumber;
197
+ orgId: z.ZodString;
198
+ /** The source row's id (`VpnEvent.id`), or the attestation / gap id this entry registers. */
199
+ ref: z.ZodString;
200
+ /** When the source event was recorded (UTC). */
201
+ at: z.ZodString;
202
+ }, "strict", z.ZodTypeAny, {
203
+ kind: "attestation";
204
+ at: string;
205
+ orgId: string;
206
+ data: {
207
+ buildId: string;
208
+ docSha256: string;
209
+ keyId: string | null;
210
+ completeness: "full" | "partial" | "server_observed_only";
211
+ };
212
+ v: 1;
213
+ seq: number;
214
+ ref: string;
215
+ }, {
216
+ kind: "attestation";
217
+ at: string;
218
+ orgId: string;
219
+ data: {
220
+ buildId: string;
221
+ docSha256: string;
222
+ keyId: string | null;
223
+ completeness: "full" | "partial" | "server_observed_only";
224
+ };
225
+ v: 1;
226
+ seq: number;
227
+ ref: string;
228
+ }>, z.ZodObject<{
229
+ kind: z.ZodLiteral<"gap">;
230
+ data: z.ZodObject<{
231
+ buildId: z.ZodString;
232
+ reason: z.ZodString;
233
+ detail: z.ZodString;
234
+ }, "strict", z.ZodTypeAny, {
235
+ buildId: string;
236
+ detail: string;
237
+ reason: string;
238
+ }, {
239
+ buildId: string;
240
+ detail: string;
241
+ reason: string;
242
+ }>;
243
+ v: z.ZodLiteral<1>;
244
+ seq: z.ZodNumber;
245
+ orgId: z.ZodString;
246
+ /** The source row's id (`VpnEvent.id`), or the attestation / gap id this entry registers. */
247
+ ref: z.ZodString;
248
+ /** When the source event was recorded (UTC). */
249
+ at: z.ZodString;
250
+ }, "strict", z.ZodTypeAny, {
251
+ kind: "gap";
252
+ at: string;
253
+ orgId: string;
254
+ data: {
255
+ buildId: string;
256
+ detail: string;
257
+ reason: string;
258
+ };
259
+ v: 1;
260
+ seq: number;
261
+ ref: string;
262
+ }, {
263
+ kind: "gap";
264
+ at: string;
265
+ orgId: string;
266
+ data: {
267
+ buildId: string;
268
+ detail: string;
269
+ reason: string;
270
+ };
271
+ v: 1;
272
+ seq: number;
273
+ ref: string;
274
+ }>]>;
275
+ export type AuditEntryPayload = z.infer<typeof auditEntryPayloadSchema>;
276
+ export type AuditVpnEventData = z.infer<typeof auditVpnEventDataSchema>;
277
+ /** The chain's known entry kinds, from the schema above. */
278
+ export declare const AUDIT_ENTRY_KINDS: readonly string[];
279
+ /** What a checkpoint signature covers: "org X's chain had head hash H at seq N when this was issued". */
280
+ export declare const auditCheckpointStatementSchema: z.ZodObject<{
281
+ v: z.ZodLiteral<1>;
282
+ type: z.ZodLiteral<"appforge.audit.checkpoint">;
283
+ orgId: z.ZodString;
284
+ seq: z.ZodNumber;
285
+ hash: z.ZodString;
286
+ issuedAt: z.ZodString;
287
+ }, "strict", z.ZodTypeAny, {
288
+ orgId: string;
289
+ type: "appforge.audit.checkpoint";
290
+ v: 1;
291
+ seq: number;
292
+ hash: string;
293
+ issuedAt: string;
294
+ }, {
295
+ orgId: string;
296
+ type: "appforge.audit.checkpoint";
297
+ v: 1;
298
+ seq: number;
299
+ hash: string;
300
+ issuedAt: string;
301
+ }>;
302
+ export type AuditCheckpointStatement = z.infer<typeof auditCheckpointStatementSchema>;
303
+ /** A stored checkpoint: the statement's fields plus the signature envelope. */
304
+ export declare const auditCheckpointSchema: z.ZodObject<Omit<{
305
+ v: z.ZodLiteral<1>;
306
+ type: z.ZodLiteral<"appforge.audit.checkpoint">;
307
+ orgId: z.ZodString;
308
+ seq: z.ZodNumber;
309
+ hash: z.ZodString;
310
+ issuedAt: z.ZodString;
311
+ }, "type" | "v"> & {
312
+ keyId: z.ZodString;
313
+ /** base64 Ed25519 signature over `auditCheckpointSigningBytes(statement)`. */
314
+ signature: z.ZodString;
315
+ }, "strict", z.ZodTypeAny, {
316
+ orgId: string;
317
+ keyId: string;
318
+ seq: number;
319
+ hash: string;
320
+ issuedAt: string;
321
+ signature: string;
322
+ }, {
323
+ orgId: string;
324
+ keyId: string;
325
+ seq: number;
326
+ hash: string;
327
+ issuedAt: string;
328
+ signature: string;
329
+ }>;
330
+ export type AuditCheckpoint = z.infer<typeof auditCheckpointSchema>;
331
+ export declare function auditCheckpointStatement(input: {
332
+ orgId: string;
333
+ seq: number;
334
+ hash: string;
335
+ issuedAt: string;
336
+ }): AuditCheckpointStatement;
337
+ /** The exact bytes a checkpoint signature covers. */
338
+ export declare function auditCheckpointSigningBytes(statement: AuditCheckpointStatement): Uint8Array;
339
+ /** `VpnEvent.createdAt` is SQLite's `datetime('now')` (`YYYY-MM-DD HH:MM:SS`, UTC). The chain records ISO-8601. */
340
+ export declare function sqliteUtcToIso(createdAt: string): string;
341
+ /** An ISO-8601 UTC string with whole seconds, as the chain and checkpoints use. */
342
+ export declare function toIsoUtcSeconds(date: Date): string;
343
+ export interface AuditChainEntryInput {
344
+ seq: number;
345
+ prevHash: string;
346
+ hash: string;
347
+ /** The stored payload: canonical JSON text (what the sealer writes), or the parsed object. */
348
+ payload: string | Record<string, unknown>;
349
+ }
350
+ export type AuditChainVerification = {
351
+ ok: true;
352
+ headSeq: number;
353
+ headHash: string;
354
+ entries: number;
355
+ } | {
356
+ ok: false;
357
+ problems: string[];
358
+ };
359
+ /**
360
+ * Recomputes a slice of one org's chain from the stored payloads alone.
361
+ *
362
+ * `anchor` is the `(seq, hash)` of the entry just before the slice: omit it
363
+ * for a chain that starts at seq 1 (the genesis hash is then the anchor), or
364
+ * pass the covering checkpoint's `(seq, hash)` for a chain whose oldest
365
+ * entries were pruned. Checks, per entry: contiguous seq, `prevHash` links to
366
+ * the previous hash, the payload is canonical JSON of a known schema, its
367
+ * `seq` and `orgId` match the entry and the org, and the hash recomputes.
368
+ * Reports every problem found rather than the first.
369
+ *
370
+ * Authenticity of the anchor / head is the caller's job (verify a checkpoint
371
+ * signature, or compare to a head the customer kept): this proves only that
372
+ * the entries are internally consistent with them.
373
+ */
374
+ export declare function verifyAuditChain(input: {
375
+ orgId: string;
376
+ anchor?: {
377
+ seq: number;
378
+ hash: string;
379
+ };
380
+ entries: readonly AuditChainEntryInput[];
381
+ }): Promise<AuditChainVerification>;
382
+ /**
383
+ * Signs bytes with a key the database does not hold. The interface exists
384
+ * so the sealer can issue checkpoints without knowing where the key lives;
385
+ * the production implementation (a Worker secret) lives in
386
+ * apps/admin-api/src/lib/audit-signing.ts. Implementations must be Ed25519 (RFC 8032).
387
+ */
388
+ export interface AuditSigner {
389
+ /** Self-certifying key id: see `ed25519KeyId`. */
390
+ readonly keyId: string;
391
+ /**
392
+ * Optional guard: why this key may not vouch for something issued at `issuedAt`
393
+ * (ISO-8601 UTC), or null if it may. Callers that stamp an `issuedAt` check it
394
+ * at that instant, right before signing, and refuse to store a signature a
395
+ * verifier would reject.
396
+ */
397
+ problemAt?(issuedAt: string): string | null;
398
+ sign(message: Uint8Array): Promise<Uint8Array>;
399
+ }
400
+ /** `sha256(raw 32-byte public key)`, first 16 hex characters. A key set cannot mislabel a key because the id is recomputed from it. */
401
+ export declare function ed25519KeyId(rawPublicKey: Uint8Array): Promise<string>;
402
+ /** Wraps a raw 32-byte Ed25519 seed as PKCS#8, the form Workers' `importKey` takes. */
403
+ export declare function ed25519SeedToPkcs8(seed: Uint8Array): Uint8Array;
404
+ /** An `AuditSigner` over a PKCS#8 Ed25519 private key via WebCrypto (`{name: "Ed25519"}`). */
405
+ export declare function createEd25519Signer(keyId: string, pkcs8: Uint8Array): Promise<AuditSigner>;
406
+ /** Verifies an Ed25519 signature against a raw 32-byte public key. Never throws: a malformed key or signature is a `false`. */
407
+ export declare function verifyEd25519(rawPublicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): Promise<boolean>;
408
+ /** Signs a checkpoint statement; returns the base64 signature the `AuditCheckpoint` row stores. */
409
+ export declare function signAuditCheckpoint(signer: AuditSigner, statement: AuditCheckpointStatement): Promise<string>;
410
+ /** Verifies a stored checkpoint's signature against a raw public key. */
411
+ export declare function verifyAuditCheckpointSignature(rawPublicKey: Uint8Array, statement: AuditCheckpointStatement, signatureB64: string): Promise<boolean>;
412
+ //# sourceMappingURL=attestation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attestation.d.ts","sourceRoot":"","sources":["../src/attestation.ts"],"names":[],"mappings":"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"}