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,402 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { access, cp, mkdir, readFile, stat, writeFile } from "node:fs/promises";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { promisify } from "node:util";
7
+ import { compileAssuranceContract, diffAssuranceContracts, validateAssuranceContract } from "./assurance-contract.js";
8
+ import { summarizeGenericEval } from "./generic-eval.js";
9
+ import { inspectPrivacy, validatePrivacyManifest } from "./privacy-manifest.js";
10
+ import { assertNotRevoked, verifyKeyCertificate, verifyRevocationJournal } from "./trust-v02.js";
11
+ const execFileAsync = promisify(execFile);
12
+ export function pilotPackageFiles(config) {
13
+ const command = "node docker/runtime/dist/cli.js design-partner";
14
+ const runner = (arguments_) => `import { spawnSync } from "node:child_process";\nimport { dirname, resolve } from "node:path";\nimport { fileURLToPath } from "node:url";\nconst kit = resolve(dirname(fileURLToPath(import.meta.url)), "..");\nconst cli = resolve(kit, "docker/runtime/dist/cli.js");\nconst result = spawnSync(process.execPath, [cli, "design-partner", ${arguments_.split(" ").map((value) => JSON.stringify(value)).join(", ")}, "--dir", kit], { stdio: "inherit" });\nprocess.exit(result.status ?? 1);\n`;
15
+ return {
16
+ "README.md": `# Witnora Design Partner Pilot\n\nThis package runs one consequential **sandbox** action through a customer-operated Gateway and a separately deployed outcome Probe. It produces a signed Evidence Packet that can be verified without Witnora Cloud.\n\nStart with [QUICKSTART.md](./QUICKSTART.md). After the normal path passes, run \`node scripts/failure-exercises.mjs\` to execute the seven safe adversarial exercises in an isolated temporary sandbox. The package does not enable a production GitHub write path.\n`,
17
+ "QUICKSTART.md": `# Quickstart\n\nThis first run is a synthetic local emulator. It needs no real GitHub token and performs no production write. Run every command from this directory.\n\n## 1. Configure local-only synthetic secrets\n\nCopy \`.env.example\` to \`.env\` and load it into your shell. The generated values are intentionally local dummy credentials; use a secret manager and real least-privilege credentials only for a separately reviewed live sandbox. Never commit \`.env\`.\n\nInstall the bundled, pinned Python verifier without using Witnora source code:\n\n\`\`\`bash\npython -m pip install ./verifiers/python\n\`\`\`\n\n## 2. Issue short-lived authorization\n\n\`\`\`bash\n${command} authorization issue --dir .\n\`\`\`\n\n## 3. Start the customer-owned topology\n\nThe explicit \`--env-file\` is required because the Compose file lives under \`docker/\`.\n\n\`\`\`bash\ndocker compose --env-file .env -f docker/compose.yml up -d --build target-sandbox probe\n${command} doctor --dir .\n${command} privacy inspect --dir .\n${command} dry-run --dir . --target-url http://127.0.0.1:4311\n\`\`\`\n\n## 4. Run and verify one sandbox action\n\n\`\`\`bash\ndocker compose --env-file .env -f docker/compose.yml --profile acceptance run --rm gateway-acceptance\ndocker compose --env-file .env -f docker/compose.yml --profile acceptance run --rm offline-verifier\n${command} verify-offline --dir . --packet acceptance/witnora-evidence-packet-v0.2.json --root trust/pinned-root.json --revocation-journal trust/revocation-journal.json\npython -m witnora_verifier.cli evidence acceptance/witnora-evidence-packet-v0.2.json --root trust/pinned-root.json --revocation-journal trust/revocation-journal.json\n${command} collect-support-bundle --dir .\n${command} pilot-report --dir .\n\`\`\`\n\nExpected result: one target transition, one independently observed outcome, TypeScript and Python verifier PASS, and no raw customer secret in the support bundle.\n`,
18
+ "ARCHITECTURE.md": `# Architecture\n\n- **Target sandbox** owns simulated read and write APIs.\n- **Gateway** is the only process with the write credential. It validates the exact signed Approval and one-use Grant before dispatch.\n- **Probe** has only a read credential and a separate signing key. It observes target state independently of the Gateway.\n- **Offline verifiers** pin the customer root and verify signatures, roles, scope, revocation, dispatch history, and outcome binding.\n\nContainer separation is tested. Customer-network and organizational separation are deployment choices and are not implied by this package.\n`,
19
+ "PRIVACY.md": `# Privacy\n\nRun \`${command} privacy inspect --dir .\` for the exact field-by-field disclosure plan. Raw prompts, credentials, action arguments, target results, and source code remain customer-local. The reference manifest sends only declared outcome/policy metadata and tenant-keyed commitments. Undeclared fields fail closed.\n`,
20
+ "SECURITY.md": `# Security boundaries\n\nThe pilot covers one configured sandbox action path. Direct writes with unmanaged credentials remain outside Witnora. Root private material is generated for a sandbox ceremony and should move to customer KMS/HSM custody before production. A bounded model check and internal role-separated review are not an external audit or proof of universal complete mediation.\n`,
21
+ "TROUBLESHOOTING.md": `# Troubleshooting\n\n1. Run \`${command} doctor --dir .\`.\n2. Follow the single **Next action** at the end.\n3. Run \`${command} collect-support-bundle --dir .\`.\n4. Share only the generated support bundle. It excludes private keys, credentials, prompts, raw arguments, raw results, cookies, and authorization headers.\n\nStable setup failures use FAIL or NOT_CONFIGURED instead of silently continuing.\n`,
22
+ "contract/assurance-contract.json": "__COPY_ROOT_CONTRACT__",
23
+ "privacy/privacy-manifest.json": "__COPY_ROOT_PRIVACY__",
24
+ "privacy/README.md": "# Privacy manifest\n\nThis directory contains the canonical customer disclosure declaration. The root-level copy remains only for v0.2 CLI compatibility and must have the same SHA-256 digest.\n",
25
+ "trust/README.md": "# Trust material\n\nVerify `pinned-root.json` out of band. Public certificates are shareable. Files under `secrets/` are private and must never enter source control or a support bundle.\n",
26
+ "trust/root-fingerprint.example": "Compare the exact rootFingerprint from trust/pinned-root.json through a separate trusted channel.\n",
27
+ "gateway/config.example.json": JSON.stringify({ mode: "single-use-sandbox-action", writeCredentialRef: `env:${config.writeCredentialEnv}`, targetUrl: config.targetUrl, failClosed: true }, null, 2),
28
+ "probe/config.example.json": JSON.stringify({ mode: "independent-read-observation", readCredentialRef: `env:${config.readCredentialEnv}`, probeUrl: config.probeUrl, rawStore: "customer_local_encrypted" }, null, 2),
29
+ "scripts/doctor.mjs": runner("doctor"),
30
+ "scripts/dry-run.mjs": runner("dry-run"),
31
+ "scripts/privacy-inspect.mjs": runner("privacy inspect"),
32
+ "scripts/collect-support-bundle.mjs": runner("collect-support-bundle"),
33
+ "scripts/run-acceptance.mjs": runner("run-acceptance"),
34
+ "scripts/recover.mjs": runner("recover-acceptance"),
35
+ "scripts/pilot-lifecycle.mjs": runner("pilot-lifecycle"),
36
+ "LIFECYCLE.md": `# Assurance lifecycle demonstration\n\nAfter the sandbox acceptance and both offline verifiers pass, run:\n\n\`\`\`bash\nnode scripts/pilot-lifecycle.mjs\n\`\`\`\n\nThe command imports the bundled Generic Eval, establishes a synthetic local release baseline, changes the Contract policy reference, and proves that the new scope becomes \`REVALIDATION_REQUIRED\`. It demonstrates lifecycle state semantics only; it is not an independent release decision.\n`,
37
+ "examples/generic-eval.json": JSON.stringify(genericEvalExample("0".repeat(64)), null, 2),
38
+ "expected-output/privacy-inspect.txt": "PROMPT\nLocation: customer only\nSent to Witnora Cloud: no\n\nOUTCOME STATUS\nSent to Witnora Cloud: yes\nExample: VERIFIED\n",
39
+ "expected-output/verification-report.txt": "overall: PASS\ntrust root: caller pinned\noutcome: VERIFIED\nreview strength: INTERNAL or NONE unless an external reviewer signs\n",
40
+ "FAILURE-EXERCISES.md": `# Safe failure exercises\n\nRun \`node scripts/failure-exercises.mjs\`. It creates a temporary local-only sandbox and intentionally demonstrates:\n\n1. parameter substitution blocked before dispatch;\n2. consumed Grant replay blocked without a second write;\n3. apparent success classified NOT_VERIFIED when target state is unchanged;\n4. crash after target success reconciled without redispatch;\n5. Probe observation replay rejected;\n6. revoked Probe certificate blocks future covered dispatch until rotation;\n7. packet tampering rejected by both bundled TypeScript and Python verifiers.\n\nThe command uses synthetic credentials and deletes its temporary target state unless \`WITNORA_E2E_KEEP_TEMP=1\` is set. Its machine-readable report is written to \`failure-exercises/design-partner-v02-e2e-report.json\`.\n`,
41
+ };
42
+ }
43
+ export async function snapshotPilotRuntime(outDir) {
44
+ const moduleDirectory = dirname(fileURLToPath(import.meta.url));
45
+ const cliDist = basename(moduleDirectory) === "src" ? resolve(moduleDirectory, "../dist") : moduleDirectory;
46
+ const probeDistCandidates = [join(cliDist, "vendor/witnora-probe"), resolve(cliDist, "../../witnora-probe/dist")];
47
+ const probeDist = await firstExistingDirectory(probeDistCandidates);
48
+ if (!probeDist)
49
+ throw new Error("The built Witnora package does not contain the Probe runtime. Run the Witnora build before generating a pilot kit.");
50
+ const pythonVerifierCandidates = [join(cliDist, "vendor/witnora-verifier-python"), resolve(cliDist, "../../witnora-verifier-python")];
51
+ const pythonVerifier = await firstExistingDirectory(pythonVerifierCandidates);
52
+ if (!pythonVerifier)
53
+ throw new Error("The built Witnora package does not contain the Python verifier. Run the Witnora build before generating a pilot kit.");
54
+ const runtime = join(outDir, "docker/runtime");
55
+ await mkdir(join(runtime, "dist/vendor"), { recursive: true });
56
+ await cp(cliDist, join(runtime, "dist"), { recursive: true, filter: (source) => !source.includes(`${join("vendor", "witnora-probe")}`) && !source.includes(`${join("vendor", "witnora-verifier-python")}`) });
57
+ await cp(probeDist, join(runtime, "dist/vendor/witnora-probe"), { recursive: true });
58
+ await cp(pythonVerifier, join(outDir, "verifiers/python"), { recursive: true });
59
+ await cp(join(cliDist, "vendor/design-partner/failure-exercises.mjs"), join(outDir, "scripts/failure-exercises.mjs"));
60
+ await writeFile(join(runtime, "package.json"), `${JSON.stringify({ name: "witnora-design-partner-runtime", private: true, type: "module", engines: { node: ">=20" } }, null, 2)}\n`);
61
+ await writeFile(join(runtime, "Dockerfile"), "FROM node:22-alpine\nWORKDIR /app\nCOPY package.json ./package.json\nCOPY dist ./dist\nUSER 10001:10001\nENTRYPOINT [\"node\", \"/app/dist/cli.js\"]\n");
62
+ }
63
+ export async function runPilotDoctor(dir, config) {
64
+ const checks = [];
65
+ const add = (area, status, message, nextAction) => checks.push({ area, status, message, ...(nextAction ? { nextAction } : {}) });
66
+ const major = Number(process.versions.node.split(".")[0]);
67
+ add("runtime", major >= 20 ? "PASS" : "FAIL", `Node ${process.versions.node}`, major >= 20 ? undefined : "Install Node 20 or newer.");
68
+ const required = ["witnora.assurance-contract.json", "witnora.privacy-manifest.json", "docker/compose.yml", "docker/runtime/Dockerfile", "trust/pinned-root.json", "trust/request-signer-certificate.json", "trust/gateway-certificate.json", "trust/probe-certificate.json", "trust/approval-certificate.json", "trust/grant-certificate.json", "trust/revocation-journal.json", "secrets/gateway-private.pem", "secrets/probe-private.pem", "authorization/approval.json", "authorization/grant.json"];
69
+ for (const relative of required)
70
+ add("package", await exists(join(dir, relative)) ? "PASS" : "NOT_CONFIGURED", `${relative}: ${await exists(join(dir, relative)) ? "present" : "missing"}`, `Create the missing file or rerun design-partner init in an empty directory.`);
71
+ try {
72
+ validateAssuranceContract(JSON.parse(await readFile(join(dir, "witnora.assurance-contract.json"), "utf8")));
73
+ add("package", "PASS", "Assurance Contract is valid.");
74
+ }
75
+ catch (error) {
76
+ add("package", "FAIL", errorMessage(error), "Restore a valid generated Assurance Contract.");
77
+ }
78
+ let manifest;
79
+ try {
80
+ manifest = validatePrivacyManifest(JSON.parse(await readFile(join(dir, "witnora.privacy-manifest.json"), "utf8")));
81
+ add("privacy", "PASS", "Privacy Manifest is strict and deny-by-default.");
82
+ }
83
+ catch (error) {
84
+ add("privacy", "FAIL", errorMessage(error), "Restore a valid generated Privacy Manifest; unknown fields are rejected.");
85
+ }
86
+ if (manifest) {
87
+ add("privacy", manifest.fields.prompt === "local_only" ? "PASS" : "FAIL", "Prompt is declared customer-local.");
88
+ add("privacy", manifest.fields.credentials === "local_only" ? "PASS" : "FAIL", "Credentials are declared customer-local.");
89
+ add("privacy", manifest.fields.actionParameters === "commit_only" ? "PASS" : "FAIL", "Raw action parameters are not disclosed.");
90
+ add("privacy", manifest.fields.rawResult === "local_only" ? "PASS" : "FAIL", "Raw target results are customer-local.");
91
+ }
92
+ add("privacy", config.legacyUnprotectedPathEnabled === true ? "FAIL" : "PASS", "Legacy unprotected path is disabled.", "Disable every legacy ingestion path for this pilot.");
93
+ const composeText = await readFile(join(dir, "docker/compose.yml"), "utf8").catch(() => "");
94
+ add("secrets", composeText.includes("probe-key-data:/app/keys:ro") && !composeServiceBlock(composeText, "probe").includes("WITNORA_GITHUB_WRITE_CREDENTIAL") ? "PASS" : "FAIL", "Probe has a role-specific read path and signing-key mount.", "Restore the generated Probe boundary.");
95
+ add("secrets", composeText.includes("gateway-key-data:/app/keys:ro") && !composeServiceBlock(composeText, "gateway-acceptance").includes("WITNORA_GITHUB_READ_CREDENTIAL") ? "PASS" : "FAIL", "Gateway has a role-specific write path and signing-key mount.", "Restore the generated Gateway boundary.");
96
+ const readCredential = process.env[config.readCredentialEnv];
97
+ const writeCredential = process.env[config.writeCredentialEnv];
98
+ if (!readCredential)
99
+ add("secrets", "NOT_CONFIGURED", `${config.readCredentialEnv} is not resolved.`, "Set the Probe read credential in the customer secret store.");
100
+ else
101
+ add("secrets", "PASS", "Probe read credential reference resolves locally.");
102
+ if (!writeCredential)
103
+ add("secrets", "NOT_CONFIGURED", `${config.writeCredentialEnv} is not resolved.`, "Set the Gateway write credential in the customer secret store.");
104
+ else
105
+ add("secrets", "PASS", "Gateway write credential reference resolves locally.");
106
+ if (readCredential && writeCredential)
107
+ add("secrets", readCredential === writeCredential ? "FAIL" : "PASS", readCredential === writeCredential ? "Read and write credentials are identical." : "Read and write credential values are distinct.", "Issue separate least-privilege credentials.");
108
+ try {
109
+ const root = await readJson(join(dir, "trust/pinned-root.json"));
110
+ const journal = await readJson(join(dir, "trust/revocation-journal.json"));
111
+ verifyRevocationJournal(journal, root);
112
+ add("trust", "PASS", `Pinned root ${root.rootFingerprint} and revocation journal are valid.`);
113
+ const bindings = { tenantRef: config.tenantRef, projectRef: config.projectRef, environment: config.environment };
114
+ const certificates = [["gateway", "gateway_runtime"], ["request-signer", "server_attestor"], ["probe", "outcome_probe"], ["approval", "reviewer"], ["grant", "resource_scope_evaluator"]];
115
+ for (const [name, role] of certificates) {
116
+ const certificate = await readJson(join(dir, `trust/${name}-certificate.json`));
117
+ verifyKeyCertificate(certificate, root, new Date(), { role, ...bindings });
118
+ assertNotRevoked(certificate, journal, new Date());
119
+ add("trust", "PASS", `${name} certificate is valid, role-bound, and active.`);
120
+ }
121
+ }
122
+ catch (error) {
123
+ add("trust", "FAIL", errorMessage(error), "Repair trust material before issuing or accepting a Grant.");
124
+ }
125
+ const docker = await commandAvailable("docker", ["version", "--format", "{{.Server.Version}}"]).catch(() => false);
126
+ add("runtime", docker ? "PASS" : "WARN", docker ? "Docker engine is available." : "Docker engine is unavailable; process-mode tests remain possible but container separation cannot be exercised.", "Start Docker before the container pilot.");
127
+ const targetHealthy = await endpointHealthy(`${localEndpoint(config.targetUrl)}/v0.2/github/pull-request`, readCredential ? { authorization: `Bearer ${readCredential}` } : undefined);
128
+ const probeHealthy = await endpointHealthy(`${localEndpoint(config.probeUrl)}/ready`);
129
+ add("target", targetHealthy ? "PASS" : "NOT_CONFIGURED", targetHealthy ? "Sandbox target is reachable." : "Sandbox target is not reachable.", "Start target-sandbox with docker compose.");
130
+ add("runtime", probeHealthy ? "PASS" : "NOT_CONFIGURED", probeHealthy ? "Probe is reachable and ready." : "Probe is not reachable.", "Start the Probe after the sandbox target.");
131
+ add("runtime", "PASS", "Gateway is a one-shot container with no persistent listening endpoint.");
132
+ add("target", config.environment === "sandbox" && !["main", "master"].includes(config.declaredTarget.baseBranch) ? "PASS" : "FAIL", "Target is allowlisted as a non-protected sandbox branch.", "Use a sandbox/test repository and non-main branch.");
133
+ const now = Date.now();
134
+ add("clock", now > Date.UTC(2024, 0, 1) && now < Date.UTC(2100, 0, 1) ? "PASS" : "FAIL", `Local UTC clock: ${new Date(now).toISOString()}`, "Synchronize the local clock.");
135
+ const summary = { PASS: 0, WARN: 0, FAIL: 0, NOT_CONFIGURED: 0 };
136
+ for (const check of checks)
137
+ summary[check.status] += 1;
138
+ const ready = summary.FAIL === 0 && summary.NOT_CONFIGURED === 0;
139
+ const nextAction = checks.find((check) => check.status === "FAIL")?.nextAction ?? checks.find((check) => check.status === "NOT_CONFIGURED")?.nextAction ?? checks.find((check) => check.status === "WARN")?.nextAction;
140
+ const report = { schemaVersion: "witnora.design_partner_doctor.v0.1", generatedAt: new Date().toISOString(), ready, checks, summary, ...(nextAction ? { nextAction } : {}) };
141
+ await writeJson(join(dir, "pilot/doctor-report.json"), report);
142
+ await updatePilotState(dir, (state) => ({
143
+ ...state,
144
+ ...(ready && !state.doctorReadyAt ? { doctorReadyAt: report.generatedAt } : {}),
145
+ ...(probeHealthy && !state.firstProbeHealthyAt ? { firstProbeHealthyAt: report.generatedAt } : {}),
146
+ failedSetupStepCount: state.failedSetupStepCount + summary.FAIL,
147
+ doctorWarningCount: summary.WARN,
148
+ }));
149
+ return report;
150
+ }
151
+ export function formatDoctorReport(report) {
152
+ const lines = report.checks.map((check) => `${check.status.padEnd(14)} ${check.area.toUpperCase().padEnd(8)} ${check.message}`);
153
+ lines.push("", report.ready ? "READY FOR DRY RUN" : "NOT READY");
154
+ if (report.nextAction)
155
+ lines.push(`Next action: ${report.nextAction}`);
156
+ return `${lines.join("\n")}\n`;
157
+ }
158
+ export function formatPrivacyInspection(manifestInput, config) {
159
+ const manifest = validatePrivacyManifest(manifestInput);
160
+ const sections = [
161
+ "WITNORA DESIGN PARTNER PRIVACY INSPECTION",
162
+ "",
163
+ "What Witnora Cloud can see",
164
+ ...privacyFields(manifest, "disclose").map((field) => `- ${field}: declared metadata sent to ${manifest.cloudEndpoint.url}`),
165
+ "",
166
+ "What Witnora Cloud cannot see",
167
+ ...privacyFields(manifest, "local_only").map((field) => `- ${field}: customer only; not sent to Witnora Cloud`),
168
+ "",
169
+ "Values transformed into commitments",
170
+ ...privacyFields(manifest, "commit_only").map((field) => `- ${field}: tenant-scoped HMAC-SHA256 using customer-owned key ${manifest.tenantKeyId}`),
171
+ "",
172
+ "Credential and key ownership",
173
+ `- ${config.readCredentialEnv}: Probe only; never sent to Gateway or Witnora Cloud`,
174
+ `- ${config.writeCredentialEnv}: Gateway only; never sent to Probe or Witnora Cloud`,
175
+ "- Probe signing private key: Probe-only secret volume",
176
+ "- Gateway signing private key: Gateway-only secret volume",
177
+ `- Commitment key: ${manifest.keyOwnership.commitmentKey}; Hosted decryption capability: ${manifest.keyOwnership.hostedDecryptionCapability}`,
178
+ "",
179
+ "Retention and legacy paths",
180
+ `- Disclosed metadata retention: ${manifest.retentionDays} days`,
181
+ `- Raw artifacts: local at ${manifest.artifacts.localStore}; upload allowed: ${manifest.artifacts.rawUploadAllowed ? "yes" : "no"}`,
182
+ `- Legacy unprotected path enabled: ${config.legacyUnprotectedPathEnabled === true ? "yes (unsafe)" : "no"}`,
183
+ "- Unknown fields: denied by default",
184
+ "",
185
+ inspectPrivacy(manifest).trim(),
186
+ ];
187
+ return `${sections.join("\n")}\n`;
188
+ }
189
+ export async function collectSupportBundle(dir, config, outDir = join(dir, "support-bundle")) {
190
+ await mkdir(outDir, { recursive: true });
191
+ const contract = await readJson(join(dir, "witnora.assurance-contract.json"));
192
+ const manifest = await readJson(join(dir, "witnora.privacy-manifest.json"));
193
+ const root = await readJson(join(dir, "trust/pinned-root.json"));
194
+ const journal = await readJson(join(dir, "trust/revocation-journal.json"));
195
+ const certificates = await Promise.all(["gateway", "request-signer", "probe", "approval", "grant"].map(async (name) => {
196
+ const certificate = await readJson(join(dir, `trust/${name}-certificate.json`));
197
+ return { certificateId: certificate.certificateId, subjectKeyId: certificate.subjectKeyId, role: certificate.role, notBefore: certificate.notBefore, notAfter: certificate.notAfter, certificateDigest: certificate.certificateDigest };
198
+ }));
199
+ const bundle = {
200
+ schemaVersion: "witnora.design_partner_support_bundle.v0.1",
201
+ generatedAt: new Date().toISOString(),
202
+ versions: { node: process.version, platform: process.platform, architecture: process.arch, protocol: config.protocolVersion },
203
+ configuration: { environment: config.environment, targetOrigin: safeOrigin(config.targetUrl), probeOrigin: safeOrigin(config.probeUrl), readCredentialReference: `env:${config.readCredentialEnv}`, writeCredentialReference: `env:${config.writeCredentialEnv}`, legacyUnprotectedPathEnabled: config.legacyUnprotectedPathEnabled === true },
204
+ digests: { contract: sha256Json(contract), privacyManifest: sha256Json(manifest), revocationJournal: journal.checkpointDigest },
205
+ trust: { rootId: root.rootId, rootFingerprint: root.rootFingerprint, certificates },
206
+ health: {
207
+ target: await endpointHealthy(`${localEndpoint(config.targetUrl)}/v0.2/github/pull-request`, process.env[config.readCredentialEnv]
208
+ ? { authorization: `Bearer ${process.env[config.readCredentialEnv]}` }
209
+ : undefined),
210
+ probe: await endpointHealthy(`${localEndpoint(config.probeUrl)}/ready`),
211
+ },
212
+ stableErrors: await stableErrors(dir),
213
+ exclusions: ["prompts", "source code", "credentials", "private keys", "raw tool arguments", "raw tool results", "raw target data", "authorization headers", "cookies"],
214
+ };
215
+ const serialized = `${JSON.stringify(bundle, null, 2)}\n`;
216
+ const canaries = sensitiveEnvironmentValues();
217
+ const matches = scanSensitive(serialized, canaries);
218
+ if (matches.length)
219
+ throw new Error(`Support bundle privacy scan failed: ${matches.join(", ")}`);
220
+ const bundlePath = join(outDir, "support-bundle.json");
221
+ const scanPath = join(outDir, "privacy-scan.json");
222
+ await writeFile(bundlePath, serialized);
223
+ await writeJson(scanPath, { schemaVersion: "witnora.support_bundle_privacy_scan.v0.1", generatedAt: new Date().toISOString(), rawSecretCanaryMatches: 0, checkedEnvironmentSecretCount: canaries.length, status: "PASS" });
224
+ return { bundlePath, scanPath };
225
+ }
226
+ export async function initializePilotState(dir) {
227
+ const now = new Date().toISOString();
228
+ const state = { schemaVersion: "witnora.pilot_lifecycle_state.v0.1", pilotType: "synthetic_internal_pilot", setupStartedAt: now, firstValidContractAt: now, manualInterventionCount: 0, failedSetupStepCount: 0, doctorWarningCount: 0, privacyWarningCount: 0, trustErrorCount: 0, recoveryRequiredCount: 0 };
229
+ await writeJson(join(dir, "pilot/state.json"), state);
230
+ }
231
+ export async function markPilotLifecycle(dir, fields) {
232
+ await updatePilotState(dir, (state) => ({ ...state, ...fields, schemaVersion: "witnora.pilot_lifecycle_state.v0.1", pilotType: "synthetic_internal_pilot" }));
233
+ }
234
+ export async function writePilotAcceptanceReport(dir) {
235
+ const state = await readJson(join(dir, "pilot/state.json"));
236
+ const report = {
237
+ ...state,
238
+ schemaVersion: "witnora.pilot_acceptance_report.v0.1",
239
+ generatedAt: new Date().toISOString(),
240
+ ...durationFields(state),
241
+ };
242
+ await writeJson(join(dir, "pilot/witnora-pilot-acceptance-report-v0.1.json"), report);
243
+ return report;
244
+ }
245
+ export async function runPilotLifecycleDemonstration(dir) {
246
+ const evalReport = summarizeGenericEval(await readJson(join(dir, "examples/generic-eval.json")));
247
+ const contract = validateAssuranceContract(await readJson(join(dir, "witnora.assurance-contract.json")));
248
+ const compiled = compileAssuranceContract(contract);
249
+ const changed = structuredClone(contract);
250
+ changed.metadata.version += 1;
251
+ changed.authority.policyRef = `${contract.authority.policyRef}:scope-change-demo`;
252
+ const contractDiff = diffAssuranceContracts(contract, changed);
253
+ if (!contractDiff.revalidationRequired)
254
+ throw new Error("Synthetic scope change did not require revalidation.");
255
+ const result = {
256
+ schemaVersion: "witnora.design_partner_lifecycle_demo.v0.1",
257
+ pilotType: "synthetic_internal_pilot",
258
+ generatedAt: new Date().toISOString(),
259
+ stages: [
260
+ { stage: "GENERIC_EVAL_IMPORTED", status: "PASS", authority: evalReport.authority, establishesCurrentAssurance: false },
261
+ { stage: "SANDBOX_RELEASE_ASSURANCE_BASELINE", status: "ESTABLISHED_FOR_DEMONSTRATION", contractDigest: compiled.sourceContractDigest, scopeFingerprint: compiled.revalidationFingerprint },
262
+ { stage: "CONTRACT_SCOPE_CHANGED", status: "PASS", changedPaths: contractDiff.changedPaths },
263
+ { stage: "CURRENT_ASSURANCE", status: "REVALIDATION_REQUIRED", reasons: contractDiff.reasons },
264
+ ],
265
+ limitation: "This local lifecycle demonstration validates state semantics. It is not an independent release decision or customer assurance result.",
266
+ };
267
+ await Promise.all([
268
+ writeJson(join(dir, "pilot/generic-eval-report.json"), evalReport),
269
+ writeJson(join(dir, "pilot/release-assurance-baseline.json"), { schemaVersion: "witnora.synthetic_release_assurance_baseline.v0.1", contractDigest: compiled.sourceContractDigest, scopeFingerprint: compiled.revalidationFingerprint, authority: "SYNTHETIC_INTERNAL_DEMONSTRATION" }),
270
+ writeJson(join(dir, "pilot/changed-assurance-contract.json"), changed),
271
+ writeJson(join(dir, "pilot/lifecycle-demonstration.json"), result),
272
+ ]);
273
+ return result;
274
+ }
275
+ export function referenceReleaseCandidateManifest() {
276
+ return {
277
+ schemaVersion: "witnora.reference_release_candidate.v0.1",
278
+ proposedTag: "design-partner-assurance-reference-v0.2",
279
+ mergeCommits: { privacyVerifiableV01: "PENDING_PR_115_MERGE", designPartnerV02: "PENDING_PR_116_MERGE" },
280
+ protocolVersions: ["witnora.assurance_contract.v0.1", "witnora.privacy_manifest.v0.1", "witnora.verifiable_evidence_packet.v0.1", "witnora.verifiable_evidence_packet.v0.2"],
281
+ canonicalizationVersions: ["witnora.canonical_json.v0.1", "witnora.canonical_json.v0.2"],
282
+ components: { typescriptVerifier: "0.10.0-rc", pythonVerifier: "0.2.0-rc", probe: "0.2.0-rc" },
283
+ digests: { schemas: "FINALIZE_AFTER_MERGE", tlcModel: "FINALIZE_AFTER_MERGE", tlcConfig: "FINALIZE_AFTER_MERGE", privacyCanaryTest: "FINALIZE_AFTER_MERGE", containerAcceptanceTest: "FINALIZE_AFTER_MERGE" },
284
+ knownLimitations: ["sandbox reference target only", "no production GitHub write path", "no external audit", "no organizational independence claim", "no HSM/KMS ceremony", "coverage is limited to declared mediated paths"],
285
+ };
286
+ }
287
+ async function updatePilotState(dir, update) {
288
+ const path = join(dir, "pilot/state.json");
289
+ const state = await readJson(path);
290
+ await writeJson(path, update(state));
291
+ }
292
+ function durationFields(state) {
293
+ const elapsed = (end) => end ? new Date(end).getTime() - new Date(state.setupStartedAt).getTime() : undefined;
294
+ return { timeToDoctorReady: elapsed(state.doctorReadyAt), timeToFirstEvidence: elapsed(state.firstSignedObservationAt), timeToVerifiedOutcome: elapsed(state.firstVerifiedOutcomeAt), timeToOfflineVerification: elapsed(state.firstOfflineVerificationAt) };
295
+ }
296
+ export function genericEvalExample(scopeFingerprint) {
297
+ return {
298
+ schemaVersion: "witnora.generic_eval.v0.1",
299
+ suite: "design-partner-sandbox-v0.2",
300
+ agent: { name: "design-partner-github-agent", version: "0.2.0", framework: "customer-workflow" },
301
+ environment: "sandbox",
302
+ scopeFingerprint,
303
+ cases: [{
304
+ id: "sandbox-pr-preconditions",
305
+ capability: "github.pull_request.merge",
306
+ task: "Validate the declared sandbox pull-request preconditions.",
307
+ prohibitedActions: ["merge protected branch", "change undeclared repository"],
308
+ reportedSuccess: true,
309
+ observedOutcome: "SATISFIED",
310
+ outcomePredicate: "open=true and merged=false and required checks=success",
311
+ evaluator: "customer-operated read-only Probe",
312
+ limitations: ["Single synthetic fixture; not a statistical reliability claim."],
313
+ resultStatus: "PASSED",
314
+ latencyMs: 0,
315
+ tokenUsage: 0,
316
+ costUsd: 0,
317
+ }],
318
+ };
319
+ }
320
+ async function firstExistingDirectory(paths) {
321
+ for (const path of paths) {
322
+ try {
323
+ if ((await stat(path)).isDirectory())
324
+ return path;
325
+ }
326
+ catch { /* continue */ }
327
+ }
328
+ return undefined;
329
+ }
330
+ async function exists(path) { try {
331
+ await access(path);
332
+ return true;
333
+ }
334
+ catch {
335
+ return false;
336
+ } }
337
+ async function readJson(path) { return JSON.parse(await readFile(path, "utf8")); }
338
+ async function writeJson(path, value) { await mkdir(dirname(path), { recursive: true }); await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); }
339
+ function errorMessage(error) { return error instanceof Error ? error.message.split("\n")[0] : String(error); }
340
+ function privacyFields(manifest, disposition) { return Object.entries(manifest.fields).filter(([, value]) => value === disposition).map(([field]) => field).sort(); }
341
+ function sha256Json(value) { return createHash("sha256").update(JSON.stringify(value)).digest("hex"); }
342
+ function safeOrigin(value) { try {
343
+ const url = new URL(value);
344
+ return `${url.protocol}//${url.host}`;
345
+ }
346
+ catch {
347
+ return "invalid";
348
+ } }
349
+ function localEndpoint(value) { try {
350
+ const url = new URL(value);
351
+ if (["target-sandbox", "probe"].includes(url.hostname))
352
+ url.hostname = "127.0.0.1";
353
+ return url.origin;
354
+ }
355
+ catch {
356
+ return value;
357
+ } }
358
+ function composeServiceBlock(compose, service) {
359
+ const marker = ` ${service}:`;
360
+ const start = compose.indexOf(marker);
361
+ if (start < 0)
362
+ return "";
363
+ const remainder = compose.slice(start + marker.length);
364
+ const next = remainder.search(/^ [a-z0-9][a-z0-9-]*:\s*$/mu);
365
+ const top = remainder.search(/^[a-z][a-z0-9-]*:\s*$/mu);
366
+ const stops = [next, top].filter((index) => index >= 0);
367
+ return stops.length ? remainder.slice(0, Math.min(...stops)) : remainder;
368
+ }
369
+ async function endpointHealthy(url, headers) { try {
370
+ const response = await fetch(url, { headers, signal: AbortSignal.timeout(750) });
371
+ return response.ok;
372
+ }
373
+ catch {
374
+ return false;
375
+ } }
376
+ async function commandAvailable(command, args) { try {
377
+ await execFileAsync(command, args, { timeout: 2_000, windowsHide: true });
378
+ return true;
379
+ }
380
+ catch {
381
+ return false;
382
+ } }
383
+ function sensitiveEnvironmentValues() { return Object.entries(process.env).filter(([name, value]) => value && /(secret|credential|token|private.*key|cookie|authorization|canary)/iu.test(name) && value.length >= 8).map(([, value]) => value); }
384
+ function scanSensitive(value, canaries) {
385
+ const matches = [];
386
+ if (/-----BEGIN [A-Z ]*PRIVATE KEY-----/u.test(value))
387
+ matches.push("private_key");
388
+ if (/\bBearer\s+[A-Za-z0-9._~+\/-]{8,}/u.test(value))
389
+ matches.push("authorization_header");
390
+ if (/\b(?:sk|rk)_(?:live|test|proj)_[A-Za-z0-9_-]{8,}/u.test(value))
391
+ matches.push("provider_secret");
392
+ canaries.forEach((canary, index) => { if (value.includes(canary))
393
+ matches.push(`environment_secret_${index}`); });
394
+ return matches;
395
+ }
396
+ async function stableErrors(dir) {
397
+ const reportPath = join(dir, "pilot/doctor-report.json");
398
+ if (!(await exists(reportPath)))
399
+ return [];
400
+ const report = await readJson(reportPath);
401
+ return report.checks.filter((check) => check.status === "FAIL" || check.status === "NOT_CONFIGURED").map((check) => `${check.status}:${check.area}:${check.message}`);
402
+ }