witnora 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +35 -2
- package/dist/command-help.js +6 -2
- package/dist/gateway-service.js +104 -0
- package/dist/gateway.js +56 -3
- package/dist/internal/control-client/collector-gateway.d.ts +4 -0
- package/dist/internal/control-client/collector-gateway.d.ts.map +1 -1
- package/dist/internal/control-client/collector-gateway.js +8 -1
- package/dist/internal/control-client/remote-collector.d.ts +18 -0
- package/dist/internal/control-client/remote-collector.d.ts.map +1 -1
- package/dist/onboard.js +19 -3
- package/dist/real-path-activation.js +100 -0
- package/dist/runtime-bootstrap.js +5 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { validateEvidenceArtifacts } from "./artifact-validation.js";
|
|
6
7
|
import { renderAgentCertBadge } from "./badge.js";
|
|
7
8
|
import { buildEvidenceBundle } from "./bundle.js";
|
|
@@ -39,7 +40,8 @@ import { runOnboard } from "./onboard.js";
|
|
|
39
40
|
import { inspectRepository } from "./onboard.js";
|
|
40
41
|
import { renderReleaseEvaluation, runReleaseEvaluation } from "./release-evaluation.js";
|
|
41
42
|
import { runPrivateCapabilityDiscovery } from "./private-discovery.js";
|
|
42
|
-
import { configureManagedWorkflowHarness, doctorCustomerGateway, initializeCustomerGateway, isGatewayDoctorReady, readManagedCustomerGatewayLogs, renderGatewayDoctor, renderManagedGatewayStatus, restartManagedCustomerGateway, runCustomerGateway, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, } from "./gateway.js";
|
|
43
|
+
import { configureManagedWorkflowHarness, doctorCustomerGateway, initializeCustomerGateway, isGatewayDoctorReady, readManagedCustomerGatewayLogs, renderGatewayDoctor, renderManagedGatewayStatus, restartManagedCustomerGateway, runCustomerGateway, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, superviseManagedCustomerGateway, } from "./gateway.js";
|
|
44
|
+
import { installCurrentGatewayService, restartCurrentGatewayService, uninstallCurrentGatewayService } from "./gateway-service.js";
|
|
43
45
|
import { verifyEvidencePacketV02 } from "./evidence-v02.js";
|
|
44
46
|
process.on("uncaughtException", reportFatalError);
|
|
45
47
|
process.on("unhandledRejection", reportFatalError);
|
|
@@ -224,6 +226,37 @@ else if (command === "gateway") {
|
|
|
224
226
|
const result = await restartManagedCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir") });
|
|
225
227
|
process.stdout.write(readBoolFlag("--json") ? `${JSON.stringify(result, null, 2)}\n` : renderManagedGatewayStatus(result));
|
|
226
228
|
}
|
|
229
|
+
else if (action === "repair") {
|
|
230
|
+
const repository = readFlag("--repo") ?? process.cwd();
|
|
231
|
+
const dir = readFlag("--dir");
|
|
232
|
+
const service = await installCurrentGatewayService({ repository, gatewayDirectory: dir, cliEntry: fileURLToPath(import.meta.url) });
|
|
233
|
+
await restartCurrentGatewayService({ repository, gatewayDirectory: dir, cliEntry: fileURLToPath(import.meta.url) });
|
|
234
|
+
const result = await startManagedCustomerGateway({ repository, dir });
|
|
235
|
+
const doctor = await doctorCustomerGateway({ repository, dir });
|
|
236
|
+
if (!isGatewayDoctorReady(doctor))
|
|
237
|
+
throw new Error(`Connection repair did not restore a ready Gateway. ${doctor.nextAction}`);
|
|
238
|
+
process.stdout.write(`Connection repaired. ${service.plan.kind} ${service.plan.id} is installed and the Gateway is healthy.\n`);
|
|
239
|
+
process.stdout.write(renderManagedGatewayStatus(result));
|
|
240
|
+
}
|
|
241
|
+
else if (action === "service") {
|
|
242
|
+
const serviceAction = process.argv[4] ?? "install";
|
|
243
|
+
const repository = readFlag("--repo") ?? process.cwd();
|
|
244
|
+
const gatewayDirectory = readFlag("--dir");
|
|
245
|
+
const cliEntry = fileURLToPath(import.meta.url);
|
|
246
|
+
if (serviceAction === "install") {
|
|
247
|
+
const result = await installCurrentGatewayService({ repository, gatewayDirectory, cliEntry });
|
|
248
|
+
process.stdout.write(`Installed ${result.plan.kind} service ${result.plan.id}.\n`);
|
|
249
|
+
}
|
|
250
|
+
else if (serviceAction === "uninstall") {
|
|
251
|
+
const result = await uninstallCurrentGatewayService({ repository, gatewayDirectory, cliEntry });
|
|
252
|
+
process.stdout.write(`Removed Gateway service ${result.plan.id}.\n`);
|
|
253
|
+
}
|
|
254
|
+
else
|
|
255
|
+
throw new Error("Use witnora gateway service install|uninstall.");
|
|
256
|
+
}
|
|
257
|
+
else if (action === "supervise") {
|
|
258
|
+
await superviseManagedCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir"), output: (message) => process.stdout.write(message) });
|
|
259
|
+
}
|
|
227
260
|
else if (action === "stop") {
|
|
228
261
|
const result = await stopManagedCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir") });
|
|
229
262
|
process.stdout.write(readBoolFlag("--json") ? `${JSON.stringify(result, null, 2)}\n` : renderManagedGatewayStatus(result));
|
|
@@ -249,7 +282,7 @@ else if (command === "gateway") {
|
|
|
249
282
|
process.stdout.write(`Configured the Managed Workflow Harness for ${result.config.workflowIds?.length ?? "all active"} Workflow Contract(s).\nConfig: ${result.path}\nRestart the managed Gateway to activate it.\n`);
|
|
250
283
|
}
|
|
251
284
|
else {
|
|
252
|
-
throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|stop|run|workflow-harness.");
|
|
285
|
+
throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|repair|service|stop|run|workflow-harness.");
|
|
253
286
|
}
|
|
254
287
|
}
|
|
255
288
|
else if (command === "discover") {
|
package/dist/command-help.js
CHANGED
|
@@ -63,6 +63,8 @@ Options:
|
|
|
63
63
|
witnora gateway status
|
|
64
64
|
witnora gateway logs [--lines 100]
|
|
65
65
|
witnora gateway restart
|
|
66
|
+
witnora gateway repair
|
|
67
|
+
witnora gateway service install|uninstall
|
|
66
68
|
witnora gateway stop
|
|
67
69
|
witnora gateway run
|
|
68
70
|
witnora gateway workflow-harness --workflow <id> --evaluator-origin http://127.0.0.1:<port>/ \\
|
|
@@ -71,8 +73,10 @@ Options:
|
|
|
71
73
|
Initializes and manages a customer-owned, metadata-only collector beside the Agent.
|
|
72
74
|
The browser approval issues a collector-scoped credential; no API key is copied into
|
|
73
75
|
the repository or exposed to the Agent. The local queue and source signing key remain
|
|
74
|
-
under customer control.
|
|
75
|
-
the
|
|
76
|
+
under customer control. The service command installs an OS-native auto-start and restart
|
|
77
|
+
boundary. Repair safely reinstalls that boundary, restarts the existing generated Gateway,
|
|
78
|
+
and reruns digest, credential, Probe, and health checks. The run command is the foreground
|
|
79
|
+
debugging path.
|
|
76
80
|
|
|
77
81
|
The reference Gateway establishes RECORDED evidence only. ENFORCED requires target
|
|
78
82
|
write credentials behind a controlled execution adapter. OUTCOME VERIFIED requires a
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir, userInfo } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
export async function installGatewayService(input) {
|
|
9
|
+
const plan = createGatewayServicePlan(input);
|
|
10
|
+
await input.writeDefinition(plan.definitionPath, plan.definition);
|
|
11
|
+
if (plan.kind === "SYSTEMD_USER")
|
|
12
|
+
await input.run("systemctl", ["--user", "daemon-reload"]);
|
|
13
|
+
await input.run(plan.install.command, plan.install.args);
|
|
14
|
+
if (plan.kind !== "SYSTEMD_USER")
|
|
15
|
+
await input.run(plan.start.command, plan.start.args);
|
|
16
|
+
return { installed: true, plan };
|
|
17
|
+
}
|
|
18
|
+
export async function installCurrentGatewayService(options) {
|
|
19
|
+
const planInput = currentPlanInput(options);
|
|
20
|
+
return installGatewayService({ ...planInput,
|
|
21
|
+
writeDefinition: async (path, value) => { await mkdir(dirname(path), { recursive: true }); await writeFile(path, value, { encoding: "utf8", mode: 0o600 }); },
|
|
22
|
+
run: options.run ?? runCommand,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export async function uninstallCurrentGatewayService(options) {
|
|
26
|
+
const plan = createGatewayServicePlan(currentPlanInput(options));
|
|
27
|
+
const run = options.run ?? runCommand;
|
|
28
|
+
await run(plan.uninstall.command, plan.uninstall.args);
|
|
29
|
+
if (plan.kind === "SYSTEMD_USER")
|
|
30
|
+
await run("systemctl", ["--user", "daemon-reload"]);
|
|
31
|
+
await rm(plan.definitionPath, { force: true });
|
|
32
|
+
return { uninstalled: true, plan };
|
|
33
|
+
}
|
|
34
|
+
export async function restartCurrentGatewayService(options) {
|
|
35
|
+
const plan = createGatewayServicePlan(currentPlanInput(options));
|
|
36
|
+
const run = options.run ?? runCommand;
|
|
37
|
+
await run(plan.stop.command, plan.stop.args).catch(() => undefined);
|
|
38
|
+
await run(plan.start.command, plan.start.args);
|
|
39
|
+
return plan;
|
|
40
|
+
}
|
|
41
|
+
export function createGatewayServicePlan(input) {
|
|
42
|
+
const suffix = createHash("sha256").update(`${input.repository}\n${input.gatewayDirectory}`).digest("hex").slice(0, 12);
|
|
43
|
+
const id = `witnora-gateway-${suffix}`;
|
|
44
|
+
const superviseArgs = [input.cliEntry, "gateway", "supervise", "--repo", input.repository, "--dir", input.gatewayDirectory];
|
|
45
|
+
if (input.platform === "win32")
|
|
46
|
+
return windowsPlan(input, id, superviseArgs);
|
|
47
|
+
if (input.platform === "darwin")
|
|
48
|
+
return launchdPlan(input, id, superviseArgs);
|
|
49
|
+
if (input.platform === "linux")
|
|
50
|
+
return systemdPlan(input, id, superviseArgs);
|
|
51
|
+
throw new Error(`Gateway service installation is not supported on ${input.platform}.`);
|
|
52
|
+
}
|
|
53
|
+
function windowsPlan(input, id, args) {
|
|
54
|
+
const definitionPath = `${input.serviceHome}\\${id}.xml`;
|
|
55
|
+
const argumentsValue = args.map(windowsArgument).join(" ");
|
|
56
|
+
const definition = `<?xml version="1.0" encoding="UTF-8"?>
|
|
57
|
+
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
58
|
+
<Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${xml(input.userId)}</UserId></LogonTrigger></Triggers>
|
|
59
|
+
<Principals><Principal id="Author"><UserId>${xml(input.userId)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
|
|
60
|
+
<Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><StartWhenAvailable>true</StartWhenAvailable><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure></Settings>
|
|
61
|
+
<Actions Context="Author"><Exec><Command>${xml(input.nodeExecutable)}</Command><Arguments>${xml(argumentsValue)}</Arguments><WorkingDirectory>${xml(input.repository)}</WorkingDirectory></Exec></Actions>
|
|
62
|
+
</Task>`;
|
|
63
|
+
return { id, kind: "WINDOWS_TASK", definitionPath, definition,
|
|
64
|
+
install: { command: "schtasks.exe", args: ["/Create", "/TN", id, "/XML", definitionPath, "/F"] },
|
|
65
|
+
start: { command: "schtasks.exe", args: ["/Run", "/TN", id] },
|
|
66
|
+
stop: { command: "schtasks.exe", args: ["/End", "/TN", id] },
|
|
67
|
+
uninstall: { command: "schtasks.exe", args: ["/Delete", "/TN", id, "/F"] } };
|
|
68
|
+
}
|
|
69
|
+
function currentPlanInput(options) {
|
|
70
|
+
const platform = options.platform ?? process.platform;
|
|
71
|
+
if (platform !== "win32" && platform !== "linux" && platform !== "darwin")
|
|
72
|
+
throw new Error(`Gateway service installation is not supported on ${platform}.`);
|
|
73
|
+
const repository = resolve(options.repository ?? process.cwd());
|
|
74
|
+
const env = options.env ?? process.env;
|
|
75
|
+
const serviceHome = platform === "win32" ? join(env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "Witnora", "services")
|
|
76
|
+
: platform === "darwin" ? join(homedir(), "Library", "LaunchAgents") : join(homedir(), ".config", "systemd", "user");
|
|
77
|
+
return { platform, repository, gatewayDirectory: resolve(repository, options.gatewayDirectory ?? ".witnora/gateway"), cliEntry: resolve(options.cliEntry), nodeExecutable: options.nodeExecutable ?? process.execPath, serviceHome, userId: platform === "win32" ? `${env.USERDOMAIN ? `${env.USERDOMAIN}\\` : ""}${env.USERNAME ?? userInfo().username}` : userInfo().username };
|
|
78
|
+
}
|
|
79
|
+
async function runCommand(command, args) { await execFileAsync(command, args, { windowsHide: true }); }
|
|
80
|
+
function systemdPlan(input, id, args) {
|
|
81
|
+
const unit = `${id}.service`;
|
|
82
|
+
const definitionPath = `${input.serviceHome}/${unit}`;
|
|
83
|
+
const definition = `[Unit]\nDescription=Witnora customer-owned Gateway\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=${systemd(input.repository)}\nExecStart=${[input.nodeExecutable, ...args].map(systemd).join(" ")}\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n`;
|
|
84
|
+
return { id, kind: "SYSTEMD_USER", definitionPath, definition,
|
|
85
|
+
install: { command: "systemctl", args: ["--user", "enable", "--now", unit] },
|
|
86
|
+
start: { command: "systemctl", args: ["--user", "start", unit] },
|
|
87
|
+
stop: { command: "systemctl", args: ["--user", "stop", unit] },
|
|
88
|
+
uninstall: { command: "systemctl", args: ["--user", "disable", "--now", unit] } };
|
|
89
|
+
}
|
|
90
|
+
function launchdPlan(input, id, args) {
|
|
91
|
+
const label = `com.witnora.${id}`;
|
|
92
|
+
const definitionPath = `${input.serviceHome}/${label}.plist`;
|
|
93
|
+
const programArguments = [input.nodeExecutable, ...args].map((value) => `<string>${xml(value)}</string>`).join("");
|
|
94
|
+
const definition = `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>${label}</string><key>ProgramArguments</key><array>${programArguments}</array><key>WorkingDirectory</key><string>${xml(input.repository)}</string><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>ThrottleInterval</key><integer>5</integer></dict></plist>`;
|
|
95
|
+
const domain = `gui/${process.getuid?.() ?? input.userId}`;
|
|
96
|
+
return { id, kind: "LAUNCHD_AGENT", definitionPath, definition,
|
|
97
|
+
install: { command: "launchctl", args: ["bootstrap", domain, definitionPath] },
|
|
98
|
+
start: { command: "launchctl", args: ["kickstart", `${domain}/${label}`] },
|
|
99
|
+
stop: { command: "launchctl", args: ["kill", "SIGTERM", `${domain}/${label}`] },
|
|
100
|
+
uninstall: { command: "launchctl", args: ["bootout", domain, definitionPath] } };
|
|
101
|
+
}
|
|
102
|
+
function windowsArgument(value) { return `"${value.replaceAll('"', '\\"')}"`; }
|
|
103
|
+
function systemd(value) { return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`; }
|
|
104
|
+
function xml(value) { return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'"); }
|
package/dist/gateway.js
CHANGED
|
@@ -514,6 +514,10 @@ export async function runCustomerGateway(options) {
|
|
|
514
514
|
host: process.env.WITNORA_GATEWAY_HOST?.trim() || config.host,
|
|
515
515
|
port: gatewayPort(process.env.WITNORA_GATEWAY_PORT, config.port),
|
|
516
516
|
environment: "customer-owned",
|
|
517
|
+
onRepairRequested: async ({ requestId }) => {
|
|
518
|
+
process.stderr.write(`Hosted connection repair ${requestId} requested a verified Gateway restart.\n`);
|
|
519
|
+
setTimeout(() => process.exit(75), 25).unref();
|
|
520
|
+
},
|
|
517
521
|
...(actionWorker && config.runtimeWorker ? { actionWorker: {
|
|
518
522
|
track: (input) => isConfiguredRuntimeProposal(input.proposal, config.runtimeWorker) ? actionWorker.track(input) : Promise.resolve(undefined),
|
|
519
523
|
status: async () => {
|
|
@@ -669,6 +673,7 @@ export async function createConfiguredWorkflowHarness(input) {
|
|
|
669
673
|
workerReady: !closing && !lastError,
|
|
670
674
|
...(lastTickAt ? { lastTickAt } : {}),
|
|
671
675
|
...(lastError ? { lastError } : {}),
|
|
676
|
+
...(input.config.schemaVersion === MANAGED_WORKFLOW_HARNESS_SCHEMA && input.config.realPathActivations?.length ? { realPathActivations: structuredClone(input.config.realPathActivations) } : {}),
|
|
672
677
|
};
|
|
673
678
|
},
|
|
674
679
|
tick,
|
|
@@ -1055,6 +1060,25 @@ export async function restartManagedCustomerGateway(options = {}) {
|
|
|
1055
1060
|
await stopManagedCustomerGateway(options);
|
|
1056
1061
|
return startManagedCustomerGateway(options);
|
|
1057
1062
|
}
|
|
1063
|
+
export async function superviseManagedCustomerGateway(options = {}) {
|
|
1064
|
+
const inspect = options.inspect ?? (() => statusManagedCustomerGateway({ repository: options.repository, dir: options.dir }));
|
|
1065
|
+
const start = options.start ?? (() => startManagedCustomerGateway({ repository: options.repository, dir: options.dir }));
|
|
1066
|
+
const sleep = options.sleep ?? wait;
|
|
1067
|
+
const maxCycles = options.maxCycles ?? Number.POSITIVE_INFINITY;
|
|
1068
|
+
let recoveries = 0;
|
|
1069
|
+
for (let cycle = 0; cycle < maxCycles; cycle += 1) {
|
|
1070
|
+
const status = await inspect();
|
|
1071
|
+
if (status.state === "CONFLICT")
|
|
1072
|
+
throw new Error(status.detail);
|
|
1073
|
+
if (status.state === "STOPPED" || status.state === "STALE") {
|
|
1074
|
+
await start();
|
|
1075
|
+
recoveries += 1;
|
|
1076
|
+
options.output?.(`Recovered customer-owned Gateway (${recoveries}).\n`);
|
|
1077
|
+
}
|
|
1078
|
+
if (cycle + 1 < maxCycles)
|
|
1079
|
+
await sleep(options.intervalMs ?? 5_000);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1058
1082
|
export async function readManagedCustomerGatewayLogs(options = {}) {
|
|
1059
1083
|
const repository = resolve(options.repository ?? process.cwd());
|
|
1060
1084
|
const directory = resolve(repository, options.dir ?? ".witnora/gateway");
|
|
@@ -1116,7 +1140,8 @@ export function parseManagedWorkflowHarnessConfig(raw) {
|
|
|
1116
1140
|
&& Array.isArray(workflowIds);
|
|
1117
1141
|
const managed = value.schemaVersion === MANAGED_WORKFLOW_HARNESS_SCHEMA
|
|
1118
1142
|
&& safeRelativeModulePath(value.evaluatorModulePath)
|
|
1119
|
-
&& validDigest(value.evaluatorModuleSha256 ?? "")
|
|
1143
|
+
&& validDigest(value.evaluatorModuleSha256 ?? "")
|
|
1144
|
+
&& validRealPathActivations(value.realPathActivations);
|
|
1120
1145
|
if (value.enabled !== true || !validDigest(value.evaluatorContractSha256 ?? "") || !workflowIdsValid || (!external && !managed)) {
|
|
1121
1146
|
throw new Error("workflow-harness.json requires an enabled, digest-pinned literal-loopback evaluator and a local credential handle.");
|
|
1122
1147
|
}
|
|
@@ -1128,6 +1153,25 @@ export function parseManagedWorkflowHarnessConfig(raw) {
|
|
|
1128
1153
|
}
|
|
1129
1154
|
return value;
|
|
1130
1155
|
}
|
|
1156
|
+
function validRealPathActivations(value) {
|
|
1157
|
+
if (value === undefined)
|
|
1158
|
+
return true;
|
|
1159
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > 50)
|
|
1160
|
+
return false;
|
|
1161
|
+
return value.every((item) => Boolean(item && typeof item === "object" && !Array.isArray(item)
|
|
1162
|
+
&& /^[A-Za-z0-9._:-]{1,200}$/.test(String(item.integrationId ?? ""))
|
|
1163
|
+
&& validDigest(item.integrationDigestSha256)
|
|
1164
|
+
&& /^[A-Za-z0-9._:-]{1,200}$/.test(String(item.taskContractId ?? ""))
|
|
1165
|
+
&& validDigest(item.taskContractDigestSha256)
|
|
1166
|
+
&& /^[A-Za-z0-9._:-]{1,200}$/.test(String(item.agentId ?? ""))
|
|
1167
|
+
&& typeof item.agentVersion === "string"
|
|
1168
|
+
&& ["sandbox", "staging", "production"].includes(item.environment)
|
|
1169
|
+
&& validDigest(item.providerContractDigestSha256)
|
|
1170
|
+
&& item.acceptance?.kind === "READ_ONLY_PROVIDER_PREFLIGHT"
|
|
1171
|
+
&& item.acceptance?.productionWrites === 0
|
|
1172
|
+
&& validDigest(item.acceptance?.observationDigestSha256)
|
|
1173
|
+
&& Number.isFinite(Date.parse(item.acceptance?.passedAt ?? ""))));
|
|
1174
|
+
}
|
|
1131
1175
|
export async function activateManagedWorkflowHarness(options) {
|
|
1132
1176
|
const repository = resolve(options.repository ?? process.cwd());
|
|
1133
1177
|
const directory = join(repository, ".witnora", "gateway");
|
|
@@ -1163,14 +1207,19 @@ export async function activateManagedWorkflowHarness(options) {
|
|
|
1163
1207
|
...(options.workflowIds ? { workflowIds: options.workflowIds } : {}),
|
|
1164
1208
|
...(options.pollIntervalMs === undefined ? {} : { pollIntervalMs: options.pollIntervalMs }),
|
|
1165
1209
|
...(options.maxConcurrency === undefined ? {} : { maxConcurrency: options.maxConcurrency }),
|
|
1210
|
+
...(options.realPathActivations?.length ? { realPathActivations: structuredClone(options.realPathActivations) } : {}),
|
|
1166
1211
|
}));
|
|
1167
1212
|
const path = join(directory, "workflow-harness.json");
|
|
1168
1213
|
const serialized = `${JSON.stringify(config, null, 2)}\n`;
|
|
1169
1214
|
let created = false;
|
|
1170
1215
|
if (await exists(path)) {
|
|
1171
1216
|
const current = await readFile(path, "utf8");
|
|
1172
|
-
if (current !== serialized)
|
|
1173
|
-
|
|
1217
|
+
if (current !== serialized) {
|
|
1218
|
+
const parsed = parseManagedWorkflowHarnessConfig(current);
|
|
1219
|
+
if (parsed.schemaVersion !== MANAGED_WORKFLOW_HARNESS_SCHEMA || canonicalHarnessConfig(parsed) !== canonicalHarnessConfig(config))
|
|
1220
|
+
throw new Error("Existing workflow-harness.json differs from the generated Assurance Harness binding; refusing to overwrite customer configuration.");
|
|
1221
|
+
await writeFile(path, serialized, { encoding: "utf8", mode: 0o600 });
|
|
1222
|
+
}
|
|
1174
1223
|
}
|
|
1175
1224
|
else {
|
|
1176
1225
|
await writeFile(path, serialized, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
@@ -1178,6 +1227,10 @@ export async function activateManagedWorkflowHarness(options) {
|
|
|
1178
1227
|
}
|
|
1179
1228
|
return { state: "READY_TO_START", path, modulePath: evaluatorModulePath, config, created };
|
|
1180
1229
|
}
|
|
1230
|
+
function canonicalHarnessConfig(value) {
|
|
1231
|
+
const { realPathActivations: _activations, ...stable } = value;
|
|
1232
|
+
return JSON.stringify(stable);
|
|
1233
|
+
}
|
|
1181
1234
|
export async function configureManagedWorkflowHarness(options) {
|
|
1182
1235
|
const directory = resolve(options.repository ?? process.cwd(), options.dir ?? ".witnora/gateway");
|
|
1183
1236
|
if (!await exists(join(directory, "gateway.json")))
|
|
@@ -50,6 +50,10 @@ export interface CustomerOwnedCollectorGatewayOptions {
|
|
|
50
50
|
status(): Promise<AssuranceHarnessHeartbeat>;
|
|
51
51
|
};
|
|
52
52
|
runtimeBinding?: RuntimeReadinessBinding;
|
|
53
|
+
onRepairRequested?: (request: {
|
|
54
|
+
requestId: string;
|
|
55
|
+
action: "RESTART_AND_VERIFY";
|
|
56
|
+
}) => void | Promise<void>;
|
|
53
57
|
}
|
|
54
58
|
export interface CustomerOwnedCollectorGateway {
|
|
55
59
|
baseUrl: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collector-gateway.d.ts","sourceRoot":"","sources":["../../../../agentcert-sdk/src/collector-gateway.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"collector-gateway.d.ts","sourceRoot":"","sources":["../../../../agentcert-sdk/src/collector-gateway.ts"],"names":[],"mappings":"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"}
|
|
@@ -176,7 +176,7 @@ export async function startCustomerOwnedCollectorGateway(options) {
|
|
|
176
176
|
try {
|
|
177
177
|
await ensureSourceKeyRegistered();
|
|
178
178
|
const workerStatus = options.actionWorker ? await options.actionWorker.status() : undefined;
|
|
179
|
-
await options.client.heartbeat({
|
|
179
|
+
const response = await options.client.heartbeat({
|
|
180
180
|
collectorId: activeCollector.id,
|
|
181
181
|
signer,
|
|
182
182
|
pendingRecordCount: current.pendingRecordCount,
|
|
@@ -184,6 +184,13 @@ export async function startCustomerOwnedCollectorGateway(options) {
|
|
|
184
184
|
runtime: runtimeBinding && workerStatus ? runtimeHeartbeat(runtimeBinding, workerStatus, options.actionWorker?.runtimeBinding?.fixtureReady) : undefined,
|
|
185
185
|
assuranceHarness: options.assuranceHarness ? await options.assuranceHarness.status() : undefined,
|
|
186
186
|
});
|
|
187
|
+
const repair = response.repair;
|
|
188
|
+
if (repair && typeof repair === "object" && !Array.isArray(repair)) {
|
|
189
|
+
const requestId = repair.requestId;
|
|
190
|
+
const action = repair.action;
|
|
191
|
+
if (typeof requestId === "string" && action === "RESTART_AND_VERIFY")
|
|
192
|
+
await options.onRepairRequested?.({ requestId, action });
|
|
193
|
+
}
|
|
187
194
|
lastRemoteSuccessAt = new Date().toISOString();
|
|
188
195
|
lastRemoteError = undefined;
|
|
189
196
|
}
|
|
@@ -63,6 +63,24 @@ export interface AssuranceHarnessHeartbeat {
|
|
|
63
63
|
workerReady: boolean;
|
|
64
64
|
lastTickAt?: string;
|
|
65
65
|
lastError?: string;
|
|
66
|
+
realPathActivations?: RealPathHarnessActivation[];
|
|
67
|
+
}
|
|
68
|
+
export interface RealPathHarnessActivation {
|
|
69
|
+
integrationId: string;
|
|
70
|
+
integrationDigestSha256: string;
|
|
71
|
+
taskContractId: string;
|
|
72
|
+
taskContractDigestSha256: string;
|
|
73
|
+
agentId: string;
|
|
74
|
+
agentVersion: string;
|
|
75
|
+
environment: "sandbox" | "staging" | "production";
|
|
76
|
+
providerPackId: string;
|
|
77
|
+
providerContractDigestSha256: string;
|
|
78
|
+
acceptance: {
|
|
79
|
+
kind: "READ_ONLY_PROVIDER_PREFLIGHT";
|
|
80
|
+
passedAt: string;
|
|
81
|
+
productionWrites: 0;
|
|
82
|
+
observationDigestSha256: string;
|
|
83
|
+
};
|
|
66
84
|
}
|
|
67
85
|
export interface RemoteCollectorClientOptions {
|
|
68
86
|
baseUrl: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-collector.d.ts","sourceRoot":"","sources":["../../../../agentcert-sdk/src/remote-collector.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAeD,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,sCAAsC,CAAC;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;IACxG,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE;QAAE,SAAS,EAAE,SAAS,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7E;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,qCAAqC,CAAC;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACvC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,2BAA2B,EAAE,MAAM,CAAC;CACrC;AAED,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,oBAAoB,CAAC;IACrC,OAAO,EAAE,uBAAuB,CAAC;IACjC,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uBAAuB,EAAE,MAAM,CAAC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"remote-collector.d.ts","sourceRoot":"","sources":["../../../../agentcert-sdk/src/remote-collector.ts"],"names":[],"mappings":"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"}
|
package/dist/onboard.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { DEFAULT_WITNORA_SERVER, saveConnection } from "./credentials.js";
|
|
5
6
|
import { authorizeProjectConnection } from "./device-authorization.js";
|
|
6
7
|
import { verifyControlPlaneConnection } from "./control-plane.js";
|
|
@@ -10,6 +11,8 @@ import { writeTryEvidence } from "./try.js";
|
|
|
10
11
|
import { doctorCustomerGateway, activateManagedWorkflowHarness, initializeCustomerGateway, inspectLocalRuntimeBinding, inspectCustomerGatewayFiles, startManagedCustomerGateway, stopManagedCustomerGateway, upgradeCustomerGatewayRuntime, RuntimeSetupNotReadyError, } from "./gateway.js";
|
|
11
12
|
import { discoverRuntimeReferences, planRuntimeSandboxModules } from "./runtime-sandbox-kit.js";
|
|
12
13
|
import { automaticRuntimeReferencesUsable, bootstrapLocalRuntime, verifyAutomaticRuntimeProbe } from "./runtime-bootstrap.js";
|
|
14
|
+
import { activateRealPathIntegrations } from "./real-path-activation.js";
|
|
15
|
+
import { installCurrentGatewayService } from "./gateway-service.js";
|
|
13
16
|
export async function runOnboard(options) {
|
|
14
17
|
const requestFetch = options.fetch ?? fetch;
|
|
15
18
|
const output = options.output ?? ((message) => process.stdout.write(message));
|
|
@@ -90,6 +93,7 @@ export async function runOnboard(options) {
|
|
|
90
93
|
limitation: "No approved customer Harness module is bound to this Agent repository.",
|
|
91
94
|
};
|
|
92
95
|
let assuranceHarnessChanged = false;
|
|
96
|
+
let continuousService;
|
|
93
97
|
await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, { status: "installing", attemptId });
|
|
94
98
|
try {
|
|
95
99
|
generatedFiles.push(...await generateRepositoryConfig(repositoryPath, repository.template, repository.name));
|
|
@@ -175,6 +179,10 @@ export async function runOnboard(options) {
|
|
|
175
179
|
}
|
|
176
180
|
}
|
|
177
181
|
else {
|
|
182
|
+
const stopped = await gatewayLifecycle.stop({ repository: repositoryPath, configHome: options.configHome, fetch: requestFetch });
|
|
183
|
+
if (stopped.state === "STOPPED" || stopped.state === "STALE") {
|
|
184
|
+
output(`\nStopped the managed Gateway for project ${binding.projectId} before rebinding this repository.\n`);
|
|
185
|
+
}
|
|
178
186
|
await assertGatewayStopped(requestFetch, binding.host, binding.port, binding.projectId, token.projectId);
|
|
179
187
|
gatewayMigration = await archiveGateway(gatewayState.directory, repositoryPath, binding.projectId);
|
|
180
188
|
output(`\nExisting Gateway belongs to project ${binding.projectId}; archived it at ${gatewayMigration.archiveDirectory}.\n`);
|
|
@@ -188,8 +196,10 @@ export async function runOnboard(options) {
|
|
|
188
196
|
}
|
|
189
197
|
const runtimeConfigured = await gatewayHasRuntimeWorker(repositoryPath);
|
|
190
198
|
generatedFiles.push(...await generateAutopilotFiles(repositoryPath, repository.name, runtimeConfigured));
|
|
199
|
+
const realPathActivation = await activateRealPathIntegrations({ repository: repositoryPath, server, projectId: token.projectId, apiKey: token.apiKey, env: options.env, fetch: requestFetch });
|
|
200
|
+
generatedFiles.push(...realPathActivation.generatedFiles);
|
|
191
201
|
try {
|
|
192
|
-
const activation = await activateManagedWorkflowHarness({ repository: repositoryPath });
|
|
202
|
+
const activation = await activateManagedWorkflowHarness({ repository: repositoryPath, realPathActivations: realPathActivation.activations });
|
|
193
203
|
if (activation.created)
|
|
194
204
|
generatedFiles.push(activation.path);
|
|
195
205
|
assuranceHarnessChanged = activation.created;
|
|
@@ -206,6 +216,10 @@ export async function runOnboard(options) {
|
|
|
206
216
|
if (stopped.state !== "STOPPED" && stopped.state !== "STALE")
|
|
207
217
|
throw new Error("The prior managed Gateway process did not stop before Assurance Harness activation.");
|
|
208
218
|
}
|
|
219
|
+
if (!options.gatewayLifecycle) {
|
|
220
|
+
const installed = await installCurrentGatewayService({ repository: repositoryPath, cliEntry: fileURLToPath(new URL("./cli.js", import.meta.url)) });
|
|
221
|
+
continuousService = { state: "INSTALLED", kind: installed.plan.kind, id: installed.plan.id };
|
|
222
|
+
}
|
|
209
223
|
managedGateway = await gatewayLifecycle.start({
|
|
210
224
|
repository: repositoryPath,
|
|
211
225
|
configHome: options.configHome,
|
|
@@ -230,17 +244,19 @@ export async function runOnboard(options) {
|
|
|
230
244
|
output(`Private discovery: ${discovery.capabilityCount} capability group(s); ${discovery.unknownCapabilityCount} pending confirmation.\n`);
|
|
231
245
|
output("Installed: customer-owned Gateway, default-deny policy, independent-probe contract, review contract, and PR/release/nightly CI.\n");
|
|
232
246
|
output(`Gateway: ${managedGateway.state} at ${managedGateway.baseUrl}${managedGateway.pid ? ` (process ${managedGateway.pid})` : ""}.\n`);
|
|
247
|
+
if (continuousService)
|
|
248
|
+
output(`Continuous operation: ${continuousService.kind} ${continuousService.id} will start at login and recover the Gateway after process failure.\n`);
|
|
233
249
|
output(runtimeReadiness.state === "LOCAL_SANDBOX_READY"
|
|
234
250
|
? "Runtime: LOCAL_SANDBOX_READY. This proves only the generated localhost sandbox loop is ready; it does not establish coverage, CURRENT, or a verified customer outcome.\n"
|
|
235
251
|
: `Runtime: RECORDED_ONLY. ${runtimeReadiness.limitations[0]}\n`);
|
|
236
252
|
output(assuranceHarnessReadiness.state === "ACTIVE"
|
|
237
|
-
?
|
|
253
|
+
? `Assurance Harness: ACTIVE. Nora can dispatch authority-ready Replay and no-write Shadow evaluations without manual Workflow IDs, evaluator origins, credentials, or contract digests.${realPathActivation.activations.length ? ` ${realPathActivation.activations.length} exact Stripe test-mode real-path binding(s) passed read-only preflight.` : ""}\n`
|
|
238
254
|
: `Assurance Harness: WAITING_FOR_CUSTOMER_HARNESS. ${assuranceHarnessReadiness.limitation}\n`);
|
|
239
255
|
output("The self-test remains isolated. The Gateway is ready in the background; run the agent normally. The first source-signed, server-reconciled Gateway run completes onboarding.\n");
|
|
240
256
|
return { projectId: token.projectId, connectionName: token.connectionName, credentialsPath, template: repository.template,
|
|
241
257
|
repositoryKind: repository.kind, generatedFiles, receiptPath, discovery, setupPlanId: setupPlan.id,
|
|
242
258
|
gatewayDirectory: join(repositoryPath, ".witnora", "gateway"), gatewayArchiveDirectory: gatewayMigration?.archiveDirectory,
|
|
243
|
-
gateway: managedGateway, runtimeReadiness, assuranceHarnessReadiness };
|
|
259
|
+
gateway: managedGateway, runtimeReadiness, assuranceHarnessReadiness, continuousService };
|
|
244
260
|
}
|
|
245
261
|
catch (error) {
|
|
246
262
|
const diagnosis = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
export async function activateRealPathIntegrations(options) {
|
|
5
|
+
const repository = resolve(options.repository ?? process.cwd());
|
|
6
|
+
const request = options.fetch ?? fetch;
|
|
7
|
+
const base = options.server.replace(/\/$/, "");
|
|
8
|
+
const response = await request(`${base}/v1/projects/${encodeURIComponent(options.projectId)}/real-path-integrations`, { headers: { authorization: `Bearer ${options.apiKey}` } });
|
|
9
|
+
if (response.status === 404)
|
|
10
|
+
return { state: "NOT_CONFIGURED", created: false, activations: [], generatedFiles: [] };
|
|
11
|
+
if (!response.ok)
|
|
12
|
+
throw new Error(`Could not load approved real-path integrations (${response.status}).`);
|
|
13
|
+
const body = await boundedJson(response);
|
|
14
|
+
const approved = Array.isArray(body.integrations) ? body.integrations.map(parsePlan).filter((plan) => plan.status === "READY_TO_ACTIVATE") : [];
|
|
15
|
+
const plans = approved.filter((plan) => plan.generated.providerPackId === "STRIPE_REFUND" && plan.environment === "sandbox" && plan.customerSummary.evaluationMode === "SHADOW");
|
|
16
|
+
if (!plans.length)
|
|
17
|
+
return { state: "NOT_CONFIGURED", created: false, activations: [], generatedFiles: [] };
|
|
18
|
+
const secret = options.env?.STRIPE_SECRET_KEY ?? process.env.STRIPE_SECRET_KEY;
|
|
19
|
+
if (!secret?.startsWith("sk_test_") || secret.length < 12)
|
|
20
|
+
throw new Error("Stripe test-mode activation requires STRIPE_SECRET_KEY to reference an sk_test_ credential in the customer environment.");
|
|
21
|
+
const activations = [];
|
|
22
|
+
const scenarios = [];
|
|
23
|
+
for (const plan of plans) {
|
|
24
|
+
const preflight = await stripePreflight(request, secret, plan, options.now?.() ?? new Date());
|
|
25
|
+
const acceptance = preflight.acceptance;
|
|
26
|
+
activations.push({ integrationId: plan.id, integrationDigestSha256: plan.digestSha256, taskContractId: plan.taskContractId, taskContractDigestSha256: plan.taskContractDigestSha256, agentId: plan.subject.agentId, agentVersion: plan.subject.agentVersion, environment: plan.environment, providerPackId: plan.generated.providerPackId, providerContractDigestSha256: plan.generated.providerContractDigestSha256, acceptance });
|
|
27
|
+
scenarios.push({ plan, value: [preflight.scenario] });
|
|
28
|
+
}
|
|
29
|
+
const modulePath = join(repository, "witnora.assurance-harness.mjs");
|
|
30
|
+
const manifestPath = join(repository, ".witnora", "gateway", "real-path-activations.json");
|
|
31
|
+
const source = generatedStripeHarness(plans);
|
|
32
|
+
const moduleDigestSha256 = sha(source);
|
|
33
|
+
let created = false;
|
|
34
|
+
const generatedFiles = [];
|
|
35
|
+
const current = await readFile(modulePath, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
|
|
36
|
+
const prior = await readFile(manifestPath, "utf8").then((value) => JSON.parse(value)).catch(() => undefined);
|
|
37
|
+
if (current === undefined) {
|
|
38
|
+
await writeFile(modulePath, source, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
39
|
+
created = true;
|
|
40
|
+
generatedFiles.push(modulePath);
|
|
41
|
+
}
|
|
42
|
+
else if (sha(current) !== prior?.moduleDigestSha256)
|
|
43
|
+
throw new Error("Existing Assurance Harness is customer-modified or belongs to a different integration; refusing to overwrite it.");
|
|
44
|
+
else if (current !== source) {
|
|
45
|
+
const temporary = `${modulePath}.${randomUUID()}.tmp`;
|
|
46
|
+
await writeFile(temporary, source, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
47
|
+
await rename(temporary, modulePath);
|
|
48
|
+
generatedFiles.push(modulePath);
|
|
49
|
+
}
|
|
50
|
+
const manifest = { schemaVersion: "witnora.real_path_activation_manifest.v0.1", projectId: options.projectId, moduleDigestSha256, activations };
|
|
51
|
+
await mkdir(dirname(manifestPath), { recursive: true });
|
|
52
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
53
|
+
generatedFiles.push(manifestPath);
|
|
54
|
+
for (const item of scenarios) {
|
|
55
|
+
const path = join(repository, ".witnora", "scenarios", `${item.plan.taskContractId}.json`);
|
|
56
|
+
await mkdir(dirname(path), { recursive: true });
|
|
57
|
+
try {
|
|
58
|
+
await writeFile(path, `${JSON.stringify(item.value, null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
59
|
+
generatedFiles.push(path);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error.code !== "EEXIST")
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { state: "READY_TO_START", created, modulePath: "witnora.assurance-harness.mjs", activations, generatedFiles };
|
|
67
|
+
}
|
|
68
|
+
async function stripePreflight(request, secret, plan, now) {
|
|
69
|
+
const response = await request("https://api.stripe.com/v1/refunds?limit=1", { headers: { authorization: `Bearer ${secret}` }, redirect: "error", signal: AbortSignal.timeout(5_000) });
|
|
70
|
+
if (!response.ok)
|
|
71
|
+
throw new Error(`Stripe test-mode read-only preflight failed (${response.status}).`);
|
|
72
|
+
const body = await boundedJson(response);
|
|
73
|
+
if (body.object !== "list" || !Array.isArray(body.data))
|
|
74
|
+
throw new Error("Stripe test-mode preflight returned an unexpected contract.");
|
|
75
|
+
const first = body.data[0] && typeof body.data[0] === "object" && !Array.isArray(body.data[0]) ? body.data[0] : undefined;
|
|
76
|
+
if (!first || typeof first.id !== "string" || !/^re_[A-Za-z0-9_]{1,200}$/.test(first.id))
|
|
77
|
+
throw new Error("Stripe test-mode activation requires one existing test refund for the no-write acceptance.");
|
|
78
|
+
const observation = Object.fromEntries(["status", "amount", "currency", "failure_reason"].flatMap((key) => scalar(first[key]) ? [[key, first[key]]] : []));
|
|
79
|
+
if (observation[plan.generated.criterion.field] !== plan.generated.criterion.expected)
|
|
80
|
+
throw new Error("The latest Stripe test refund does not satisfy the approved business success definition.");
|
|
81
|
+
const parameterDigest = sha(first.id);
|
|
82
|
+
const resultDigest = sha(canonical(observation));
|
|
83
|
+
return { acceptance: { kind: "READ_ONLY_PROVIDER_PREFLIGHT", passedAt: now.toISOString(), productionWrites: 0, observationDigestSha256: resultDigest }, scenario: { id: `stripe:${sha(first.id).slice(0, 16)}`, source: "LIVE_SHADOW", sanitized: true, input: { resourceId: first.id }, inputDigestSha256: sha(canonical({ resourceId: first.id })), baseline: { resultDigestSha256: resultDigest, actionIntents: plan.generated.actionPathBindings.map((item) => ({ pathId: item.actionPathId, parametersDigestSha256: parameterDigest })) } } };
|
|
84
|
+
}
|
|
85
|
+
function generatedStripeHarness(plans) {
|
|
86
|
+
const contracts = plans.map((plan) => ({ taskContractId: plan.taskContractId, criterion: plan.generated.criterion, actionPathIds: plan.generated.actionPathBindings.map((item) => item.actionPathId) }));
|
|
87
|
+
return `import {createHash} from "node:crypto";\nimport {readFile} from "node:fs/promises";\nimport {join} from "node:path";\nconst contracts=${JSON.stringify(contracts)};\nconst sha=(value)=>createHash("sha256").update(typeof value==="string"?value:JSON.stringify(value)).digest("hex");\nexport function createWitnoraBusinessTaskEvaluatorOptions(context){return {\n loadShadowObservations:async(task)=>{const contract=contracts.find((item)=>item.taskContractId===task.id);if(!contract)throw new Error("No exact real-path contract.");const path=join(context.repository,".witnora","scenarios",task.id+".json");const value=JSON.parse(await readFile(path,"utf8"));if(!Array.isArray(value)||value.length>100)throw new Error("Local scenario file is invalid.");return value;},\n evaluateShadowCandidate:async(candidate,task)=>{const contract=contracts.find((item)=>item.taskContractId===task.id);if(!contract)throw new Error("No exact real-path contract.");const resourceId=String(candidate.input?.resourceId??"");if(!/^re_[A-Za-z0-9_]{1,200}$/.test(resourceId))throw new Error("Stripe refund resourceId is invalid.");for(const pathId of contract.actionPathIds)candidate.propose({pathId,parametersDigestSha256:sha(resourceId)});const key=process.env.STRIPE_SECRET_KEY;if(!key?.startsWith("sk_test_"))throw new Error("Stripe test-mode credential is unavailable.");const response=await fetch("https://api.stripe.com/v1/refunds/"+encodeURIComponent(resourceId),{headers:{authorization:"Bearer "+key},redirect:"error",signal:AbortSignal.timeout(5000)});if(!response.ok)throw new Error("Stripe read-only observation failed.");const raw=await response.json();const observed={status:raw.status,amount:raw.amount,currency:raw.currency,failure_reason:raw.failure_reason};const actual=observed[contract.criterion.field];return {resultDigestSha256:sha(observed),criteria:[{id:contract.criterion.id,passed:actual===contract.criterion.expected}]};}\n};}\n`;
|
|
88
|
+
}
|
|
89
|
+
function parsePlan(value) { if (!value || typeof value !== "object" || Array.isArray(value))
|
|
90
|
+
throw new Error("Real-path integration response is invalid."); const plan = value; if (plan.schemaVersion !== "witnora.real_path_integration.v0.1" || !plan.id || !plan.taskContractId || !plan.subject?.agentId || !plan.subject.agentVersion || !plan.generated || plan.generated.boundaries?.rawPayloadUpload !== false || plan.generated.boundaries.rawCredentialUpload !== false || plan.generated.boundaries.evaluatorWrites !== false || plan.generated.boundaries.firstAcceptanceProductionWrites !== 0 || !digest(plan.digestSha256) || !digest(plan.taskContractDigestSha256) || !digest(plan.generated.providerContractDigestSha256))
|
|
91
|
+
throw new Error("Real-path integration response failed its safety contract."); return plan; }
|
|
92
|
+
async function boundedJson(response) { const text = await response.text(); if (text.length > 1_048_576)
|
|
93
|
+
throw new Error("Real-path response exceeded the size limit."); const value = JSON.parse(text); if (!value || typeof value !== "object" || Array.isArray(value))
|
|
94
|
+
throw new Error("Real-path response is invalid."); return value; }
|
|
95
|
+
function scalar(value) { return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean"; }
|
|
96
|
+
function digest(value) { return typeof value === "string" && /^[a-f0-9]{64}$/.test(value); }
|
|
97
|
+
function canonical(value) { if (value === null || typeof value !== "object")
|
|
98
|
+
return JSON.stringify(value); if (Array.isArray(value))
|
|
99
|
+
return `[${value.map(canonical).join(",")}]`; return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`; }
|
|
100
|
+
function sha(value) { return createHash("sha256").update(value).digest("hex"); }
|
|
@@ -49,8 +49,11 @@ export async function bootstrapLocalRuntime(input) {
|
|
|
49
49
|
body: JSON.stringify({ ...contract, contractFingerprintSha256 }),
|
|
50
50
|
});
|
|
51
51
|
const body = await response.json().catch(() => ({}));
|
|
52
|
-
if (!response.ok)
|
|
53
|
-
|
|
52
|
+
if (!response.ok) {
|
|
53
|
+
const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : undefined;
|
|
54
|
+
const code = typeof body.code === "string" ? body.code : undefined;
|
|
55
|
+
throw new Error(`Hosted Runtime bootstrap did not complete (HTTP ${response.status})${code ? ` [${code}]` : ""}${detail ? `: ${detail.slice(0, 300)}` : "."}`);
|
|
56
|
+
}
|
|
54
57
|
const issued = validateResponse(body, contractFingerprintSha256, attempt.deliveryPublicKeyPem);
|
|
55
58
|
const probeCredentialPath = join(directory, "outcome-probe-api-key.txt");
|
|
56
59
|
const probeCredentialMetadataPath = join(directory, "outcome-probe-api-key.json");
|