witnora 0.18.11 → 0.18.12

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/gateway.js CHANGED
@@ -61,6 +61,7 @@ export async function initializeCustomerGateway(options) {
61
61
  port: gatewayPort(process.env.WITNORA_GATEWAY_PORT, 8787),
62
62
  storageDirectory: "data",
63
63
  privacyMode: "metadata_only",
64
+ ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}),
64
65
  coverage: runtimeKit
65
66
  ? { recorded: "configured", enforced: "configured", outcomeVerified: "configured" }
66
67
  : { recorded: "configured", enforced: "not_configured", outcomeVerified: "not_configured" },
@@ -136,14 +137,20 @@ export async function upgradeCustomerGatewayRuntime(options) {
136
137
  if (current.projectId !== options.authorization.projectId || current.server !== options.authorization.server)
137
138
  throw new Error("Existing Gateway project/server binding does not match this authorized Runtime upgrade.");
138
139
  const generatedClient = clientRaw === gatewayClient(current)
139
- || (!current.runtimeWorker && clientRaw === recordedGatewayClient(current));
140
+ || (!current.runtimeWorker && (clientRaw === recordedGatewayClient(current)
141
+ || clientRaw === recordedGatewayClient({ ...current, agentIdentity: undefined })));
140
142
  if (!generatedClient || readmeRaw !== gatewayReadme(current))
141
143
  throw new Error("Existing Gateway files differ from the Witnora-generated version; refusing to overwrite them.");
142
144
  if (current.runtimeWorker && current.runtimeWorker.adapterId !== LOCAL_SANDBOX_ADAPTER_ID) {
143
145
  throw new Error("Existing Gateway already has a different Runtime worker; preserve it and reconfigure in Advanced mode.");
144
146
  }
145
147
  const runtimeKit = await prepareRuntimeSandboxKit(options.runtimeReferences, options.authorization, options.configHome, options.fetch ?? fetch, directory);
146
- const next = { ...current, coverage: { recorded: "configured", enforced: "configured", outcomeVerified: "configured" }, runtimeWorker: runtimeKit.config };
148
+ const next = {
149
+ ...current,
150
+ ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}),
151
+ coverage: { recorded: "configured", enforced: "configured", outcomeVerified: "configured" },
152
+ runtimeWorker: runtimeKit.config,
153
+ };
147
154
  next.runtimeWorker.configDigestSha256 = gatewayConfigDigest(next);
148
155
  const replacingGeneratedRuntime = current.runtimeWorker?.adapterId === LOCAL_SANDBOX_ADAPTER_ID;
149
156
  if (replacingGeneratedRuntime) {
@@ -155,7 +162,8 @@ export async function upgradeCustomerGatewayRuntime(options) {
155
162
  || createHash("sha256").update(probeBytes).digest("hex") !== current.runtimeWorker.probeModuleSha256) {
156
163
  throw new Error("Existing generated Runtime modules were modified; refusing to overwrite them.");
157
164
  }
158
- if (sameRuntimeGeneration(current.runtimeWorker, next.runtimeWorker)) {
165
+ if (sameRuntimeGeneration(current.runtimeWorker, next.runtimeWorker)
166
+ && JSON.stringify(current.agentIdentity) === JSON.stringify(next.agentIdentity)) {
159
167
  return { config: current, generatedFiles: [], changed: false, rollback: async () => undefined };
160
168
  }
161
169
  }
@@ -1358,6 +1366,8 @@ function parseConfig(raw) {
1358
1366
  }
1359
1367
  if (value.privacyMode !== "metadata_only")
1360
1368
  throw new Error("Gateway privacyMode must remain metadata_only.");
1369
+ if (value.agentIdentity)
1370
+ validateGatewayAgentIdentity(value.agentIdentity);
1361
1371
  if (!value.host || !Number.isSafeInteger(value.port) || Number(value.port) < 1 || Number(value.port) > 65_535)
1362
1372
  throw new Error("Gateway host and port are invalid.");
1363
1373
  if (value.runtimeWorker)
@@ -1368,6 +1378,13 @@ function parseConfig(raw) {
1368
1378
  }
1369
1379
  return config;
1370
1380
  }
