witnora 0.9.3 → 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.
Files changed (77) hide show
  1. package/README.md +76 -9
  2. package/dist/assurance-contract.js +312 -0
  3. package/dist/assurance-loop-demo.js +245 -0
  4. package/dist/authorization-state-machine.js +35 -0
  5. package/dist/authorization-v02.js +60 -0
  6. package/dist/bundle.js +22 -0
  7. package/dist/canonical-v02.js +197 -0
  8. package/dist/canonical.js +31 -0
  9. package/dist/cli.js +132 -0
  10. package/dist/command-help.js +81 -0
  11. package/dist/control-plane.js +12 -3
  12. package/dist/credentials.js +1 -1
  13. package/dist/design-partner-pilot.js +402 -0
  14. package/dist/design-partner-v02.js +449 -0
  15. package/dist/evidence-v02.js +118 -0
  16. package/dist/generic-eval.js +160 -0
  17. package/dist/github-design-partner-v02.js +399 -0
  18. package/dist/github-live-v02.js +45 -0
  19. package/dist/guided-setup.js +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/offline-verifier.js +375 -0
  22. package/dist/onboard.js +132 -0
  23. package/dist/onboarding-templates.js +49 -6
  24. package/dist/outcome-evaluator.js +51 -0
  25. package/dist/privacy-manifest.js +273 -0
  26. package/dist/probe-protocol-v02.js +101 -0
  27. package/dist/runtime-context.js +75 -0
  28. package/dist/sandbox.js +1 -1
  29. package/dist/schema-validator.js +141 -3
  30. package/dist/trust-v02.js +111 -0
  31. package/dist/try.js +1 -1
  32. package/dist/vendor/design-partner/failure-exercises.mjs +353 -0
  33. package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts +8 -0
  34. package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts.map +1 -1
  35. package/dist/vendor/onegent-runtime/browser-enforcement-runtime.js +43 -1
  36. package/dist/vendor/witnora-probe/canonical.d.ts +3 -0
  37. package/dist/vendor/witnora-probe/canonical.js +39 -0
  38. package/dist/vendor/witnora-probe/cli.d.ts +2 -0
  39. package/dist/vendor/witnora-probe/cli.js +36 -0
  40. package/dist/vendor/witnora-probe/config.d.ts +2 -0
  41. package/dist/vendor/witnora-probe/config.js +63 -0
  42. package/dist/vendor/witnora-probe/credential-resolver.d.ts +6 -0
  43. package/dist/vendor/witnora-probe/credential-resolver.js +17 -0
  44. package/dist/vendor/witnora-probe/crypto.d.ts +2 -0
  45. package/dist/vendor/witnora-probe/crypto.js +14 -0
  46. package/dist/vendor/witnora-probe/emulator.d.ts +51 -0
  47. package/dist/vendor/witnora-probe/emulator.js +164 -0
  48. package/dist/vendor/witnora-probe/index.d.ts +11 -0
  49. package/dist/vendor/witnora-probe/index.js +11 -0
  50. package/dist/vendor/witnora-probe/logger.d.ts +3 -0
  51. package/dist/vendor/witnora-probe/logger.js +26 -0
  52. package/dist/vendor/witnora-probe/metrics.d.ts +12 -0
  53. package/dist/vendor/witnora-probe/metrics.js +31 -0
  54. package/dist/vendor/witnora-probe/probe.d.ts +22 -0
  55. package/dist/vendor/witnora-probe/probe.js +208 -0
  56. package/dist/vendor/witnora-probe/request-verifier.d.ts +9 -0
  57. package/dist/vendor/witnora-probe/request-verifier.js +84 -0
  58. package/dist/vendor/witnora-probe/server.d.ts +9 -0
  59. package/dist/vendor/witnora-probe/server.js +85 -0
  60. package/dist/vendor/witnora-probe/storage.d.ts +16 -0
  61. package/dist/vendor/witnora-probe/storage.js +114 -0
  62. package/dist/vendor/witnora-probe/types.d.ts +126 -0
  63. package/dist/vendor/witnora-probe/types.js +2 -0
  64. package/dist/vendor/witnora-verifier-python/README.md +43 -0
  65. package/dist/vendor/witnora-verifier-python/pyproject.toml +23 -0
  66. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/__init__.py +33 -0
  67. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/canonical.py +218 -0
  68. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/cli.py +75 -0
  69. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/conformance.py +124 -0
  70. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/crypto.py +139 -0
  71. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/errors.py +15 -0
  72. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/event_chain.py +80 -0
  73. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/evidence.py +336 -0
  74. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/probe.py +188 -0
  75. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/producer.py +127 -0
  76. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/trust.py +337 -0
  77. package/package.json +5 -5
