witnora 0.20.15 → 0.20.17
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/credentials.js +4 -2
- package/dist/device-authorization.js +1 -0
- package/dist/onboard.js +21 -11
- package/dist/runtime-bootstrap.js +2 -1
- package/dist/runtime-sandbox-kit.js +22 -4
- package/package.json +1 -1
package/dist/credentials.js
CHANGED
|
@@ -23,8 +23,9 @@ export async function saveConnection(name, input, options = {}) {
|
|
|
23
23
|
return path;
|
|
24
24
|
}
|
|
25
25
|
export async function loadConnection(name, options = {}) {
|
|
26
|
+
const explicitConfigHome = options.configHome ?? process.env.WITNORA_CONFIG_HOME ?? process.env.AGENTCERT_CONFIG_HOME;
|
|
26
27
|
const file = await readCredentialFile(credentialsPath(options))
|
|
27
|
-
?? (
|
|
28
|
+
?? (explicitConfigHome !== undefined ? undefined : await readCredentialFile(legacyCredentialsPath()));
|
|
28
29
|
if (!file)
|
|
29
30
|
return undefined;
|
|
30
31
|
const connectionName = name ? validateConnectionName(name) : file.defaultConnection;
|
|
@@ -90,7 +91,8 @@ function validateConnection(input) {
|
|
|
90
91
|
throw new Error("Witnora project ID is required.");
|
|
91
92
|
if (!apiKey.startsWith("ac_live_"))
|
|
92
93
|
throw new Error("Witnora API key must start with ac_live_.");
|
|
93
|
-
|
|
94
|
+
const approvedSetupPlanId = input.approvedSetupPlanId?.trim();
|
|
95
|
+
return { server: normalizeServer(input.server), projectId, apiKey, ...(approvedSetupPlanId ? { approvedSetupPlanId } : {}) };
|
|
94
96
|
}
|
|
95
97
|
function normalizeServer(value) {
|
|
96
98
|
let url;
|
|
@@ -25,6 +25,7 @@ export async function authorizeProjectConnection(options) {
|
|
|
25
25
|
server,
|
|
26
26
|
projectId: token.projectId,
|
|
27
27
|
apiKey: token.apiKey,
|
|
28
|
+
approvedSetupPlanId: token.approvedSetupPlanId,
|
|
28
29
|
}, { configHome: options.configHome });
|
|
29
30
|
return { ...token, server, credentialsPath };
|
|
30
31
|
}
|
package/dist/onboard.js
CHANGED
|
@@ -48,18 +48,22 @@ export async function runOnboard(options) {
|
|
|
48
48
|
configHome: options.configHome,
|
|
49
49
|
});
|
|
50
50
|
if (reusableConnection)
|
|
51
|
-
output("Reusing
|
|
51
|
+
output("Reusing this Agent's previously approved setup connection; verifying its exact Setup Plan.\n");
|
|
52
52
|
const credentialsPath = token.credentialsPath;
|
|
53
53
|
await verifyControlPlaneConnection({ baseUrl: server, projectId: token.projectId, apiKey: token.apiKey, fetch: requestFetch });
|
|
54
|
-
const
|
|
54
|
+
const planQuery = token.approvedSetupPlanId ? `?setupPlanId=${encodeURIComponent(token.approvedSetupPlanId)}` : "";
|
|
55
|
+
const planSnapshot = await jsonRequest(requestFetch, `${server}/v1/projects/${encodeURIComponent(token.projectId)}/setup-plans${planQuery}`, {
|
|
55
56
|
method: "GET", headers: { authorization: `Bearer ${token.apiKey}` },
|
|
56
57
|
});
|
|
57
58
|
const setupPlan = planSnapshot.plan;
|
|
58
|
-
if (!setupPlan || !setupPlan.authorizedOperations.some((item) => item.kind === "authorize_install")) {
|
|
59
|
+
if (!setupPlan || (token.approvedSetupPlanId && setupPlan.id !== token.approvedSetupPlanId) || !setupPlan.authorizedOperations.some((item) => item.kind === "authorize_install")) {
|
|
59
60
|
throw new Error("Confirm the Setup Plan and authorize installation in the Witnora Workspace before running onboard.");
|
|
60
61
|
}
|
|
62
|
+
const attemptId = `install-${Date.now()}-${randomSuffix()}`;
|
|
63
|
+
// Resume an authorized failed install before requesting fresh Runtime authority.
|
|
64
|
+
await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, { status: "installing", attemptId });
|
|
61
65
|
let localRuntime = await discoverRuntimeReferences({
|
|
62
|
-
repository: repositoryPath, configHome: options.configHome, projectId: token.projectId, server, env: options.env ?? process.env,
|
|
66
|
+
repository: repositoryPath, configHome: options.configHome, projectId: token.projectId, setupPlanId: setupPlan.id, server, env: options.env ?? process.env,
|
|
63
67
|
});
|
|
64
68
|
let runtimeLimitation = localRuntime.limitation;
|
|
65
69
|
let runtimeBootstrapRequired = !localRuntime.references;
|
|
@@ -96,7 +100,6 @@ export async function runOnboard(options) {
|
|
|
96
100
|
runtimeLimitation = `Hosted Runtime bootstrap did not complete: ${detail.slice(0, 500)} The existing Gateway was preserved and setup remains RECORDED_ONLY.`;
|
|
97
101
|
}
|
|
98
102
|
}
|
|
99
|
-
const attemptId = `install-${Date.now()}-${randomSuffix()}`;
|
|
100
103
|
const generatedFiles = [];
|
|
101
104
|
const gatewayLifecycle = options.gatewayLifecycle ?? {
|
|
102
105
|
start: startManagedCustomerGateway,
|
|
@@ -145,7 +148,6 @@ export async function runOnboard(options) {
|
|
|
145
148
|
}
|
|
146
149
|
throw lastError;
|
|
147
150
|
};
|
|
148
|
-
await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, { status: "installing", attemptId });
|
|
149
151
|
try {
|
|
150
152
|
generatedFiles.push(...await generateRepositoryConfig(repositoryPath, repository.template, repository.name));
|
|
151
153
|
const selfTestDir = join(repositoryPath, ".witnora", "onboarding", "self-test", attemptId);
|
|
@@ -203,9 +205,13 @@ export async function runOnboard(options) {
|
|
|
203
205
|
generatedFiles.push(...gateway.generatedFiles);
|
|
204
206
|
}
|
|
205
207
|
else {
|
|
206
|
-
const binding = await inspectGatewayBinding(gatewayState.directory, token.projectId, server);
|
|
207
|
-
if (binding.
|
|
208
|
-
|
|
208
|
+
const binding = await inspectGatewayBinding(gatewayState.directory, token.projectId, server, setupPlan.id);
|
|
209
|
+
if (binding.projectId === token.projectId && binding.server === server) {
|
|
210
|
+
const planRepairRequired = !binding.matches && Boolean(binding.runtimeSetupPlanId);
|
|
211
|
+
if (planRepairRequired && !localRuntime.references) {
|
|
212
|
+
throw new RuntimeSetupNotReadyError(`The existing Gateway belongs to a different Setup Plan and could not be restored. ${runtimeLimitation ?? "Runtime references are unavailable."} Run onboard again to retry; the existing Gateway is preserved.`);
|
|
213
|
+
}
|
|
214
|
+
await saveConnection(binding.connectionName, { server, projectId: token.projectId, apiKey: token.apiKey, approvedSetupPlanId: setupPlan.id }, { configHome: options.configHome });
|
|
209
215
|
if (localRuntime.references) {
|
|
210
216
|
try {
|
|
211
217
|
runtimeUpgrade = await prepareRuntime(() => upgradeCustomerGatewayRuntime({ repository: repositoryPath, authorization: token, runtimeReferences: localRuntime.references, agentIdentity, configHome: options.configHome, fetch: requestFetch }));
|
|
@@ -222,6 +228,8 @@ export async function runOnboard(options) {
|
|
|
222
228
|
catch (error) {
|
|
223
229
|
if (!(error instanceof RuntimeSetupNotReadyError))
|
|
224
230
|
throw error;
|
|
231
|
+
if (planRepairRequired)
|
|
232
|
+
throw error;
|
|
225
233
|
runtimeLimitation = `Local sandbox Runtime references were found but did not establish readiness: ${error.message}`;
|
|
226
234
|
output(`\nExisting customer-owned Gateway remains RECORDED_ONLY because its Runtime references did not pass readiness checks: ${error.message}\n`);
|
|
227
235
|
}
|
|
@@ -597,7 +605,7 @@ function configuredGatewayPorts(environment) {
|
|
|
597
605
|
const port = Number(value);
|
|
598
606
|
return Number.isInteger(port) && port > 0 && port <= 65_535 ? new Set([port]) : new Set();
|
|
599
607
|
}
|
|
600
|
-
async function inspectGatewayBinding(directory, projectId, server) {
|
|
608
|
+
async function inspectGatewayBinding(directory, projectId, server, setupPlanId) {
|
|
601
609
|
let value;
|
|
602
610
|
try {
|
|
603
611
|
value = JSON.parse(await readFile(join(directory, "gateway.json"), "utf8"));
|
|
@@ -611,10 +619,12 @@ async function inspectGatewayBinding(directory, projectId, server) {
|
|
|
611
619
|
const collectorId = typeof value.collectorId === "string" ? value.collectorId.trim() : "";
|
|
612
620
|
const host = typeof value.host === "string" && value.host.trim() ? value.host.trim() : "127.0.0.1";
|
|
613
621
|
const port = Number(value.port);
|
|
622
|
+
const worker = value.runtimeWorker;
|
|
623
|
+
const runtimeSetupPlanId = typeof worker?.runtimeSetupPlanId === "string" ? worker.runtimeSetupPlanId : undefined;
|
|
614
624
|
if (!existingProjectId || !existingServer || !connectionName || !collectorId || !Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
615
625
|
throw new Error("Existing Gateway configuration is missing a valid project, server, connection, collector, host, or port. Preserve it and repair the setup in Advanced mode.");
|
|
616
626
|
}
|
|
617
|
-
return { matches: existingProjectId === projectId && existingServer === server, projectId: existingProjectId, server: existingServer, connectionName, collectorId, host, port };
|
|
627
|
+
return { matches: existingProjectId === projectId && existingServer === server && runtimeSetupPlanId === setupPlanId, runtimeSetupPlanId, projectId: existingProjectId, server: existingServer, connectionName, collectorId, host, port };
|
|
618
628
|
}
|
|
619
629
|
async function assertGatewayStopped(requestFetch, host, port, oldCollectorId, oldProjectId, newProjectId) {
|
|
620
630
|
try {
|
|
@@ -3,7 +3,7 @@ import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { credentialsPath } from "./credentials.js";
|
|
6
|
-
import { saveRuntimeReferenceManifest } from "./runtime-sandbox-kit.js";
|
|
6
|
+
import { runtimeReferenceManifestPath, saveRuntimeReferenceManifest } from "./runtime-sandbox-kit.js";
|
|
7
7
|
const SCHEMA_VERSION = "witnora.runtime_bootstrap.v0.1";
|
|
8
8
|
const ENVELOPE_ALGORITHM = "X25519-HKDF-SHA256-A256GCM";
|
|
9
9
|
const PROBE_SCOPES = ["outcomes:verify"];
|
|
@@ -85,6 +85,7 @@ export async function bootstrapLocalRuntime(input) {
|
|
|
85
85
|
await acknowledge(requestFetch, endpoint, input.apiKey, issued.bootstrapId, issued.envelope.digestSha256);
|
|
86
86
|
const manifestPath = join(dirname(credentialsPath({ configHome: input.configHome })), "runtime-references.json");
|
|
87
87
|
await saveRuntimeReferenceManifest(manifestPath, input.projectId, server, references);
|
|
88
|
+
await saveRuntimeReferenceManifest(runtimeReferenceManifestPath({ ...input, server, setupPlanId: input.planId }), input.projectId, server, references);
|
|
88
89
|
await rm(attemptPath, { force: true });
|
|
89
90
|
return { references, bootstrapId: issued.bootstrapId };
|
|
90
91
|
}
|
|
@@ -16,15 +16,26 @@ export async function discoverRuntimeReferences(input) {
|
|
|
16
16
|
runtimeKeyId: input.env.WITNORA_RUNTIME_KEY_ID?.trim(),
|
|
17
17
|
runtimeSigningKeyHandle: input.env.WITNORA_RUNTIME_SIGNING_KEY_HANDLE?.trim(),
|
|
18
18
|
};
|
|
19
|
-
const manifestPath =
|
|
20
|
-
const
|
|
19
|
+
const manifestPath = runtimeReferenceManifestPath(input);
|
|
20
|
+
const legacyManifestPath = join(dirname(credentialsPath({ configHome: input.configHome })), "runtime-references.json");
|
|
21
|
+
const gateway = input.setupPlanId ? await optionalJson(join(input.repository, ".witnora", "gateway", "gateway.json")) : undefined;
|
|
22
|
+
const worker = object(gateway?.runtimeWorker);
|
|
23
|
+
const gatewayReferences = gateway?.projectId === input.projectId && gateway.server === input.server && worker.runtimeSetupPlanId === input.setupPlanId
|
|
24
|
+
? completeReferences({ ...worker, setupPlanId: worker.runtimeSetupPlanId, bootstrapId: worker.runtimeBootstrapId,
|
|
25
|
+
adapterDigestSha256: worker.adapterModuleSha256, probeDigestSha256: worker.probeModuleSha256, fixtureContractDigestSha256: worker.fixtureContractSha256 })
|
|
26
|
+
: undefined;
|
|
27
|
+
const manifest = await optionalJson(manifestPath) ?? (gatewayReferences
|
|
28
|
+
? { schemaVersion: "witnora.runtime_references.v0.1", projectId: input.projectId, server: input.server, references: gatewayReferences }
|
|
29
|
+
: await optionalJson(legacyManifestPath));
|
|
21
30
|
const manifestReferences = manifest?.schemaVersion === "witnora.runtime_references.v0.1" && manifest.projectId === input.projectId && manifest.server === input.server
|
|
22
31
|
? completeReferences(manifest.references)
|
|
23
32
|
: undefined;
|
|
33
|
+
const matchingReferences = !input.setupPlanId || !manifestReferences?.probeApiKeyId || manifestReferences.setupPlanId === input.setupPlanId
|
|
34
|
+
? manifestReferences : undefined;
|
|
24
35
|
const environmentOverrides = Object.fromEntries(Object.entries(entries).filter(([, value]) => value));
|
|
25
|
-
const configuredReferences = completeReferences({ ...
|
|
36
|
+
const configuredReferences = completeReferences({ ...matchingReferences, ...environmentOverrides });
|
|
26
37
|
if (configuredReferences) {
|
|
27
|
-
if (Object.keys(environmentOverrides).length > 0)
|
|
38
|
+
if (input.setupPlanId || Object.keys(environmentOverrides).length > 0)
|
|
28
39
|
await saveRuntimeReferenceManifest(manifestPath, input.projectId, input.server, configuredReferences);
|
|
29
40
|
return { references: configuredReferences, manifestPath };
|
|
30
41
|
}
|
|
@@ -48,6 +59,13 @@ export async function discoverRuntimeReferences(input) {
|
|
|
48
59
|
? `Local sandbox Runtime references are incomplete (${present.join(", ")} present). Existing Gateway setup remains RECORDED_ONLY.`
|
|
49
60
|
: "No existing private Witnora Runtime reference manifest was found. Gateway setup remains RECORDED_ONLY until Hosted bootstrap can issue one-time Runtime references." };
|
|
50
61
|
}
|
|
62
|
+
export function runtimeReferenceManifestPath(input) {
|
|
63
|
+
const root = dirname(credentialsPath({ configHome: input.configHome }));
|
|
64
|
+
if (!input.setupPlanId)
|
|
65
|
+
return join(root, "runtime-references.json");
|
|
66
|
+
const bindingId = createHash("sha256").update(JSON.stringify([normalizeServer(input.server), input.projectId, input.setupPlanId])).digest("hex");
|
|
67
|
+
return join(root, "runtime-bootstrap", bindingId, "runtime-references.json");
|
|
68
|
+
}
|
|
51
69
|
function completeReferences(value) {
|
|
52
70
|
const mandateId = text(value.mandateId);
|
|
53
71
|
const runtimeIdentityId = text(value.runtimeIdentityId);
|