1381
+ function validateGatewayAgentIdentity(value) {
1382
+ for (const [key, item] of Object.entries(value)) {
1383
+ if (typeof item !== "string" || item.trim().length === 0 || item.length > 200) {
1384
+ throw new Error(`Gateway agentIdentity.${key} must be a non-empty string no longer than 200 characters.`);
1385
+ }
1386
+ }
1387
+ }
1371
1388
  export function parseManagedWorkflowHarnessConfig(raw) {
1372
1389
  const value = JSON.parse(raw);
1373
1390
  const workflowIds = value.workflowIds;
@@ -1652,7 +1669,11 @@ async function ed25519PublicKeyPem(handle) {
1652
1669
  return createPublicKey(key).export({ type: "spki", format: "pem" }).toString();
1653
1670
  }
1654
1671
  function recordedGatewayClient(config) {
1655
- return `import { readFile } from "node:fs/promises";\n\nconst baseUrl = "http://${config.host}:${config.port}";\nlet gatewayToken;\n\nasync function token() {\n if (gatewayToken) return gatewayToken;\n const secrets = JSON.parse(await readFile(new URL("./secrets.json", import.meta.url), "utf8"));\n if (typeof secrets.gatewayToken !== "string" || secrets.gatewayToken.length < 32) {\n throw new Error("Witnora local Gateway token is missing or invalid.");\n }\n gatewayToken = secrets.gatewayToken;\n return gatewayToken;\n}\n\nasync function post(runId, operation, body) {\n if (!/^[A-Za-z0-9._:-]+$/.test(runId)) throw new Error("Witnora runId contains unsupported characters.");\n const response = await fetch(\`\${baseUrl}/v1/runs/\${encodeURIComponent(runId)}/\${operation}\`, {\n method: "POST",\n headers: { authorization: \`Bearer \${await token()}\`, "content-type": "application/json" },\n body: JSON.stringify(body),\n });\n const result = await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(result.error ?? \`Witnora Gateway returned HTTP \${response.status}.\`);\n return result;\n}\n\nexport const witnoraGateway = {\n start(runId, metadata = {}) {\n return post(runId, "start", { payload: metadata, idempotencyKey: "run-start" });\n },\n event(runId, type, metadata = {}, idempotencyKey = \`\${type}-\${crypto.randomUUID()}\`) {\n return post(runId, "events", { type, payload: metadata, idempotencyKey });\n },\n complete(runId, metadata = {}) {\n return post(runId, "complete", {\n payload: metadata,\n evidenceStrength: {\n schemaVersion: "agentcert.evidence_strength.v0.1",\n level: "recorded",\n claims: [],\n limitations: ["No write-credential mediation or independent outcome probe is configured."],\n },\n idempotencyKey: "run-complete",\n });\n },\n};\n`;
1672
+ const defaultAgent = config.agentIdentity ? JSON.stringify(config.agentIdentity) : undefined;
1673
+ const startPayload = defaultAgent
1674
+ ? `const payload = { ...metadata, agent: metadata.agent ?? ${defaultAgent} };\n return post(runId, "start", { payload, idempotencyKey: "run-start" });`
1675
+ : `return post(runId, "start", { payload: metadata, idempotencyKey: "run-start" });`;
1676
+ return `import { readFile } from "node:fs/promises";\n\nconst baseUrl = "http://${config.host}:${config.port}";\nlet gatewayToken;\n\nasync function token() {\n if (gatewayToken) return gatewayToken;\n const secrets = JSON.parse(await readFile(new URL("./secrets.json", import.meta.url), "utf8"));\n if (typeof secrets.gatewayToken !== "string" || secrets.gatewayToken.length < 32) {\n throw new Error("Witnora local Gateway token is missing or invalid.");\n }\n gatewayToken = secrets.gatewayToken;\n return gatewayToken;\n}\n\nasync function post(runId, operation, body) {\n if (!/^[A-Za-z0-9._:-]+$/.test(runId)) throw new Error("Witnora runId contains unsupported characters.");\n const response = await fetch(\`\${baseUrl}/v1/runs/\${encodeURIComponent(runId)}/\${operation}\`, {\n method: "POST",\n headers: { authorization: \`Bearer \${await token()}\`, "content-type": "application/json" },\n body: JSON.stringify(body),\n });\n const result = await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(result.error ?? \`Witnora Gateway returned HTTP \${response.status}.\`);\n return result;\n}\n\nexport const witnoraGateway = {\n start(runId, metadata = {}) {\n ${startPayload}\n },\n event(runId, type, metadata = {}, idempotencyKey = \`\${type}-\${crypto.randomUUID()}\`) {\n return post(runId, "events", { type, payload: metadata, idempotencyKey });\n },\n complete(runId, metadata = {}) {\n return post(runId, "complete", {\n payload: metadata,\n evidenceStrength: {\n schemaVersion: "agentcert.evidence_strength.v0.1",\n level: "recorded",\n claims: [],\n limitations: ["No write-credential mediation or independent outcome probe is configured."],\n },\n idempotencyKey: "run-complete",\n });\n },\n};\n`;
1656
1677
  }
1657
1678
  async function writeExclusive(path, content, force, mode) {
1658
1679
  if (!force && await exists(path))
package/dist/onboard.js CHANGED
@@ -19,6 +19,12 @@ export async function runOnboard(options) {
19
19
  const server = normalizeServer(options.server ?? DEFAULT_WITNORA_SERVER);
20
20
  const repositoryPath = resolve(options.repository ?? process.cwd());
21
21
  const repository = await inspectRepository(repositoryPath, options.template);
22
+ const agentIdentity = {
23
+ externalId: repository.name,
24
+ name: humanizeAgentName(repository.name),
25
+ version: repository.version,
26
+ framework: frameworkForTemplate(repository.template),
27
+ };
22
28
  const connectionName = options.name ?? repository.slug;
23
29
  const token = await authorizeProjectConnection({
24
30
  projectId: options.projectId,
@@ -95,6 +101,24 @@ export async function runOnboard(options) {
95
101
  };
96
102
  let assuranceHarnessChanged = false;
97
103
  let continuousService;
104
+ const prepareRuntime = async (operation) => {
105
+ const delays = [250, 500, 1_000, 2_000, 4_000, 8_000];
106
+ let lastError;
107
+ for (let attempt = 0; attempt <= delays.length; attempt += 1) {
108
+ try {
109
+ return await operation();
110
+ }
111
+ catch (error) {
112
+ if (!(error instanceof RuntimeSetupNotReadyError))
113
+ throw error;
114
+ lastError = error;
115
+ if (attempt === delays.length)
116
+ break;
117
+ await (options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))))(delays[attempt]);
118
+ }
119
+ }
120
+ throw lastError;
121
+ };
98
122
  await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, { status: "installing", attemptId });
99
123
  try {
100
124
  generatedFiles.push(...await generateRepositoryConfig(repositoryPath, repository.template, repository.name));
@@ -137,11 +161,12 @@ export async function runOnboard(options) {
137
161
  if (gatewayState.status === "absent") {
138
162
  let gateway;
139
163
  try {
140
- gateway = await initializeCustomerGateway({
164
+ gateway = await prepareRuntime(() => initializeCustomerGateway({
141
165
  projectId: token.projectId, server, repository: repositoryPath, authorization: token,
142
166
  runtimeReferences: localRuntime.references,
167
+ agentIdentity,
143
168
  fetch: requestFetch, configHome: options.configHome, output,
144
- });
169
+ }));
145
170
  }
146
171
  catch (error) {
147
172
  if (!(error instanceof RuntimeSetupNotReadyError) || !localRuntime.references)
@@ -157,7 +182,7 @@ export async function runOnboard(options) {
157
182
  await saveConnection(binding.connectionName, { server, projectId: token.projectId, apiKey: token.apiKey }, { configHome: options.configHome });
158
183
  if (localRuntime.references) {
159
184
  try {
160
- runtimeUpgrade = await upgradeCustomerGatewayRuntime({ repository: repositoryPath, authorization: token, runtimeReferences: localRuntime.references, configHome: options.configHome, fetch: requestFetch });
185
+ runtimeUpgrade = await prepareRuntime(() => upgradeCustomerGatewayRuntime({ repository: repositoryPath, authorization: token, runtimeReferences: localRuntime.references, agentIdentity, configHome: options.configHome, fetch: requestFetch }));
161
186
  if (runtimeUpgrade.changed) {
162
187
  const stopped = await gatewayLifecycle.stop({ repository: repositoryPath, configHome: options.configHome, fetch: requestFetch });
163
188
  if (stopped.state !== "STOPPED" || stopped.healthy)
@@ -187,11 +212,12 @@ export async function runOnboard(options) {
187
212
  await assertGatewayStopped(requestFetch, binding.host, binding.port, binding.projectId, token.projectId);
188
213
  gatewayMigration = await archiveGateway(gatewayState.directory, repositoryPath, binding.projectId);
189
214
  output(`\nExisting Gateway belongs to project ${binding.projectId}; archived it at ${gatewayMigration.archiveDirectory}.\n`);
190
- const gateway = await initializeCustomerGateway({
215
+ const gateway = await prepareRuntime(() => initializeCustomerGateway({
191
216
  projectId: token.projectId, server, repository: repositoryPath, authorization: token,
192
217
  runtimeReferences: localRuntime.references,
218
+ agentIdentity,
193
219
  fetch: requestFetch, configHome: options.configHome, output,
194
- });
220
+ }));
195
221
  generatedFiles.push(...gateway.generatedFiles);
196
222
  }
197
223
  }
@@ -252,7 +278,7 @@ export async function runOnboard(options) {
252
278
  : { state: "RECORDED_ONLY", checkedAt: new Date().toISOString(), limitations: [runtimeLimitation ?? "The local sandbox Runtime worker has not established exact adapter, probe, source-key, identity, mandate, and readiness bindings."] };
253
279
  await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, {
254
280
  status: "verified", attemptId, generatedFiles: relativeGeneratedFiles(repositoryPath, generatedFiles), runtimeReadiness,
255
- connectedAgent: { externalId: repository.name, name: humanizeAgentName(repository.name), version: repository.version, framework: frameworkForTemplate(repository.template) },
281
+ connectedAgent: agentIdentity,
256
282
  ...(runtimeBinding ? { runtimeBinding } : {}),
257
283
  });
258
284
  output(`\nWitnora Setup Autopilot completed for ${repository.name}.\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.18.11",
3
+ "version": "0.18.12",
4
4
  "description": "Independent assurance for covered agent action paths across models and frameworks.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",