@@ -0,0 +1,449 @@
1
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+ import { compileAssuranceContract, exampleAssuranceContract } from "./assurance-contract.js";
4
+ import { examplePrivacyManifest } from "./privacy-manifest.js";
5
+ import { collectSupportBundle, formatDoctorReport, formatPrivacyInspection, genericEvalExample, initializePilotState, markPilotLifecycle, pilotPackageFiles, referenceReleaseCandidateManifest, runPilotDoctor, runPilotLifecycleDemonstration, snapshotPilotRuntime, writePilotAcceptanceReport } from "./design-partner-pilot.js";
6
+ import { createAcceptanceTrustMaterial, createGitHubAcceptanceIdentity, executionBoundaryDigest, githubActionDigest, issueAcceptanceAuthorization, readGitHubTargetState, revokeProbeCertificate, rotateProbeCertificate, runDesignPartnerAcceptance, startGitHubTargetEmulator } from "./github-design-partner-v02.js";
7
+ import { verifyEvidencePacketV02 } from "./evidence-v02.js";
8
+ import { assertLiveGitHubSafety, readLiveGitHubPullRequest } from "./github-live-v02.js";
9
+ export function hardenedDesignPartnerCompose(relativeRoot = ".") {
10
+ return baseDesignPartnerCompose()
11
+ .replace("design-partner target-emulator --state", "design-partner target-emulator --dir /kit --state")
12
+ .replace(" - acceptance-data:/output\n", " - ./acceptance:/output\n")
13
+ .replace(" - acceptance-data:/output:ro\n", " - ./acceptance:/output:ro\n")
14
+ .replaceAll("context: \${WITNORA_SOURCE_ROOT:-../..}/packages/witnora-cli\n dockerfile: Dockerfile.design-partner", "context: ./docker/runtime\n dockerfile: Dockerfile")
15
+ .replace("context: \${WITNORA_SOURCE_ROOT:-../..}/packages/witnora-probe\n command: [\"node\", \"dist/cli.js\"]", "context: ./docker/runtime\n dockerfile: Dockerfile\n entrypoint: [\"node\", \"/app/dist/vendor/witnora-probe/cli.js\"]")
16
+ .replace(/^\s{2}acceptance-data: \{\}\r?\n/mu, "")
17
+ .replaceAll("- ./", `- ${relativeRoot}/`)
18
+ .replaceAll("file: ./secrets/", `file: ${relativeRoot}/secrets/`)
19
+ .replaceAll("context: ./docker/runtime", `context: ${relativeRoot}/docker/runtime`);
20
+ }
21
+ export async function runDesignPartnerCommand(args) {
22
+ const action = args[0] ?? "help";
23
+ if (action === "init")
24
+ return init(args);
25
+ if (action === "doctor")
26
+ return doctor(args);
27
+ if (action === "dry-run")
28
+ return dryRun(args);
29
+ if (action === "run-acceptance")
30
+ return acceptance(args);
31
+ if (action === "recover-acceptance")
32
+ return acceptance([...args, "--recover-only"]);
33
+ if (action === "prepare-live-github")
34
+ return prepareLiveGitHub(args);
35
+ if (action === "authorization" && args[1] === "issue")
36
+ return issueAuthorization(args);
37
+ if (action === "verify-offline")
38
+ return verifyOffline(args);
39
+ if (action === "privacy" && args[1] === "inspect")
40
+ return inspectPrivacy(args);
41
+ if (action === "collect-support-bundle")
42
+ return supportBundle(args);
43
+ if (action === "pilot-report")
44
+ return pilotReport(args);
45
+ if (action === "pilot-lifecycle")
46
+ return pilotLifecycle(args);
47
+ if (action === "trust" && args[1] === "inspect")
48
+ return inspectTrust(args);
49
+ if (action === "trust" && args[1] === "revoke-probe")
50
+ return revokeProbe(args);
51
+ if (action === "trust" && args[1] === "rotate-probe")
52
+ return rotateProbe(args);
53
+ if (action === "target-emulator")
54
+ return targetEmulator(args);
55
+ if (action === "help" || action === "--help" || action === "-h") {
56
+ process.stdout.write(help());
57
+ return;
58
+ }
59
+ throw new Error(`Unknown design-partner command: ${action}. Run 'witnora design-partner --help'.`);
60
+ }
61
+ async function init(args) {
62
+ if ((args[1] ?? "github-pr-merge") !== "github-pr-merge")
63
+ throw new Error("Only github-pr-merge is supported in v0.2.");
64
+ const outDir = resolve(flag(args, "--out") ?? ".witnora/design-partner");
65
+ const trustDir = join(outDir, "trust");
66
+ const secretsDir = join(outDir, "secrets");
67
+ await Promise.all([mkdir(trustDir, { recursive: true }), mkdir(secretsDir, { recursive: true })]);
68
+ const declaredTarget = { owner: "witnora-design-partner", repository: "assurance-sandbox", pullRequestNumber: 17, baseBranch: "acceptance", baseSha: "base-a", headBranch: "agent-change", headSha: "head-a", open: true, merged: false, checks: { build: "success", policy: "success" }, mergeable: true, targetVersion: 1 };
69
+ const config = { protocolVersion: "witnora.design_partner_github.v0.2", tenantRef: "tenant:design-partner", projectRef: "project:github-sandbox", environment: "sandbox", targetUrl: "http://target-sandbox:4311", probeUrl: "http://probe:4312", readCredentialEnv: "WITNORA_GITHUB_READ_CREDENTIAL", writeCredentialEnv: "WITNORA_GITHUB_WRITE_CREDENTIAL", trustDir: "./trust", legacyUnprotectedPathEnabled: false, declaredTarget };
70
+ const trust = createAcceptanceTrustMaterial({ tenantRef: config.tenantRef, projectRef: config.projectRef, environment: config.environment });
71
+ const contract = exampleAssuranceContract();
72
+ contract.subject.agentId = "design-partner-github-agent";
73
+ contract.subject.environment = "sandbox";
74
+ contract.capability.id = "github.pull_request.merge";
75
+ contract.capability.action = "UPDATE";
76
+ const files = {
77
+ "design-partner.json": JSON.stringify(config, null, 2),
78
+ "witnora.assurance-contract.json": JSON.stringify(contract, null, 2),
79
+ "witnora.privacy-manifest.json": JSON.stringify(examplePrivacyManifest(), null, 2),
80
+ ".env.example": "# Synthetic emulator values only; do not reuse for a live sandbox.\nWITNORA_GITHUB_READ_CREDENTIAL=local-emulator-read-only\nWITNORA_GITHUB_WRITE_CREDENTIAL=local-emulator-write-only\nWITNORA_TARGET_URL=http://127.0.0.1:4311\nWITNORA_PROBE_URL=http://127.0.0.1:4312\nWITNORA_PROBE_STORAGE_KEY_BASE64=AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=\n",
81
+ ".gitignore": "secrets/\nprobe/data/\nacceptance/\nclaims/\nsupport-bundle/\n.env\n",
82
+ "sandbox-policy.json": JSON.stringify({ repositories: ["witnora-design-partner/assurance-sandbox"], protectedBranches: ["main", "master"], liveModeDefault: false }, null, 2),
83
+ "acceptance-checklist.md": checklist(),
84
+ "docker-compose.yml": hardenedDesignPartnerCompose("."),
85
+ "docker/compose.yml": hardenedDesignPartnerCompose(".."),
86
+ "trust/pinned-root.json": JSON.stringify(trust.root, null, 2),
87
+ "trust/gateway-certificate.json": JSON.stringify(trust.gatewayCertificate, null, 2),
88
+ "trust/request-signer-certificate.json": JSON.stringify(trust.requestSignerCertificate, null, 2),
89
+ "trust/probe-certificate.json": JSON.stringify(trust.probeCertificate, null, 2),
90
+ "trust/approval-certificate.json": JSON.stringify(trust.approvalCertificate, null, 2),
91
+ "trust/grant-certificate.json": JSON.stringify(trust.grantCertificate, null, 2),
92
+ "trust/revocation-journal.json": JSON.stringify(trust.journal, null, 2),
93
+ "probe/config.json": JSON.stringify(probeConfig(config, trust, "http://127.0.0.1:4311", "./data", "../secrets/probe-private.pem"), null, 2),
94
+ "probe/config.container.json": JSON.stringify(probeConfig(config, trust, "http://target-sandbox:4311", "/app/data", "/app/keys/probe-private.pem"), null, 2),
95
+ "probe/request-signer-public.pem": trust.requestSignerCertificate.publicKeyPem,
96
+ "secrets/root-private.pem": trust.rootPrivateKeyPem,
97
+ "secrets/request-signer-private.pem": trust.requestSignerPrivateKeyPem,
98
+ "secrets/gateway-private.pem": trust.gatewayPrivateKeyPem,
99
+ "secrets/probe-private.pem": trust.probePrivateKeyPem,
100
+ "secrets/approval-private.pem": trust.approvalPrivateKeyPem,
101
+ "secrets/grant-private.pem": trust.grantPrivateKeyPem,
102
+ };
103
+ const customerFiles = pilotPackageFiles(config);
104
+ customerFiles["contract/assurance-contract.json"] = JSON.stringify(contract, null, 2);
105
+ customerFiles["privacy/privacy-manifest.json"] = JSON.stringify(examplePrivacyManifest(), null, 2);
106
+ customerFiles["examples/generic-eval.json"] = JSON.stringify(genericEvalExample(compileAssuranceContract(contract).revalidationFingerprint), null, 2);
107
+ Object.assign(files, customerFiles);
108
+ await Promise.all(Object.entries(files).map(async ([relative, content]) => { const path = join(outDir, relative); await mkdir(resolve(path, ".."), { recursive: true }); await writeFile(path, `${content.trimEnd()}\n`, { flag: "wx" }); }));
109
+ for (const secret of [join(secretsDir, "root-private.pem"), join(secretsDir, "request-signer-private.pem"), join(secretsDir, "gateway-private.pem"), join(secretsDir, "probe-private.pem"), join(secretsDir, "approval-private.pem"), join(secretsDir, "grant-private.pem")])
110
+ await chmod(secret, 0o600).catch(() => undefined);
111
+ await writeFile(join(outDir, "target-state.json"), `${JSON.stringify(declaredTarget, null, 2)}\n`);
112
+ await snapshotPilotRuntime(outDir);
113
+ await Promise.all([mkdir(join(outDir, "acceptance"), { recursive: true }), mkdir(join(outDir, "claims"), { recursive: true })]);
114
+ await initializePilotState(outDir);
115
+ await writeFile(join(outDir, "release-candidate-manifest.json"), `${JSON.stringify(referenceReleaseCandidateManifest(), null, 2)}\n`);
116
+ process.stdout.write(`Initialized the self-contained GitHub design-partner kit at ${outDir}\n\nNext:\n 1. Open QUICKSTART.md. The first run uses local synthetic credentials only.\n 2. Issue a short-lived signed approval and Grant.\n 3. Start the Docker Compose topology.\n 4. Run doctor, privacy inspect, and dry-run.\n 5. Run acceptance only against the allowlisted sandbox.\n`);
117
+ }
118
+ async function doctor(args) {
119
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
120
+ const report = await runPilotDoctor(dir, await readConfig(dir));
121
+ process.stdout.write(formatDoctorReport(report));
122
+ if (!report.ready)
123
+ throw new Error(`Design-partner doctor found ${report.summary.FAIL + report.summary.NOT_CONFIGURED} unsafe or incomplete condition(s).`);
124
+ }
125
+ async function dryRun(args) {
126
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
127
+ const config = await readConfig(dir);
128
+ const readCredential = requiredEnvironment(config.readCredentialEnv);
129
+ const targetUrl = flag(args, "--target-url") ?? process.env.WITNORA_TARGET_URL ?? config.targetUrl;
130
+ const before = await readGitHubTargetState(targetUrl, readCredential);
131
+ if (before.merged)
132
+ throw new Error("Dry-run target is already merged; reset the sandbox fixture.");
133
+ process.stdout.write(`PASS Contract validation\nPASS Canonical target identity preview\nPASS Policy requires exact approval\nPASS Read-only target precondition\nPASS No target write performed\nTarget: ${before.owner}/${before.repository}#${before.pullRequestNumber}\n`);
134
+ }
135
+ async function acceptance(args) {
136
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
137
+ const config = await readConfig(dir);
138
+ const outDir = flag(args, "--out") ?? join(dir, "acceptance");
139
+ const authorization = await readAuthorization(dir);
140
+ const outcome = await runDesignPartnerAcceptance({ outDir, attemptStatePath: join(outDir, "gateway-attempt-v0.2.json"), grantClaimDirectory: process.env.WITNORA_GRANT_CLAIM_DIR ?? join(dir, "claims"), targetUrl: flag(args, "--target-url") ?? process.env.WITNORA_TARGET_URL ?? config.targetUrl, probeUrl: flag(args, "--probe-url") ?? process.env.WITNORA_PROBE_URL ?? config.probeUrl, writeCredential: requiredEnvironment(config.writeCredentialEnv), declaredTarget: config.declaredTarget, tenantRef: config.tenantRef, projectRef: config.projectRef, environment: config.environment, trustMaterial: await readGatewayTrustMaterial(dir), authorization, crashAfterTargetSuccess: args.includes("--crash-after-target-success"), crashAfterTargetAcknowledged: args.includes("--crash-after-target-acknowledged"), recoverOnly: args.includes("--recover-only") });
141
+ const now = new Date().toISOString();
142
+ await markPilotLifecycle(dir, { firstSignedObservationAt: now, firstTargetDispatchAt: now, ...(outcome.report.overall === "PASS" ? { firstVerifiedOutcomeAt: now } : {}), recoveryRequiredCount: args.includes("--recover-only") ? 1 : 0 });
143
+ await writePilotAcceptanceReport(dir);
144
+ process.stdout.write(`PASS 1/5 Approval and one-use Grant validated\nPASS 2/5 Durable dispatch journal written\nPASS 3/5 Target dispatch completed or reconciled\nPASS 4/5 Independent Probe observation received\n${outcome.report.overall === "PASS" ? "PASS" : "FAIL"} 5/5 Evidence Packet verified\nEvidence Packet v0.2: ${outcome.paths.packet}\nOffline verification: ${outcome.report.overall}\nMetrics: ${JSON.stringify(outcome.metrics)}\n`);
145
+ if (outcome.report.overall !== "PASS")
146
+ process.exitCode = 1;
147
+ }
148
+ async function prepareLiveGitHub(args) {
149
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
150
+ const config = await readConfig(dir);
151
+ const repository = requiredFlag(args, "--repository");
152
+ const pullRequestNumber = Number(requiredFlag(args, "--pull-request"));
153
+ const [owner, name, ...rest] = repository.split("/");
154
+ if (!owner || !name || rest.length > 0 || !Number.isSafeInteger(pullRequestNumber) || pullRequestNumber < 1) {
155
+ throw new Error("Live GitHub target must use --repository owner/name and a positive --pull-request number.");
156
+ }
157
+ const allowlist = requiredEnvironment("WITNORA_LIVE_GITHUB_ALLOWLIST");
158
+ const target = { owner, repository: name, pullRequestNumber };
159
+ const state = await readLiveGitHubPullRequest({
160
+ ...target,
161
+ credential: requiredEnvironment(config.readCredentialEnv),
162
+ requiredChecks: Object.keys(config.declaredTarget.checks),
163
+ apiBase: flag(args, "--api-base"),
164
+ });
165
+ assertLiveGitHubSafety(target, state.baseBranch, allowlist);
166
+ if (state.merged || !state.open)
167
+ throw new Error("Live GitHub sandbox pull request must be open and unmerged.");
168
+ config.declaredTarget = state;
169
+ await writeFile(join(dir, "design-partner.json"), `${JSON.stringify(config, null, 2)}\n`);
170
+ const trust = await readGatewayTrustMaterial(dir);
171
+ const liveProbeConfig = probeConfig(config, trust, "", "./data-live", "../secrets/probe-private.pem");
172
+ delete liveProbeConfig.githubTarget;
173
+ liveProbeConfig.githubLive = {
174
+ apiBase: flag(args, "--api-base") ?? "https://api.github.com",
175
+ requiredCredentialRef: `env:${config.readCredentialEnv}`,
176
+ };
177
+ await writeFile(join(dir, "probe/config.live.json"), `${JSON.stringify(liveProbeConfig, null, 2)}\n`);
178
+ process.stdout.write(`Prepared live GitHub sandbox ${repository}#${pullRequestNumber}. Review the exact SHAs, then issue fresh authorization before dispatch.\n`);
179
+ }
180
+ async function verifyOffline(args) {
181
+ const packetPath = flag(args, "--packet") ?? args[1];
182
+ const rootPath = flag(args, "--root");
183
+ if (!packetPath || !rootPath || !flag(args, "--revocation-journal"))
184
+ throw new Error("verify-offline requires --packet, --root, and --revocation-journal from a trusted current source.");
185
+ const packet = JSON.parse(await readFile(resolve(packetPath), "utf8"));
186
+ const root = JSON.parse(await readFile(resolve(rootPath), "utf8"));
187
+ const journalPath = flag(args, "--revocation-journal");
188
+ const latestJournal = JSON.parse(await readFile(resolve(journalPath), "utf8"));
189
+ const historicalAt = flag(args, "--historical-at");
190
+ const report = verifyEvidencePacketV02(packet, root, historicalAt ? new Date(historicalAt) : new Date(), latestJournal);
191
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
192
+ const dir = flag(args, "--dir");
193
+ if (report.overall === "PASS" && dir) {
194
+ await markPilotLifecycle(resolve(dir), { firstOfflineVerificationAt: new Date().toISOString() });
195
+ await writePilotAcceptanceReport(resolve(dir));
196
+ }
197
+ if (report.overall !== "PASS")
198
+ process.exitCode = 1;
199
+ }
200
+ async function inspectPrivacy(args) { const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner"); const manifest = JSON.parse(await readFile(join(dir, "witnora.privacy-manifest.json"), "utf8")); process.stdout.write(formatPrivacyInspection(manifest, await readConfig(dir))); }
201
+ async function supportBundle(args) { const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner"); const out = flag(args, "--out"); const result = await collectSupportBundle(dir, await readConfig(dir), out ? resolve(out) : undefined); process.stdout.write(`Support bundle: ${result.bundlePath}\nPrivacy scan: ${result.scanPath}\nRaw secret canary matches: 0\n`); }
202
+ async function pilotReport(args) { const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner"); process.stdout.write(`${JSON.stringify(await writePilotAcceptanceReport(dir), null, 2)}\n`); }
203
+ async function pilotLifecycle(args) {
204
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
205
+ const report = await runPilotLifecycleDemonstration(dir);
206
+ const stages = report.stages;
207
+ process.stdout.write(`${stages.map((stage, index) => `${index + 1}/4 ${stage.status.padEnd(30)} ${stage.stage}`).join("\n")}\nReport: ${join(dir, "pilot/lifecycle-demonstration.json")}\n`);
208
+ }
209
+ async function inspectTrust(args) { const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner"); const trust = await readGatewayTrustMaterial(dir); process.stdout.write(`Pinned root: ${trust.root.rootFingerprint}\nGateway role: ${trust.gatewayCertificate.role}\nProbe role: ${trust.probeCertificate.role}\nRevocation checkpoint: ${trust.journal.checkpointDigest}\n`); }
210
+ async function issueAuthorization(args) {
211
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
212
+ const config = await readConfig(dir);
213
+ const trust = await readAdministrativeTrustMaterial(dir);
214
+ const identity = createGitHubAcceptanceIdentity(config.declaredTarget, config.targetUrl);
215
+ const authorization = issueAcceptanceAuthorization(trust, { tenantRef: config.tenantRef, projectRef: config.projectRef, environment: config.environment, actionDigest: githubActionDigest(identity) });
216
+ await mkdir(join(dir, "authorization"), { recursive: true });
217
+ await Promise.all([writeFile(join(dir, "authorization/approval.json"), `${JSON.stringify(authorization.approval, null, 2)}\n`), writeFile(join(dir, "authorization/grant.json"), `${JSON.stringify(authorization.grant, null, 2)}\n`)]);
218
+ await markPilotLifecycle(dir, { firstGrantIssuedAt: new Date().toISOString() });
219
+ process.stdout.write(`Issued signed approval ${authorization.approval.approvalId} and single-use Grant ${authorization.grant.grantId}. Private issuer keys remain outside Gateway and target runtime mounts.\n`);
220
+ }
221
+ async function revokeProbe(args) {
222
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
223
+ const trust = revokeProbeCertificate(await readAdministrativeTrustMaterial(dir), { effectiveAt: flag(args, "--effective-at") ?? new Date().toISOString(), retroactive: args.includes("--retroactive"), reason: flag(args, "--reason") ?? "Customer-operated Probe key revoked." });
224
+ await writeFile(join(dir, "trust/revocation-journal.json"), `${JSON.stringify(trust.journal, null, 2)}\n`);
225
+ process.stdout.write(`Revoked Probe certificate ${trust.probeCertificate.certificateId}. New observations at or after the effective time will fail closed.\n`);
226
+ }
227
+ async function rotateProbe(args) {
228
+ const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner");
229
+ const trust = rotateProbeCertificate(await readAdministrativeTrustMaterial(dir));
230
+ await Promise.all([
231
+ writeFile(join(dir, "trust/probe-certificate.json"), `${JSON.stringify(trust.probeCertificate, null, 2)}\n`),
232
+ writeFile(join(dir, "secrets/probe-private.pem"), trust.probePrivateKeyPem),
233
+ ]);
234
+ const updateConfig = async (relative) => { const path = join(dir, relative); const value = JSON.parse(await readFile(path, "utf8")); value.probeSigningKey.keyCertificateId = trust.probeCertificate.certificateId; await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); };
235
+ await Promise.all([updateConfig("probe/config.json"), updateConfig("probe/config.container.json")]);
236
+ await chmod(join(dir, "secrets/probe-private.pem"), 0o600).catch(() => undefined);
237
+ process.stdout.write(`Rotated Probe certificate to ${trust.probeCertificate.certificateId}. Restart the Probe before requesting a new observation.\n`);
238
+ }
239
+ async function targetEmulator(args) { const dir = resolve(flag(args, "--dir") ?? ".witnora/design-partner"); const trust = await readAuthorizationTrustMaterial(dir); const config = await readConfig(dir); await startGitHubTargetEmulator({ host: flag(args, "--host"), port: Number(flag(args, "--port") ?? "4311"), statePath: flag(args, "--state") ?? join(dir, "target-state.json"), readCredential: requiredEnvironment("WITNORA_GITHUB_READ_CREDENTIAL"), writeCredential: requiredEnvironment("WITNORA_GITHUB_WRITE_CREDENTIAL"), expectedExecutionBoundaryDigest: executionBoundaryDigest(config.targetUrl), authorizationTrust: trust }); }
240
+ async function readConfig(dir) { const value = JSON.parse(await readFile(join(dir, "design-partner.json"), "utf8")); if (value.protocolVersion !== "witnora.design_partner_github.v0.2")
241
+ throw new Error("Unsupported design-partner config version."); return value; }
242
+ async function readGatewayTrustMaterial(dir) {
243
+ const read = async (relative) => JSON.parse(await readFile(join(dir, relative), "utf8"));
244
+ const requestSignerKeyPath = process.env.WITNORA_REQUEST_SIGNER_PRIVATE_KEY_PATH ?? join(dir, "secrets/request-signer-private.pem");
245
+ const gatewayKeyPath = process.env.WITNORA_GATEWAY_PRIVATE_KEY_PATH ?? join(dir, "secrets/gateway-private.pem");
246
+ return { root: await read("trust/pinned-root.json"), requestSignerCertificate: await read("trust/request-signer-certificate.json"), gatewayCertificate: await read("trust/gateway-certificate.json"), probeCertificate: await read("trust/probe-certificate.json"), approvalCertificate: await read("trust/approval-certificate.json"), grantCertificate: await read("trust/grant-certificate.json"), journal: await read("trust/revocation-journal.json"), requestSignerPrivateKeyPem: await readFile(requestSignerKeyPath, "utf8"), gatewayPrivateKeyPem: await readFile(gatewayKeyPath, "utf8") };
247
+ }
248
+ async function readAuthorizationTrustMaterial(dir) {
249
+ const read = async (relative) => JSON.parse(await readFile(join(dir, relative), "utf8"));
250
+ return {
251
+ root: await read("trust/pinned-root.json"),
252
+ approvalCertificate: await read("trust/approval-certificate.json"),
253
+ grantCertificate: await read("trust/grant-certificate.json"),
254
+ journal: await read("trust/revocation-journal.json"),
255
+ };
256
+ }
257
+ async function readAdministrativeTrustMaterial(dir) {
258
+ return { ...await readGatewayTrustMaterial(dir), rootPrivateKeyPem: await readFile(join(dir, "secrets/root-private.pem"), "utf8"), probePrivateKeyPem: await readFile(join(dir, "secrets/probe-private.pem"), "utf8"), approvalPrivateKeyPem: await readFile(join(dir, "secrets/approval-private.pem"), "utf8"), grantPrivateKeyPem: await readFile(join(dir, "secrets/grant-private.pem"), "utf8") };
259
+ }
260
+ async function readAuthorization(dir) { return { approval: JSON.parse(await readFile(join(dir, "authorization/approval.json"), "utf8")), grant: JSON.parse(await readFile(join(dir, "authorization/grant.json"), "utf8")) }; }
261
+ function requiredEnvironment(name) { const value = process.env[name]; if (!value)
262
+ throw new Error(`${name} must be supplied by the customer secret store.`); return value; }
263
+ function flag(args, name) { const index = args.indexOf(name); return index >= 0 ? args[index + 1] : undefined; }
264
+ function requiredFlag(args, name) { const value = flag(args, name); if (!value)
265
+ throw new Error(`${name} is required.`); return value; }
266
+ function help() { return `Usage:\n witnora design-partner init github-pr-merge --out ./witnora-pilot\n witnora design-partner authorization issue --dir ./witnora-pilot\n witnora design-partner doctor --dir ./witnora-pilot\n witnora design-partner privacy inspect --dir ./witnora-pilot\n witnora design-partner collect-support-bundle --dir ./witnora-pilot\n witnora design-partner pilot-lifecycle --dir ./witnora-pilot\n witnora design-partner pilot-report --dir ./witnora-pilot\n witnora design-partner trust inspect --dir ./witnora-pilot\n witnora design-partner trust revoke-probe [--effective-at <timestamp>]\n witnora design-partner trust rotate-probe\n witnora design-partner dry-run --dir ./witnora-pilot --target-url http://127.0.0.1:4311\n witnora design-partner run-acceptance --dir ./witnora-pilot\n witnora design-partner recover-acceptance --dir ./witnora-pilot\n witnora design-partner prepare-live-github --repository owner/sandbox-repo --pull-request 1 (read-only preflight)\n witnora design-partner verify-offline --packet <path> --root <path> --revocation-journal <latest.json> [--historical-at <timestamp>]\n`; }
267
+ function checklist() { return "# GitHub sandbox acceptance checklist\n\n- [ ] Repository name contains sandbox or test.\n- [ ] Base branch is not main/master.\n- [ ] Agent has no target write credential.\n- [ ] Gateway write and Probe read credentials differ.\n- [ ] Pinned root fingerprint was verified out of band.\n- [ ] Probe and Gateway secret mounts are disjoint.\n- [ ] Dry-run completed without a target write.\n- [ ] Evidence limitations were reviewed.\n"; }
268
+ function probeConfig(config, trust, targetUrl, stateDirectory, privateKeyPemPath) {
269
+ return {
270
+ host: "0.0.0.0", port: 4312, stateDirectory, requestBodyLimitBytes: 65_536,
271
+ observationTimeoutMs: 5_000, shutdownTimeoutMs: 10_000,
272
+ retry: { maxAttempts: 4, baseDelayMs: 250, scanIntervalMs: 500 },
273
+ bindings: { tenantRef: config.tenantRef, projectRef: config.projectRef, environment: config.environment, probeId: "customer-github-probe" },
274
+ requestVerificationKeys: [{ keyId: trust.requestSignerCertificate.subjectKeyId, publicKeyPemPath: "./request-signer-public.pem", role: "probe_observation_request_signer", validFrom: trust.requestSignerCertificate.notBefore, validUntil: trust.requestSignerCertificate.notAfter }],
275
+ probeSigningKey: { keyCertificateId: trust.probeCertificate.certificateId, privateKeyPemPath },
276
+ probeVersion: "0.2.0", storageEncryptionKeyEnv: "WITNORA_PROBE_STORAGE_KEY_BASE64",
277
+ githubTarget: { url: targetUrl, requiredCredentialRef: `env:${config.readCredentialEnv}` },
278
+ };
279
+ }
280
+ function baseDesignPartnerCompose() {
281
+ return `services:
282
+ target-data-init:
283
+ image: node:22-alpine
284
+ user: "0:0"
285
+ read_only: true
286
+ network_mode: none
287
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=4m"]
288
+ command: ["sh", "-eu", "-c", "if [ ! -f /state/target-state.json ]; then cp /seed/target-state.json /state/target-state.json; fi; chown -R 10001:10001 /state; chmod 0700 /state; chmod 0600 /state/target-state.json"]
289
+ volumes:
290
+ - ./target-state.json:/seed/target-state.json:ro
291
+ - target-data:/state
292
+ target-sandbox:
293
+ build:
294
+ context: \${WITNORA_SOURCE_ROOT:-../..}/packages/witnora-cli
295
+ dockerfile: Dockerfile.design-partner
296
+ entrypoint: ["/bin/sh", "-c"]
297
+ command: ["exec node /app/dist/cli.js design-partner target-emulator --state /state/target-state.json --port 4311"]
298
+ user: "10001:10001"
299
+ read_only: true
300
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=16m"]
301
+ volumes:
302
+ - ./target-state.json:/seed/target-state.json:ro
303
+ - ./design-partner.json:/kit/design-partner.json:ro
304
+ - ./trust:/kit/trust:ro
305
+ - target-data:/state
306
+ environment:
307
+ WITNORA_GITHUB_READ_CREDENTIAL: \${WITNORA_GITHUB_READ_CREDENTIAL:?set a read-only sandbox credential}
308
+ WITNORA_GITHUB_WRITE_CREDENTIAL: \${WITNORA_GITHUB_WRITE_CREDENTIAL:?set a distinct write sandbox credential}
309
+ ports: ["127.0.0.1:4311:4311"]
310
+ networks: [read-path, write-path]
311
+ depends_on:
312
+ target-data-init:
313
+ condition: service_completed_successfully
314
+ probe-data-init:
315
+ image: node:22-alpine
316
+ user: "0:0"
317
+ read_only: true
318
+ network_mode: none
319
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=4m"]
320
+ command: ["sh", "-eu", "-c", "chown -R 10001:10001 /data; chmod 0700 /data"]
321
+ volumes:
322
+ - probe-data:/data
323
+ probe-key-init:
324
+ image: node:22-alpine
325
+ user: "0:0"
326
+ read_only: true
327
+ network_mode: none
328
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=4m"]
329
+ command: ["sh", "-eu", "-c", "umask 077; cp /run/secrets/probe-private.pem /keys/probe-private.pem; chown 10001:10001 /keys/probe-private.pem; chmod 0400 /keys/probe-private.pem"]
330
+ volumes:
331
+ - probe-key-data:/keys
332
+ secrets:
333
+ - source: probe_signing_key
334
+ target: probe-private.pem
335
+ probe:
336
+ build:
337
+ context: \${WITNORA_SOURCE_ROOT:-../..}/packages/witnora-probe
338
+ command: ["node", "dist/cli.js"]
339
+ user: "10001:10001"
340
+ read_only: true
341
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=16m"]
342
+ volumes:
343
+ - ./probe/config.container.json:/kit/probe/config.container.json:ro
344
+ - ./probe/request-signer-public.pem:/kit/probe/request-signer-public.pem:ro
345
+ - probe-data:/app/data
346
+ - probe-key-data:/app/keys:ro
347
+ environment:
348
+ WITNORA_PROBE_CONFIG: /kit/probe/config.container.json
349
+ WITNORA_PROBE_STORAGE_KEY_BASE64: \${WITNORA_PROBE_STORAGE_KEY_BASE64:?set a base64 32-byte local storage key}
350
+ WITNORA_GITHUB_READ_CREDENTIAL: \${WITNORA_GITHUB_READ_CREDENTIAL:?set a read-only sandbox credential}
351
+ ports: ["127.0.0.1:4312:4312"]
352
+ networks: [read-path]
353
+ depends_on:
354
+ target-sandbox:
355
+ condition: service_started
356
+ probe-data-init:
357
+ condition: service_completed_successfully
358
+ probe-key-init:
359
+ condition: service_completed_successfully
360
+ gateway-key-init:
361
+ profiles: ["acceptance"]
362
+ image: node:22-alpine
363
+ user: "0:0"
364
+ read_only: true
365
+ network_mode: none
366
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=4m"]
367
+ command: ["sh", "-eu", "-c", "umask 077; cp /run/secrets/request-signer-private.pem /keys/request-signer-private.pem; cp /run/secrets/gateway-private.pem /keys/gateway-private.pem; chown 10001:10001 /keys/*.pem; chmod 0400 /keys/*.pem"]
368
+ volumes:
369
+ - gateway-key-data:/keys
370
+ secrets:
371
+ - source: request_signing_key
372
+ target: request-signer-private.pem
373
+ - source: gateway_signing_key
374
+ target: gateway-private.pem
375
+ acceptance-output-init:
376
+ profiles: ["acceptance"]
377
+ image: node:22-alpine
378
+ user: "0:0"
379
+ read_only: true
380
+ network_mode: none
381
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=4m"]
382
+ command: ["sh", "-eu", "-c", "chown -R 10001:10001 /output /claims /pilot; chmod 0700 /output /claims /pilot"]
383
+ volumes:
384
+ - ./acceptance:/output
385
+ - ./claims:/claims
386
+ - ./pilot:/pilot
387
+ gateway-acceptance:
388
+ profiles: ["acceptance"]
389
+ build:
390
+ context: \${WITNORA_SOURCE_ROOT:-../..}/packages/witnora-cli
391
+ dockerfile: Dockerfile.design-partner
392
+ command: ["design-partner", "run-acceptance", "--dir", "/kit", "--out", "/output", "--target-url", "http://target-sandbox:4311", "--probe-url", "http://probe:4312"]
393
+ user: "10001:10001"
394
+ read_only: true
395
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=16m"]
396
+ volumes:
397
+ - ./design-partner.json:/kit/design-partner.json:ro
398
+ - ./trust:/kit/trust:ro
399
+ - ./authorization:/kit/authorization:ro
400
+ - ./pilot:/kit/pilot
401
+ - gateway-key-data:/app/keys:ro
402
+ - acceptance-data:/output
403
+ - ./claims:/claims
404
+ environment:
405
+ WITNORA_REQUEST_SIGNER_PRIVATE_KEY_PATH: /app/keys/request-signer-private.pem
406
+ WITNORA_GATEWAY_PRIVATE_KEY_PATH: /app/keys/gateway-private.pem
407
+ WITNORA_GRANT_CLAIM_DIR: /claims
408
+ WITNORA_GITHUB_WRITE_CREDENTIAL: \${WITNORA_GITHUB_WRITE_CREDENTIAL:?set a sandbox write credential}
409
+ networks: [read-path, write-path]
410
+ depends_on:
411
+ target-sandbox:
412
+ condition: service_started
413
+ probe:
414
+ condition: service_started
415
+ gateway-key-init:
416
+ condition: service_completed_successfully
417
+ acceptance-output-init:
418
+ condition: service_completed_successfully
419
+ offline-verifier:
420
+ profiles: ["acceptance"]
421
+ build:
422
+ context: \${WITNORA_SOURCE_ROOT:-../..}/packages/witnora-cli
423
+ dockerfile: Dockerfile.design-partner
424
+ command: ["design-partner", "verify-offline", "--packet", "/output/witnora-evidence-packet-v0.2.json", "--root", "/trust/pinned-root.json", "--revocation-journal", "/trust/revocation-journal.json"]
425
+ user: "10001:10001"
426
+ read_only: true
427
+ tmpfs: ["/tmp:rw,noexec,nosuid,size=16m"]
428
+ volumes:
429
+ - ./trust:/trust:ro
430
+ - acceptance-data:/output:ro
431
+ network_mode: none
432
+ volumes:
433
+ target-data: {}
434
+ probe-data: {}
435
+ probe-key-data: {}
436
+ gateway-key-data: {}
437
+ acceptance-data: {}
438
+ secrets:
439
+ probe_signing_key:
440
+ file: ./secrets/probe-private.pem
441
+ request_signing_key:
442
+ file: ./secrets/request-signer-private.pem
443
+ gateway_signing_key:
444
+ file: ./secrets/gateway-private.pem
445
+ networks:
446
+ write-path: {}
447
+ read-path: {}
448
+ `;
449
+ }
@@ -0,0 +1,118 @@
1
+ import { createPublicKey, verify } from "node:crypto";
2
+ import { canonicalBytesV02, sha256CanonicalV02 } from "./canonical-v02.js";
3
+ import { verifyAuthorizationV02 } from "./authorization-v02.js";
4
+ import { verifyProbeAttestation, verifyProbeRequest } from "./probe-protocol-v02.js";
5
+ import { assertNotRevoked, assertRevocationJournalExtends, verifyKeyCertificate, verifyRevocationJournal } from "./trust-v02.js";
6
+ export function gatewaySignedValue(packet) {
7
+ const { gatewaySignature: _signature, ...value } = packet;
8
+ return value;
9
+ }
10
+ export function verifyEvidencePacketV02(packet, pinnedRoot, verificationTime = new Date(), latestRevocationJournal) {
11
+ const failures = [];
12
+ if (!packet || typeof packet !== "object" || packet.protocolVersion !== "witnora.verifiable_evidence_packet.v0.2"
13
+ || !packet.approval || !packet.grant || !packet.executionAttempt || !packet.reconciliation || !packet.probeRequest
14
+ || !packet.probeAttestation || !packet.trust || !packet.separation || !packet.privacy || !packet.evidenceStrength || !packet.gatewaySignature) {
15
+ return failedReport("Evidence packet v0.2 is structurally incomplete.");
16
+ }
17
+ let crypto = true;
18
+ try {
19
+ if (packet.protocolVersion !== "witnora.verifiable_evidence_packet.v0.2")
20
+ throw new Error("Unsupported evidence protocol version.");
21
+ if (packet.trust.rootId !== pinnedRoot.rootId)
22
+ throw new Error("Packet does not reference the pinned root.");
23
+ if (!latestRevocationJournal)
24
+ throw new Error("Latest trusted revocation journal is required; packet-embedded trust state cannot establish current verification.");
25
+ const journal = latestRevocationJournal;
26
+ verifyRevocationJournal(journal, pinnedRoot);
27
+ verifyRevocationJournal(packet.trust.revocationJournal, pinnedRoot);
28
+ assertRevocationJournalExtends(packet.trust.revocationJournal, journal);
29
+ const requestedAt = new Date(packet.probeRequest.requestedAt);
30
+ verifyKeyCertificate(packet.trust.requestSignerCertificate, pinnedRoot, requestedAt, { role: "server_attestor", tenantRef: packet.tenantRef, projectRef: packet.projectRef, environment: packet.environment });
31
+ assertNotRevoked(packet.trust.requestSignerCertificate, journal, requestedAt);
32
+ if (packet.probeRequest.requestSignerKeyId !== packet.trust.requestSignerCertificate.subjectKeyId)
33
+ throw new Error("Probe request signer certificate ID mismatch.");
34
+ const observedAt = new Date(packet.probeAttestation.observedAt);
35
+ const eventVerificationTime = verificationTime;
36
+ verifyProbeRequest(packet.probeRequest, packet.trust.requestSignerCertificate.publicKeyPem, {
37
+ tenantRef: packet.tenantRef, projectRef: packet.projectRef, environment: packet.environment,
38
+ actionDigest: packet.actionDigest, contractDigest: packet.contractDigest, resourceCommitment: packet.resourceCommitment,
39
+ predicateDigest: packet.predicateDigest, probeId: packet.probeAttestation.probeId, now: observedAt,
40
+ });
41
+ const issuedAt = new Date(packet.issuedAt);
42
+ verifyKeyCertificate(packet.trust.gatewayCertificate, pinnedRoot, issuedAt, { role: "gateway_runtime", tenantRef: packet.tenantRef, projectRef: packet.projectRef, environment: packet.environment });
43
+ assertNotRevoked(packet.trust.gatewayCertificate, journal, issuedAt);
44
+ if (packet.gatewaySignature.keyId !== packet.trust.gatewayCertificate.subjectKeyId || !verify(null, canonicalBytesV02(gatewaySignedValue(packet)), createPublicKey(packet.trust.gatewayCertificate.publicKeyPem), Buffer.from(packet.gatewaySignature.signature, "base64")))
45
+ throw new Error("Gateway packet signature is invalid.");
46
+ verifyAuthorizationV02({ approval: packet.approval, grant: packet.grant, trust: { root: pinnedRoot, approvalCertificate: packet.trust.approvalCertificate, grantCertificate: packet.trust.grantCertificate, revocationJournal: journal }, expected: { tenantRef: packet.tenantRef, projectRef: packet.projectRef, environment: packet.environment, actionDigest: packet.actionDigest }, at: issuedAt });
47
+ verifyProbeAttestation(packet.probeAttestation, packet.probeRequest, { root: pinnedRoot, certificate: packet.trust.probeCertificate, revocationJournal: journal }, {
48
+ tenantRef: packet.tenantRef, projectRef: packet.projectRef, environment: packet.environment,
49
+ actionDigest: packet.actionDigest, contractDigest: packet.contractDigest, resourceCommitment: packet.resourceCommitment,
50
+ predicateDigest: packet.predicateDigest, probeId: packet.probeAttestation.probeId, now: eventVerificationTime, maxFreshnessMs: 300_000,
51
+ });
52
+ if (packet.probeAttestation.probeId !== packet.trust.probeCertificate.operatorIdentity)
53
+ throw new Error("Probe identity is not bound to the certified operator identity.");
54
+ }
55
+ catch (error) {
56
+ crypto = false;
57
+ failures.push(error instanceof Error ? error.message : String(error));
58
+ }
59
+ const terminalState = packet.executionAttempt.state === "RECONCILED_SUCCESS";
60
+ const recoveryChainValid = sha256CanonicalV02(packet.executionAttempt.recoveryHistory) === packet.eventChainDigest && packet.executionAttempt.recoveryHistory.at(-1)?.state === packet.executionAttempt.state;
61
+ const dispatchReconciled = packet.executionAttempt.dispatchCount === 1 && terminalState && recoveryChainValid && packet.reconciliation.status === "TARGET_STATE_RECONCILED";
62
+ const grantUseBound = packet.reconciliation.authorizationBindingSource === "TARGET_GRANT_ECHO"
63
+ ? packet.probeAttestation.normalizedObservation.consumedGrantId === packet.grant.grantId
64
+ : packet.reconciliation.authorizationBindingSource === "GATEWAY_DISPATCH_RECORD" && dispatchReconciled;
65
+ const authorizationBound = crypto && packet.approval.actionDigest === packet.actionDigest && packet.grant.actionDigest === packet.actionDigest && packet.grant.maxUses === 1 && grantUseBound;
66
+ const outcomeObserved = packet.probeAttestation.outcomeStatus === "VERIFIED" && dispatchReconciled;
67
+ const customerOperated = ["separate_process", "separate_container", "customer_network"].includes(packet.separation.probeDeploymentMode)
68
+ && pinnedRoot.ownerType === "CUSTOMER_ASSURANCE_ROOT"
69
+ && packet.evidenceStrength.outcomeObservation === "CUSTOMER_OPERATED_READ_PATH";
70
+ const expectedSeparation = packet.separation.probeDeploymentMode === "customer_network"
71
+ ? "CUSTOMER_NETWORK_SEPARATED"
72
+ : packet.separation.probeDeploymentMode === "separate_container" ? "CONTAINER_SEPARATED" : "PROCESS_SEPARATED";
73
+ const expectedAuthorizationStrength = authorizationBound
74
+ ? packet.reconciliation.authorizationBindingSource === "TARGET_GRANT_ECHO" ? "EXACT_ACTION_GRANT_BOUND" : "GATEWAY_DISPATCH_BOUND"
75
+ : "NOT_ESTABLISHED";
76
+ const strengthConsistent = packet.evidenceStrength.authorizationBinding === expectedAuthorizationStrength
77
+ && packet.evidenceStrength.executionBinding === (dispatchReconciled ? "TARGET_STATE_RECONCILED" : packet.executionAttempt.state === "DISPATCH_UNKNOWN" ? "DISPATCH_UNKNOWN" : "NOT_ESTABLISHED")
78
+ && packet.evidenceStrength.sourceIntegrity === (crypto ? "CUSTOMER_ROOT_CERTIFIED" : "SELF_ASSERTED")
79
+ && packet.evidenceStrength.outcomeObservation === (outcomeObserved ? "CUSTOMER_OPERATED_READ_PATH" : "NOT_OBSERVED")
80
+ && packet.evidenceStrength.operatorSeparation === expectedSeparation
81
+ && packet.evidenceStrength.review !== "EXTERNAL"
82
+ && packet.privacy.rawCanaryMatchesOutsideLocal === 0;
83
+ if (!authorizationBound)
84
+ failures.push("Authorization is not bound to the exact action.");
85
+ if (!dispatchReconciled)
86
+ failures.push("Target dispatch is not reconciled.");
87
+ if (!outcomeObserved)
88
+ failures.push("Outcome was not independently observed.");
89
+ if (!recoveryChainValid)
90
+ failures.push("Execution recovery history is incomplete or does not match its digest.");
91
+ if (!strengthConsistent)
92
+ failures.push("Evidence-strength vector overclaims the packet evidence.");
93
+ if (sha256CanonicalV02(packet.probeRequest) !== packet.probeAttestation.requestDigest)
94
+ failures.push("Probe request digest mismatch.");
95
+ return {
96
+ protocolVersion: "witnora.offline_verification_report.v0.2",
97
+ overall: crypto && authorizationBound && dispatchReconciled && outcomeObserved && customerOperated && strengthConsistent && recoveryChainValid ? "PASS" : "FAIL",
98
+ networkUsed: false,
99
+ dimensions: {
100
+ cryptographicallyValid: crypto,
101
+ trustedByPinnedRoot: crypto,
102
+ authorizationBound,
103
+ dispatchReconciled,
104
+ outcomeObserved,
105
+ customerOperated,
106
+ // v0.2 has no independent reviewer signature object, so this claim cannot be established from the packet.
107
+ thirdPartyReviewed: false,
108
+ },
109
+ failures,
110
+ };
111
+ }
112
+ function failedReport(message) {
113
+ return {
114
+ protocolVersion: "witnora.offline_verification_report.v0.2", overall: "FAIL", networkUsed: false,
115
+ dimensions: { cryptographicallyValid: false, trustedByPinnedRoot: false, authorizationBound: false, dispatchReconciled: false, outcomeObserved: false, customerOperated: false, thirdPartyReviewed: false },
116
+ failures: [message],
117
+ };
118
+ }