witnora 0.10.0 → 0.10.2
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 +54 -9
- package/dist/assurance-contract.js +312 -0
- package/dist/assurance-loop-demo.js +245 -0
- package/dist/authorization-state-machine.js +35 -0
- package/dist/authorization-v02.js +60 -0
- package/dist/canonical-v02.js +197 -0
- package/dist/canonical.js +31 -0
- package/dist/cli.js +118 -0
- package/dist/command-help.js +76 -0
- package/dist/control-plane.js +3 -3
- package/dist/credentials.js +1 -1
- package/dist/design-partner-pilot.js +402 -0
- package/dist/design-partner-v02.js +449 -0
- package/dist/evidence-v02.js +118 -0
- package/dist/generic-eval.js +160 -0
- package/dist/github-design-partner-v02.js +399 -0
- package/dist/github-live-v02.js +45 -0
- package/dist/guided-setup.js +1 -1
- package/dist/index.js +1 -0
- package/dist/offline-verifier.js +375 -0
- package/dist/onboard.js +132 -0
- package/dist/onboarding-templates.js +3 -3
- package/dist/outcome-evaluator.js +51 -0
- package/dist/privacy-manifest.js +273 -0
- package/dist/probe-protocol-v02.js +101 -0
- package/dist/sandbox.js +1 -1
- package/dist/trust-v02.js +111 -0
- package/dist/try.js +1 -1
- package/dist/vendor/design-partner/failure-exercises.mjs +353 -0
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts +8 -0
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts.map +1 -1
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.js +43 -1
- package/dist/vendor/witnora-probe/canonical.d.ts +3 -0
- package/dist/vendor/witnora-probe/canonical.js +39 -0
- package/dist/vendor/witnora-probe/cli.d.ts +2 -0
- package/dist/vendor/witnora-probe/cli.js +36 -0
- package/dist/vendor/witnora-probe/config.d.ts +2 -0
- package/dist/vendor/witnora-probe/config.js +63 -0
- package/dist/vendor/witnora-probe/credential-resolver.d.ts +6 -0
- package/dist/vendor/witnora-probe/credential-resolver.js +17 -0
- package/dist/vendor/witnora-probe/crypto.d.ts +2 -0
- package/dist/vendor/witnora-probe/crypto.js +14 -0
- package/dist/vendor/witnora-probe/emulator.d.ts +51 -0
- package/dist/vendor/witnora-probe/emulator.js +164 -0
- package/dist/vendor/witnora-probe/index.d.ts +11 -0
- package/dist/vendor/witnora-probe/index.js +11 -0
- package/dist/vendor/witnora-probe/logger.d.ts +3 -0
- package/dist/vendor/witnora-probe/logger.js +26 -0
- package/dist/vendor/witnora-probe/metrics.d.ts +12 -0
- package/dist/vendor/witnora-probe/metrics.js +31 -0
- package/dist/vendor/witnora-probe/probe.d.ts +22 -0
- package/dist/vendor/witnora-probe/probe.js +208 -0
- package/dist/vendor/witnora-probe/request-verifier.d.ts +9 -0
- package/dist/vendor/witnora-probe/request-verifier.js +84 -0
- package/dist/vendor/witnora-probe/server.d.ts +9 -0
- package/dist/vendor/witnora-probe/server.js +85 -0
- package/dist/vendor/witnora-probe/storage.d.ts +16 -0
- package/dist/vendor/witnora-probe/storage.js +114 -0
- package/dist/vendor/witnora-probe/types.d.ts +126 -0
- package/dist/vendor/witnora-probe/types.js +2 -0
- package/dist/vendor/witnora-verifier-python/README.md +43 -0
- package/dist/vendor/witnora-verifier-python/pyproject.toml +23 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/__init__.py +33 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/canonical.py +218 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/cli.py +75 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/conformance.py +124 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/crypto.py +139 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/errors.py +15 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/event_chain.py +80 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/evidence.py +336 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/probe.py +188 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/producer.py +127 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/trust.py +337 -0
- package/package.json +4 -4
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createPrivateKey, sign } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:net";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import { cp, mkdtemp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
6
|
+
import { delimiter, join, resolve } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
|
|
10
|
+
const standaloneRoot = resolve(import.meta.dirname, "..");
|
|
11
|
+
const standalone = existsSync(join(standaloneRoot, "docker", "runtime", "dist", "cli.js"));
|
|
12
|
+
const root = standalone ? standaloneRoot : resolve(import.meta.dirname, "..");
|
|
13
|
+
const cli = standalone ? join(root, "docker", "runtime", "dist", "cli.js") : join(root, "packages", "witnora-cli", "dist", "cli.js");
|
|
14
|
+
const probe = standalone ? join(root, "docker", "runtime", "dist", "vendor", "witnora-probe", "cli.js") : join(root, "packages", "witnora-probe", "dist", "cli.js");
|
|
15
|
+
const pythonPackage = standalone ? join(root, "verifiers", "python", "src") : join(root, "packages", "witnora-verifier-python", "src");
|
|
16
|
+
const cliDist = standalone ? join(root, "docker", "runtime", "dist") : join(root, "packages", "witnora-cli", "dist");
|
|
17
|
+
const { canonicalBytesV02 } = await import(pathToFileURL(join(cliDist, "canonical-v02.js")).href);
|
|
18
|
+
const { signProbeAttestation } = await import(pathToFileURL(join(cliDist, "probe-protocol-v02.js")).href);
|
|
19
|
+
const readCredential = "e2e-read-only-credential";
|
|
20
|
+
const writeCredential = "e2e-write-only-credential";
|
|
21
|
+
const storageKey = Buffer.alloc(32, 7).toString("base64");
|
|
22
|
+
const temp = await mkdtemp(join(tmpdir(), "witnora-design-partner-v02-"));
|
|
23
|
+
const kit = join(temp, "kit");
|
|
24
|
+
const children = [];
|
|
25
|
+
const privacyCanaries = [
|
|
26
|
+
"CANARY_PR_TITLE_customer_private_7af9",
|
|
27
|
+
"CANARY_COMMIT_MESSAGE_customer_private_83b2",
|
|
28
|
+
"CANARY_WEBHOOK_PAYLOAD_customer_private_91cc",
|
|
29
|
+
"CANARY_TARGET_RESPONSE_customer_private_a12e",
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (standalone) {
|
|
34
|
+
await cp(root, kit, {
|
|
35
|
+
recursive: true,
|
|
36
|
+
filter: (source) => !["acceptance", "claims", "support-bundle", "failure-exercises"].some((name) => source.includes(`${join(root, name)}`)),
|
|
37
|
+
});
|
|
38
|
+
process.stdout.write("[standalone kit copy] PASS\n");
|
|
39
|
+
} else {
|
|
40
|
+
await runNode([cli, "design-partner", "init", "github-pr-merge", "--out", kit], {}, "kit init");
|
|
41
|
+
}
|
|
42
|
+
await injectPrivacyCanaries(kit);
|
|
43
|
+
const [targetPort, probePort] = await Promise.all([freePort(), freePort()]);
|
|
44
|
+
await configureProbe(kit, targetPort, probePort);
|
|
45
|
+
await issueAuthorization();
|
|
46
|
+
|
|
47
|
+
const target = startNode([cli, "design-partner", "target-emulator", "--dir", kit, "--state", join(kit, "target-state.json"), "--port", String(targetPort)], {
|
|
48
|
+
WITNORA_GITHUB_READ_CREDENTIAL: readCredential,
|
|
49
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
50
|
+
}, "target");
|
|
51
|
+
children.push(target);
|
|
52
|
+
await waitFor(`http://127.0.0.1:${targetPort}/health`);
|
|
53
|
+
|
|
54
|
+
let probeProcess = startNode([probe], {
|
|
55
|
+
WITNORA_PROBE_CONFIG: join(kit, "probe", "config.json"),
|
|
56
|
+
WITNORA_PROBE_STORAGE_KEY_BASE64: storageKey,
|
|
57
|
+
WITNORA_GITHUB_READ_CREDENTIAL: readCredential,
|
|
58
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: undefined,
|
|
59
|
+
}, "probe");
|
|
60
|
+
children.push(probeProcess);
|
|
61
|
+
await waitFor(`http://127.0.0.1:${probePort}/ready`);
|
|
62
|
+
|
|
63
|
+
await runNode([cli, "design-partner", "doctor", "--dir", kit], {
|
|
64
|
+
WITNORA_GITHUB_READ_CREDENTIAL: readCredential,
|
|
65
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
66
|
+
}, "doctor");
|
|
67
|
+
await runNode([cli, "design-partner", "dry-run", "--dir", kit, "--target-url", `http://127.0.0.1:${targetPort}`], {
|
|
68
|
+
WITNORA_GITHUB_READ_CREDENTIAL: readCredential,
|
|
69
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: undefined,
|
|
70
|
+
}, "dry-run");
|
|
71
|
+
|
|
72
|
+
const firstOut = join(temp, "acceptance-normal");
|
|
73
|
+
const firstAcceptance = await runNode([cli, "design-partner", "run-acceptance", "--dir", kit, "--out", firstOut, "--target-url", `http://127.0.0.1:${targetPort}`, "--probe-url", `http://127.0.0.1:${probePort}`], {
|
|
74
|
+
WITNORA_GITHUB_READ_CREDENTIAL: undefined,
|
|
75
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
76
|
+
}, "gateway acceptance");
|
|
77
|
+
const acceptanceMetrics = parseAcceptanceMetrics(firstAcceptance.stdout);
|
|
78
|
+
const packetPath = join(firstOut, "witnora-evidence-packet-v0.2.json");
|
|
79
|
+
const rootPath = join(firstOut, "pinned-trust-root-v0.2.json");
|
|
80
|
+
const packet = JSON.parse(await readFile(packetPath, "utf8"));
|
|
81
|
+
assert(packet.executionAttempt.dispatchCount === 1, "normal acceptance dispatched exactly once");
|
|
82
|
+
assert(packet.executionAttempt.state === "RECONCILED_SUCCESS", "normal acceptance reconciled successfully");
|
|
83
|
+
|
|
84
|
+
const grantReplayOut = join(temp, "acceptance-grant-replay");
|
|
85
|
+
await expectFailure(() => runNode([cli, "design-partner", "run-acceptance", "--dir", kit, "--out", grantReplayOut, "--target-url", `http://127.0.0.1:${targetPort}`, "--probe-url", `http://127.0.0.1:${probePort}`], {
|
|
86
|
+
WITNORA_GITHUB_READ_CREDENTIAL: undefined,
|
|
87
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
88
|
+
}, "Grant replay"), "Exercise B: a consumed one-use Grant is rejected");
|
|
89
|
+
assert((await readTarget(targetPort)).targetVersion === 2, "Exercise B: Grant replay produced no second target write");
|
|
90
|
+
|
|
91
|
+
const firstJournalPath = join(firstOut, "revocation-journal-v0.2.json");
|
|
92
|
+
await runNode([cli, "design-partner", "verify-offline", "--packet", packetPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "TypeScript verifier");
|
|
93
|
+
await runPython(["-m", "witnora_verifier.cli", "evidence", packetPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "Python verifier");
|
|
94
|
+
const liveBindingPath = join(firstOut, "live-gateway-binding-packet.json");
|
|
95
|
+
const { signature: _probeSignature, protocolVersion: _probeProtocol, ...probeAttestationInput } = packet.probeAttestation;
|
|
96
|
+
const liveProbeAttestation = signProbeAttestation({
|
|
97
|
+
...probeAttestationInput,
|
|
98
|
+
normalizedObservation: Object.fromEntries(Object.entries(packet.probeAttestation.normalizedObservation).filter(([key]) => key !== "consumedGrantId")),
|
|
99
|
+
}, await readFile(join(kit, "secrets", "probe-private.pem"), "utf8"));
|
|
100
|
+
const liveBindingPacket = await resignGatewayPacket({
|
|
101
|
+
...packet,
|
|
102
|
+
reconciliation: { ...packet.reconciliation, authorizationBindingSource: "GATEWAY_DISPATCH_RECORD" },
|
|
103
|
+
probeAttestation: liveProbeAttestation,
|
|
104
|
+
separation: { ...packet.separation, probeDeploymentMode: "customer_network" },
|
|
105
|
+
evidenceStrength: { ...packet.evidenceStrength, authorizationBinding: "GATEWAY_DISPATCH_BOUND", operatorSeparation: "CUSTOMER_NETWORK_SEPARATED" },
|
|
106
|
+
}, join(kit, "secrets", "gateway-private.pem"));
|
|
107
|
+
await writeFile(liveBindingPath, `${JSON.stringify(liveBindingPacket, null, 2)}\n`);
|
|
108
|
+
await runNode([cli, "design-partner", "verify-offline", "--packet", liveBindingPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "live-binding TypeScript verifier");
|
|
109
|
+
await runPython(["-m", "witnora_verifier.cli", "evidence", liveBindingPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "live-binding Python verifier");
|
|
110
|
+
await assertReplayRejected(packet, probePort);
|
|
111
|
+
|
|
112
|
+
const tamperedPath = join(firstOut, "tampered-packet.json");
|
|
113
|
+
await writeFile(tamperedPath, `${JSON.stringify({ ...packet, actionDigest: `${packet.actionDigest}-tampered` }, null, 2)}\n`);
|
|
114
|
+
await expectFailure(() => runNode([cli, "design-partner", "verify-offline", "--packet", tamperedPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "tampered TypeScript verifier"), "TypeScript rejects packet tampering");
|
|
115
|
+
await expectFailure(() => runPython(["-m", "witnora_verifier.cli", "evidence", tamperedPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "tampered Python verifier"), "Python rejects packet tampering");
|
|
116
|
+
|
|
117
|
+
const externalReviewOverclaimPath = join(firstOut, "external-review-overclaim.json");
|
|
118
|
+
const externalReviewOverclaim = await resignGatewayPacket({ ...packet, evidenceStrength: { ...packet.evidenceStrength, review: "EXTERNAL" } }, join(kit, "secrets", "gateway-private.pem"));
|
|
119
|
+
await writeFile(externalReviewOverclaimPath, `${JSON.stringify(externalReviewOverclaim, null, 2)}\n`);
|
|
120
|
+
await expectFailure(() => runNode([cli, "design-partner", "verify-offline", "--packet", externalReviewOverclaimPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "external-review overclaim TypeScript verifier"), "TypeScript rejects unsigned external-review overclaim");
|
|
121
|
+
await expectFailure(() => runPython(["-m", "witnora_verifier.cli", "evidence", externalReviewOverclaimPath, "--root", rootPath, "--revocation-journal", firstJournalPath], {}, "external-review overclaim Python verifier"), "Python rejects unsigned external-review overclaim");
|
|
122
|
+
|
|
123
|
+
await resetTarget(kit);
|
|
124
|
+
await issueAuthorization();
|
|
125
|
+
await assertParameterMutationRejected(targetPort, kit);
|
|
126
|
+
await resetTarget(kit);
|
|
127
|
+
await issueAuthorization();
|
|
128
|
+
const crashOut = join(temp, "acceptance-crash-recovery");
|
|
129
|
+
await expectFailure(() => runNode([cli, "design-partner", "run-acceptance", "--dir", kit, "--out", crashOut, "--target-url", `http://127.0.0.1:${targetPort}`, "--probe-url", `http://127.0.0.1:${probePort}`, "--crash-after-target-success"], {
|
|
130
|
+
WITNORA_GITHUB_READ_CREDENTIAL: undefined,
|
|
131
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
132
|
+
}, "crashing Gateway process"), "Gateway process exits after target success");
|
|
133
|
+
await runNode([cli, "design-partner", "recover-acceptance", "--dir", kit, "--out", crashOut, "--target-url", `http://127.0.0.1:${targetPort}`, "--probe-url", `http://127.0.0.1:${probePort}`], {
|
|
134
|
+
WITNORA_GITHUB_READ_CREDENTIAL: undefined,
|
|
135
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
136
|
+
}, "new Gateway recovery process");
|
|
137
|
+
const crashPacket = JSON.parse(await readFile(join(crashOut, "witnora-evidence-packet-v0.2.json"), "utf8"));
|
|
138
|
+
const states = crashPacket.executionAttempt.recoveryHistory.map((entry) => entry.state);
|
|
139
|
+
assert(states.includes("DISPATCH_UNKNOWN"), "crash path records DISPATCH_UNKNOWN");
|
|
140
|
+
assert(states.at(-1) === "RECONCILED_SUCCESS", "crash path reconciles target state");
|
|
141
|
+
assert(crashPacket.executionAttempt.dispatchCount === 1, "crash recovery does not duplicate dispatch");
|
|
142
|
+
|
|
143
|
+
const revocationEffectiveAt = new Date(Date.now() + 100).toISOString();
|
|
144
|
+
await runNode([cli, "design-partner", "trust", "revoke-probe", "--dir", kit, "--effective-at", revocationEffectiveAt, "--reason", "E2E rotation rehearsal"], {}, "revoke Probe certificate");
|
|
145
|
+
const latestJournal = join(kit, "trust", "revocation-journal.json");
|
|
146
|
+
await runNode([cli, "design-partner", "verify-offline", "--packet", packetPath, "--root", rootPath, "--revocation-journal", latestJournal], {}, "historical TypeScript verification after non-retroactive revocation");
|
|
147
|
+
await runPython(["-m", "witnora_verifier.cli", "evidence", packetPath, "--root", rootPath, "--revocation-journal", latestJournal], {}, "historical Python verification after non-retroactive revocation");
|
|
148
|
+
await new Promise((ok) => setTimeout(ok, 150));
|
|
149
|
+
await resetTarget(kit);
|
|
150
|
+
await issueAuthorization();
|
|
151
|
+
const revokedOut = join(temp, "acceptance-revoked-probe");
|
|
152
|
+
await expectFailure(() => runNode([cli, "design-partner", "run-acceptance", "--dir", kit, "--out", revokedOut, "--target-url", `http://127.0.0.1:${targetPort}`, "--probe-url", `http://127.0.0.1:${probePort}`], {
|
|
153
|
+
WITNORA_GITHUB_READ_CREDENTIAL: undefined,
|
|
154
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
155
|
+
}, "revoked Probe acceptance"), "revoked Probe blocks a new covered dispatch");
|
|
156
|
+
assert((await readTarget(targetPort)).merged === false, "revoked Probe failure occurs before target dispatch");
|
|
157
|
+
|
|
158
|
+
await runNode([cli, "design-partner", "trust", "rotate-probe", "--dir", kit], {}, "rotate Probe certificate");
|
|
159
|
+
probeProcess.kill("SIGTERM");
|
|
160
|
+
await onceExit(probeProcess, 2_000);
|
|
161
|
+
probeProcess = startNode([probe], {
|
|
162
|
+
WITNORA_PROBE_CONFIG: join(kit, "probe", "config.json"),
|
|
163
|
+
WITNORA_PROBE_STORAGE_KEY_BASE64: storageKey,
|
|
164
|
+
WITNORA_GITHUB_READ_CREDENTIAL: readCredential,
|
|
165
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: undefined,
|
|
166
|
+
}, "probe-rotated");
|
|
167
|
+
children.push(probeProcess);
|
|
168
|
+
await waitFor(`http://127.0.0.1:${probePort}/ready`);
|
|
169
|
+
await issueAuthorization();
|
|
170
|
+
const rotatedOut = join(temp, "acceptance-rotated-probe");
|
|
171
|
+
await runNode([cli, "design-partner", "run-acceptance", "--dir", kit, "--out", rotatedOut, "--target-url", `http://127.0.0.1:${targetPort}`, "--probe-url", `http://127.0.0.1:${probePort}`], {
|
|
172
|
+
WITNORA_GITHUB_READ_CREDENTIAL: undefined,
|
|
173
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
174
|
+
}, "rotated Probe acceptance");
|
|
175
|
+
|
|
176
|
+
const config = JSON.parse(await readFile(join(kit, "design-partner.json"), "utf8"));
|
|
177
|
+
await writeFile(join(kit, "target-state.json"), `${JSON.stringify({ ...config.declaredTarget, simulateFalseSuccess: true }, null, 2)}\n`);
|
|
178
|
+
await issueAuthorization();
|
|
179
|
+
const falseSuccessOut = join(temp, "acceptance-false-success");
|
|
180
|
+
await expectFailure(() => runNode([cli, "design-partner", "run-acceptance", "--dir", kit, "--out", falseSuccessOut, "--target-url", `http://127.0.0.1:${targetPort}`, "--probe-url", `http://127.0.0.1:${probePort}`], {
|
|
181
|
+
WITNORA_GITHUB_READ_CREDENTIAL: undefined,
|
|
182
|
+
WITNORA_GITHUB_WRITE_CREDENTIAL: writeCredential,
|
|
183
|
+
}, "false-success acceptance"), "apparent target success does not become verified success");
|
|
184
|
+
const falsePacket = JSON.parse(await readFile(join(falseSuccessOut, "witnora-evidence-packet-v0.2.json"), "utf8"));
|
|
185
|
+
assert(falsePacket.probeAttestation.outcomeStatus === "NOT_VERIFIED", "false success is independently observed as NOT_VERIFIED");
|
|
186
|
+
assert((await readTarget(targetPort)).merged === false, "false-success target remains unmerged");
|
|
187
|
+
await assertNoPrivacyCanaryEgress([firstOut, crashOut, rotatedOut, falseSuccessOut], firstAcceptance.stdout);
|
|
188
|
+
|
|
189
|
+
const summary = {
|
|
190
|
+
protocolVersion: "witnora.design_partner_e2e_report.v0.2",
|
|
191
|
+
overall: "PASS",
|
|
192
|
+
processBoundaries: {
|
|
193
|
+
target: ["read_credential", "write_credential"],
|
|
194
|
+
probe: ["read_credential"],
|
|
195
|
+
gateway: ["write_credential"],
|
|
196
|
+
evidence: ["no_raw_credentials"],
|
|
197
|
+
},
|
|
198
|
+
assertions: [
|
|
199
|
+
"normal path independently reconciled",
|
|
200
|
+
"consumed one-use Grant replay rejected without a second target write",
|
|
201
|
+
"TypeScript and Python offline verifiers passed",
|
|
202
|
+
"replay rejected",
|
|
203
|
+
"tampering rejected by both verifiers",
|
|
204
|
+
"external-review overclaim rejected by both verifiers",
|
|
205
|
+
"parameter mutation rejected before dispatch",
|
|
206
|
+
"crash after target success recovered without duplicate dispatch",
|
|
207
|
+
"non-retroactive revocation preserved historical verification",
|
|
208
|
+
"revoked Probe blocked new dispatch",
|
|
209
|
+
"rotated Probe restored the covered path",
|
|
210
|
+
"apparent target success failed independent outcome verification",
|
|
211
|
+
"raw customer canaries remained outside emitted evidence and reports",
|
|
212
|
+
],
|
|
213
|
+
acceptanceMetrics,
|
|
214
|
+
};
|
|
215
|
+
const artifactDirectory = standalone ? join(root, "failure-exercises") : join(root, ".artifacts");
|
|
216
|
+
await mkdir(artifactDirectory, { recursive: true });
|
|
217
|
+
await writeFile(join(artifactDirectory, "design-partner-v02-e2e-report.json"), `${JSON.stringify(summary, null, 2)}\n`);
|
|
218
|
+
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
219
|
+
} finally {
|
|
220
|
+
for (const child of children.reverse()) child.kill("SIGTERM");
|
|
221
|
+
await Promise.all(children.map((child) => onceExit(child, 2_000)));
|
|
222
|
+
if (process.env.WITNORA_E2E_KEEP_TEMP === "1") process.stderr.write(`[e2e] retained ${temp}\n`);
|
|
223
|
+
else await rm(temp, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function configureProbe(directory, targetPort, probePort) {
|
|
227
|
+
const kitConfigPath = join(directory, "design-partner.json");
|
|
228
|
+
const kitConfig = JSON.parse(await readFile(kitConfigPath, "utf8"));
|
|
229
|
+
kitConfig.targetUrl = `http://127.0.0.1:${targetPort}`;
|
|
230
|
+
kitConfig.probeUrl = `http://127.0.0.1:${probePort}`;
|
|
231
|
+
await writeFile(kitConfigPath, `${JSON.stringify(kitConfig, null, 2)}\n`);
|
|
232
|
+
const path = join(directory, "probe", "config.json");
|
|
233
|
+
const config = JSON.parse(await readFile(path, "utf8"));
|
|
234
|
+
config.host = "127.0.0.1";
|
|
235
|
+
config.port = probePort;
|
|
236
|
+
config.githubTarget.url = `http://127.0.0.1:${targetPort}`;
|
|
237
|
+
await writeFile(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function injectPrivacyCanaries(directory) {
|
|
241
|
+
const configPath = join(directory, "design-partner.json");
|
|
242
|
+
const config = JSON.parse(await readFile(configPath, "utf8"));
|
|
243
|
+
config.declaredTarget.pullRequestTitle = privacyCanaries[0];
|
|
244
|
+
config.declaredTarget.commitMessage = privacyCanaries[1];
|
|
245
|
+
config.declaredTarget.webhookPayload = { body: privacyCanaries[2] };
|
|
246
|
+
config.declaredTarget.targetResponseMetadata = { private: privacyCanaries[3] };
|
|
247
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
248
|
+
await writeFile(join(directory, "target-state.json"), `${JSON.stringify(config.declaredTarget, null, 2)}\n`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function assertNoPrivacyCanaryEgress(directories, stdout) {
|
|
252
|
+
const outputs = [stdout];
|
|
253
|
+
for (const directory of directories) {
|
|
254
|
+
for (const path of await filesRecursively(directory)) outputs.push(await readFile(path, "utf8"));
|
|
255
|
+
}
|
|
256
|
+
for (const canary of privacyCanaries) assert(outputs.every((value) => !value.includes(canary)), `${canary.split("_customer")[0]} did not egress into evidence, reports, or CLI output`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function filesRecursively(directory) {
|
|
260
|
+
const paths = [];
|
|
261
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
262
|
+
const path = join(directory, entry.name);
|
|
263
|
+
if (entry.isDirectory()) paths.push(...await filesRecursively(path));
|
|
264
|
+
else if ((await stat(path)).size < 10_000_000) paths.push(path);
|
|
265
|
+
}
|
|
266
|
+
return paths;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function resetTarget(directory) {
|
|
270
|
+
const config = JSON.parse(await readFile(join(directory, "design-partner.json"), "utf8"));
|
|
271
|
+
await writeFile(join(directory, "target-state.json"), `${JSON.stringify(config.declaredTarget, null, 2)}\n`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async function assertReplayRejected(packet, port) {
|
|
275
|
+
const response = await fetch(`http://127.0.0.1:${port}/v0.2/observe`, {
|
|
276
|
+
method: "POST", headers: { "content-type": "application/json" },
|
|
277
|
+
body: JSON.stringify({
|
|
278
|
+
request: packet.probeRequest,
|
|
279
|
+
expected: packet.probeAttestation.normalizedObservation,
|
|
280
|
+
query: { type: "github.pull_request.read.v0.2", repository: "witnora-design-partner/assurance-sandbox", pullRequestNumber: 17 },
|
|
281
|
+
}),
|
|
282
|
+
});
|
|
283
|
+
assert(response.status === 409, `Probe replay rejected with 409, received ${response.status}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function resignGatewayPacket(packet, privateKeyPath) {
|
|
287
|
+
const { gatewaySignature: _gatewaySignature, ...signedValue } = packet;
|
|
288
|
+
const privateKey = createPrivateKey(await readFile(privateKeyPath, "utf8"));
|
|
289
|
+
return {
|
|
290
|
+
...signedValue,
|
|
291
|
+
gatewaySignature: {
|
|
292
|
+
keyId: packet.gatewaySignature.keyId,
|
|
293
|
+
signature: sign(null, canonicalBytesV02(signedValue), privateKey).toString("base64"),
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function issueAuthorization() {
|
|
299
|
+
await runNode([cli, "design-partner", "authorization", "issue", "--dir", kit], {}, "signed authorization issuance");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function assertParameterMutationRejected(port, directory) {
|
|
303
|
+
const approval = JSON.parse(await readFile(join(directory, "authorization", "approval.json"), "utf8"));
|
|
304
|
+
const grant = JSON.parse(await readFile(join(directory, "authorization", "grant.json"), "utf8"));
|
|
305
|
+
const response = await fetch(`http://127.0.0.1:${port}/v0.2/github/pull-request`, {
|
|
306
|
+
method: "POST", headers: { authorization: `Bearer ${writeCredential}`, "content-type": "application/json" },
|
|
307
|
+
body: JSON.stringify({ idempotencyKey: grant.grantId, approval, grant, identity: {
|
|
308
|
+
protocolVersion: "witnora.github_pull_request_merge.v0.2", owner: "witnora-design-partner", repository: "assurance-sandbox",
|
|
309
|
+
pullRequestNumber: 17, baseBranch: "acceptance", approvedBaseSha: "base-a", headBranch: "agent-change", approvedHeadSha: "head-mutated",
|
|
310
|
+
mergeMethod: "squash", requiredChecks: ["build", "policy"], agentRef: "agent:design-partner", agentBuildDigest: "build",
|
|
311
|
+
contractDigest: "contract", policyVersion: "policy", mandateVersion: "mandate", resourceCommitment: "resource", parameterCommitment: "params",
|
|
312
|
+
} }),
|
|
313
|
+
});
|
|
314
|
+
assert(response.status === 403, `Mutated action rejected with 403, received ${response.status}`);
|
|
315
|
+
const state = await readTarget(port);
|
|
316
|
+
assert(state.merged === false, "parameter mutation produced no target write");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function readTarget(port) {
|
|
320
|
+
const response = await fetch(`http://127.0.0.1:${port}/v0.2/github/pull-request`, { headers: { authorization: `Bearer ${readCredential}` } });
|
|
321
|
+
return response.json();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function startNode(args, overrides, label) {
|
|
325
|
+
const child = spawn(process.execPath, args, { cwd: root, env: cleanEnv(overrides), stdio: ["ignore", "pipe", "pipe"] });
|
|
326
|
+
child.stdout.on("data", (data) => process.stdout.write(`[${label}] ${data}`));
|
|
327
|
+
child.stderr.on("data", (data) => process.stderr.write(`[${label}] ${data}`));
|
|
328
|
+
return child;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function runNode(args, overrides, label) { return runProcess(process.execPath, args, cleanEnv(overrides), label); }
|
|
332
|
+
async function runPython(args, overrides, label) {
|
|
333
|
+
const env = cleanEnv(overrides); env.PYTHONPATH = [pythonPackage, env.PYTHONPATH].filter(Boolean).join(delimiter);
|
|
334
|
+
return runProcess(process.env.PYTHON ?? "python", args, env, label);
|
|
335
|
+
}
|
|
336
|
+
async function runProcess(command, args, env, label) {
|
|
337
|
+
const child = spawn(command, args, { cwd: root, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
338
|
+
let stdout = ""; let stderr = ""; child.stdout.on("data", (data) => { stdout += data; }); child.stderr.on("data", (data) => { stderr += data; });
|
|
339
|
+
const code = await new Promise((resolveExit, reject) => { child.once("error", reject); child.once("exit", resolveExit); });
|
|
340
|
+
if (code !== 0) throw new Error(`${label} failed (${code}).\n${stdout}\n${stderr}`);
|
|
341
|
+
process.stdout.write(`[${label}] PASS\n`); return { stdout, stderr };
|
|
342
|
+
}
|
|
343
|
+
function cleanEnv(overrides) { const env = { ...process.env, ...overrides }; for (const [key, value] of Object.entries(env)) if (value === undefined) delete env[key]; return env; }
|
|
344
|
+
async function freePort() { const server = createServer(); await new Promise((ok, fail) => server.once("error", fail).listen(0, "127.0.0.1", ok)); const port = server.address().port; await new Promise((ok) => server.close(ok)); return port; }
|
|
345
|
+
async function waitFor(url) { const deadline = Date.now() + 10_000; while (Date.now() < deadline) { try { const response = await fetch(url); if (response.ok) return; } catch {} await new Promise((ok) => setTimeout(ok, 100)); } throw new Error(`Timed out waiting for ${url}`); }
|
|
346
|
+
function assert(condition, message) { if (!condition) throw new Error(`Assertion failed: ${message}`); process.stdout.write(`[assert] PASS ${message}\n`); }
|
|
347
|
+
function parseAcceptanceMetrics(stdout) {
|
|
348
|
+
const line = stdout.split(/\r?\n/u).find((entry) => entry.startsWith("Metrics: "));
|
|
349
|
+
if (!line) throw new Error("Acceptance command did not emit setup and verification metrics.");
|
|
350
|
+
return JSON.parse(line.slice("Metrics: ".length));
|
|
351
|
+
}
|
|
352
|
+
async function expectFailure(operation, message) { try { await operation(); } catch { process.stdout.write(`[assert] PASS ${message}\n`); return; } throw new Error(`Assertion failed: ${message}`); }
|
|
353
|
+
async function onceExit(child, timeoutMs) { if (child.exitCode !== null) return; await Promise.race([new Promise((ok) => child.once("exit", ok)), new Promise((ok) => setTimeout(ok, timeoutMs))]); if (child.exitCode === null) child.kill("SIGKILL"); }
|
|
@@ -157,10 +157,18 @@ export interface BrowserEnforcementExecutionOptions {
|
|
|
157
157
|
status: "CLAIMED";
|
|
158
158
|
claimedAt: string;
|
|
159
159
|
}>;
|
|
160
|
+
recordExecutionAttempt(executionSessionId: string, update: {
|
|
161
|
+
phase: "DISPATCH_STARTED" | "DISPATCH_ACKNOWLEDGED" | "DISPATCH_UNKNOWN" | "RECONCILED_SUCCESS" | "RECONCILED_FAILURE" | "CONFLICT";
|
|
162
|
+
writeResponseCommitment?: string;
|
|
163
|
+
targetOperationId?: string;
|
|
164
|
+
stableErrorCode?: string;
|
|
165
|
+
}): Promise<void>;
|
|
160
166
|
markConsumed(executionGrantId: string, executionSessionId: string): Promise<void>;
|
|
167
|
+
failpoint?: (name: BrowserEnforcementFailpoint) => void | Promise<void>;
|
|
161
168
|
now?: () => Date;
|
|
162
169
|
clockSkewMs?: number;
|
|
163
170
|
}
|
|
171
|
+
export type BrowserEnforcementFailpoint = "AFTER_GRANT_CLAIM" | "AFTER_EXECUTION_ATTEMPT_PERSISTENCE" | "IMMEDIATELY_BEFORE_TARGET_REQUEST" | "IMMEDIATELY_AFTER_TARGET_ACCEPTS_REQUEST" | "AFTER_TARGET_RESPONSE_BEFORE_LOCAL_RECORD" | "AFTER_LOCAL_RECORD_BEFORE_GRANT_CONSUME" | "DURING_GRANT_CONSUME" | "BEFORE_OUTCOME_OBSERVATION" | "DURING_RECONCILIATION";
|
|
164
172
|
export interface BrowserEnforcementRuntimeBundle {
|
|
165
173
|
protocolVersion: typeof BROWSER_ENFORCEMENT_PROTOCOL;
|
|
166
174
|
executionGrant: SignedExecutionGrantWire;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-enforcement-runtime.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/browser-enforcement-runtime.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"browser-enforcement-runtime.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/browser-enforcement-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,10 @@ export async function executeBrowserEnforcement(options) {
|
|
|
39
39
|
idempotencyKey: `claim:${grant.jti}`,
|
|
40
40
|
}, options.runtime.signer);
|
|
41
41
|
await options.claimGrant(runtimeClaim);
|
|
42
|
+
await options.failpoint?.("AFTER_GRANT_CLAIM");
|
|
43
|
+
// Claim and attempt reservation are one Hosted transaction. A returned claim
|
|
44
|
+
// therefore proves the RESERVED attempt is durable as well.
|
|
45
|
+
await options.failpoint?.("AFTER_EXECUTION_ATTEMPT_PERSISTENCE");
|
|
42
46
|
const credentialLeaseId = options.credentialLease.credentialLeaseId ?? randomUUID();
|
|
43
47
|
const startedAt = now().toISOString();
|
|
44
48
|
const genesis = sha256(canonicalJson({ executionSessionId, executionGrantDigest: options.grant.payloadSha256, runtimeIdentityId: options.runtime.runtimeIdentityId }));
|
|
@@ -106,6 +110,8 @@ export async function executeBrowserEnforcement(options) {
|
|
|
106
110
|
status: "ACTIVE",
|
|
107
111
|
};
|
|
108
112
|
let leaseRevoked = false;
|
|
113
|
+
let dispatchStarted = false;
|
|
114
|
+
let dispatchAcknowledged = false;
|
|
109
115
|
const revokeCredentialLease = async () => {
|
|
110
116
|
if (leaseRevoked)
|
|
111
117
|
return;
|
|
@@ -138,7 +144,22 @@ export async function executeBrowserEnforcement(options) {
|
|
|
138
144
|
}
|
|
139
145
|
await append("FINAL_PARAMETERS_VERIFIED", { parametersDigest: finalParametersDigest });
|
|
140
146
|
await append("HIGH_RISK_SUBMISSION_STARTED", { actionId: grant.actionId });
|
|
141
|
-
|
|
147
|
+
await options.recordExecutionAttempt(executionSessionId, { phase: "DISPATCH_STARTED" });
|
|
148
|
+
dispatchStarted = true;
|
|
149
|
+
let adapterResult;
|
|
150
|
+
try {
|
|
151
|
+
await options.failpoint?.("IMMEDIATELY_BEFORE_TARGET_REQUEST");
|
|
152
|
+
adapterResult = await options.adapter.execute(options.action, { idempotencyKey: executionSessionId, attempt: 1 });
|
|
153
|
+
await options.failpoint?.("IMMEDIATELY_AFTER_TARGET_ACCEPTS_REQUEST");
|
|
154
|
+
await options.failpoint?.("AFTER_TARGET_RESPONSE_BEFORE_LOCAL_RECORD");
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
await options.recordExecutionAttempt(executionSessionId, {
|
|
158
|
+
phase: "DISPATCH_UNKNOWN",
|
|
159
|
+
stableErrorCode: "TARGET_RESULT_UNKNOWN",
|
|
160
|
+
});
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
142
163
|
const execution = {
|
|
143
164
|
method: adapterResult.method ?? "LOCAL_ADAPTER",
|
|
144
165
|
status: "COMPLETED",
|
|
@@ -147,10 +168,18 @@ export async function executeBrowserEnforcement(options) {
|
|
|
147
168
|
observedState: adapterResult.observedState,
|
|
148
169
|
rollbackToken: adapterResult.rollbackToken,
|
|
149
170
|
};
|
|
171
|
+
await options.recordExecutionAttempt(executionSessionId, {
|
|
172
|
+
phase: "DISPATCH_ACKNOWLEDGED",
|
|
173
|
+
writeResponseCommitment: sha256(canonicalJson(adapterResult)),
|
|
174
|
+
});
|
|
175
|
+
dispatchAcknowledged = true;
|
|
176
|
+
await options.failpoint?.("AFTER_LOCAL_RECORD_BEFORE_GRANT_CONSUME");
|
|
177
|
+
await options.failpoint?.("DURING_GRANT_CONSUME");
|
|
150
178
|
await options.markConsumed(grant.executionGrantId, executionSessionId);
|
|
151
179
|
await append("HIGH_RISK_SUBMISSION_COMPLETED", { targetSystem: execution.targetSystem });
|
|
152
180
|
await append("TARGET_RESPONSE_OBSERVED", { observedStateDigest: sha256(canonicalJson(execution.observedState ?? {})) });
|
|
153
181
|
await append("OUTCOME_PROBE_STARTED", { probe: options.outcomeProbe.name });
|
|
182
|
+
await options.failpoint?.("BEFORE_OUTCOME_OBSERVATION");
|
|
154
183
|
const observation = await options.outcomeProbe.observe(options.action, execution);
|
|
155
184
|
await append("OUTCOME_OBSERVED", { observationId: observation.observationId, source: observation.source, observedStateDigest: sha256(canonicalJson(observation.observedState)) });
|
|
156
185
|
const predicateDigest = sha256(canonicalJson(options.outcomePredicate));
|
|
@@ -185,10 +214,15 @@ export async function executeBrowserEnforcement(options) {
|
|
|
185
214
|
await revokeCredentialLease();
|
|
186
215
|
await append("EXECUTION_SESSION_COMPLETED", { outcomeAttestationDigest: outcomeAttestation.payloadSha256 });
|
|
187
216
|
const auditEvents = await options.targetAuditSource.listEvents(startedAt, now().toISOString());
|
|
217
|
+
await options.failpoint?.("DURING_RECONCILIATION");
|
|
188
218
|
const matching = auditEvents.filter((event) => event.actionId === grant.actionId && event.executionSessionId === executionSessionId && event.operation === grant.allowedOperation && event.resource === grant.allowedResource && event.parametersDigest === grant.parametersDigest);
|
|
189
219
|
const unmatchedTargetEvents = auditEvents.filter((event) => !matching.includes(event)).map((event) => event.targetEventId);
|
|
190
220
|
const duplicateMatches = matching.length > 1 ? matching.slice(1).map((event) => event.targetEventId) : [];
|
|
191
221
|
const reconciliationResult = options.targetAuditSource.exclusiveCredential && matching.length === 1 && !unmatchedTargetEvents.length && !duplicateMatches.length ? "PASSED" : unmatchedTargetEvents.length || duplicateMatches.length ? "BYPASS_DETECTED" : "INCOMPLETE";
|
|
222
|
+
await options.recordExecutionAttempt(executionSessionId, {
|
|
223
|
+
phase: reconciliationResult === "PASSED" ? "RECONCILED_SUCCESS" : reconciliationResult === "BYPASS_DETECTED" ? "CONFLICT" : "RECONCILED_FAILURE",
|
|
224
|
+
stableErrorCode: reconciliationResult === "PASSED" ? undefined : reconciliationResult === "BYPASS_DETECTED" ? "TARGET_RECONCILIATION_CONFLICT" : "TARGET_RECONCILIATION_INCOMPLETE",
|
|
225
|
+
});
|
|
192
226
|
const reconciliationReport = signRuntimeObject({
|
|
193
227
|
protocolVersion: BROWSER_ENFORCEMENT_PROTOCOL,
|
|
194
228
|
objectType: "ReconciliationReport",
|
|
@@ -240,6 +274,14 @@ export async function executeBrowserEnforcement(options) {
|
|
|
240
274
|
};
|
|
241
275
|
}
|
|
242
276
|
catch (error) {
|
|
277
|
+
if (dispatchStarted && !dispatchAcknowledged) {
|
|
278
|
+
try {
|
|
279
|
+
await options.recordExecutionAttempt(executionSessionId, { phase: "DISPATCH_UNKNOWN", stableErrorCode: "TARGET_RESULT_UNKNOWN" });
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
// The authoritative attempt remains DISPATCH_STARTED and recovery must reconcile it.
|
|
283
|
+
}
|
|
284
|
+
}
|
|
243
285
|
try {
|
|
244
286
|
await append("EXECUTION_SESSION_FAILED", { reasonCode: error instanceof Error ? error.message.slice(0, 200) : "EXECUTION_FAILED" });
|
|
245
287
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export const CANONICALIZATION_VERSION = "witnora.canonical_json.v0.2";
|
|
3
|
+
const MAX_DEPTH = 64;
|
|
4
|
+
export function canonicalJson(value) {
|
|
5
|
+
return JSON.stringify(canonicalValue(value, "$", 0));
|
|
6
|
+
}
|
|
7
|
+
export function sha256Canonical(value) {
|
|
8
|
+
return createHash("sha256").update(canonicalJson(value), "utf8").digest("hex");
|
|
9
|
+
}
|
|
10
|
+
function canonicalValue(value, path, depth) {
|
|
11
|
+
if (depth > MAX_DEPTH)
|
|
12
|
+
throw new Error(`${path} exceeds the v0.2 nesting limit of ${MAX_DEPTH}.`);
|
|
13
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
14
|
+
return value;
|
|
15
|
+
if (typeof value === "number") {
|
|
16
|
+
if (!Number.isFinite(value))
|
|
17
|
+
throw new Error(`${path} contains a non-finite number.`);
|
|
18
|
+
if (Object.is(value, -0))
|
|
19
|
+
throw new Error(`${path} contains negative zero.`);
|
|
20
|
+
if (!Number.isSafeInteger(value)) {
|
|
21
|
+
throw new Error(`${path} must be a safe integer; encode decimals as normalized strings.`);
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(value)) {
|
|
26
|
+
return value.map((item, index) => canonicalValue(item, `${path}[${index}]`, depth + 1));
|
|
27
|
+
}
|
|
28
|
+
if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
|
|
29
|
+
const source = value;
|
|
30
|
+
const output = {};
|
|
31
|
+
for (const key of Object.keys(source).sort()) {
|
|
32
|
+
if (source[key] === undefined)
|
|
33
|
+
throw new Error(`${path}.${key} is undefined.`);
|
|
34
|
+
output[key] = canonicalValue(source[key], `${path}.${key}`, depth + 1);
|
|
35
|
+
}
|
|
36
|
+
return output;
|
|
37
|
+
}
|
|
38
|
+
throw new Error(`${path} contains unsupported ${typeof value}.`);
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadProbeConfig } from "./config.js";
|
|
3
|
+
import { createJsonLogger } from "./logger.js";
|
|
4
|
+
import { WitnoraProbe } from "./probe.js";
|
|
5
|
+
import { createProbeServer } from "./server.js";
|
|
6
|
+
const logger = createJsonLogger();
|
|
7
|
+
const configPath = process.env.WITNORA_PROBE_CONFIG;
|
|
8
|
+
if (!configPath) {
|
|
9
|
+
logger.error("probe.startup_failed", { reason: "WITNORA_PROBE_CONFIG is required." });
|
|
10
|
+
process.exitCode = 1;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
try {
|
|
14
|
+
const config = await loadProbeConfig(configPath);
|
|
15
|
+
const probe = new WitnoraProbe(config, logger);
|
|
16
|
+
probe.start();
|
|
17
|
+
const http = createProbeServer(probe, logger, config.requestBodyLimitBytes);
|
|
18
|
+
const address = await http.listen(config.host, config.port);
|
|
19
|
+
logger.info("probe.started", { host: address.address, port: address.port, probeId: config.bindings.probeId });
|
|
20
|
+
let stopping = false;
|
|
21
|
+
const stop = async (signal) => {
|
|
22
|
+
if (stopping)
|
|
23
|
+
return;
|
|
24
|
+
stopping = true;
|
|
25
|
+
logger.info("probe.shutdown_requested", { signal });
|
|
26
|
+
await http.close();
|
|
27
|
+
await probe.shutdown();
|
|
28
|
+
};
|
|
29
|
+
process.on("SIGTERM", () => void stop("SIGTERM"));
|
|
30
|
+
process.on("SIGINT", () => void stop("SIGINT"));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
logger.error("probe.startup_failed", { reason: error instanceof Error ? error.message : "Unknown startup error." });
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
}
|