witnora 0.18.1 → 0.18.3
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-service.js +8 -2
- package/dist/gateway.js +66 -0
- package/dist/onboard.js +6 -2
- package/dist/private-discovery.js +38 -4
- package/dist/real-path-activation.js +26 -2
- package/dist/vendor/onegent-runtime/production-evaluator-kit.d.ts +2 -1
- package/dist/vendor/onegent-runtime/production-evaluator-kit.d.ts.map +1 -1
- package/dist/vendor/onegent-runtime/production-evaluator-kit.js +1 -0
- package/dist/vendor/onegent-runtime/provider-integration-packs.d.ts +1 -1
- package/dist/vendor/onegent-runtime/provider-integration-packs.d.ts.map +1 -1
- package/dist/vendor/onegent-runtime/provider-integration-packs.js +7 -1
- package/package.json +1 -1
package/dist/gateway-service.js
CHANGED
|
@@ -18,7 +18,13 @@ export async function installGatewayService(input) {
|
|
|
18
18
|
export async function installCurrentGatewayService(options) {
|
|
19
19
|
const planInput = currentPlanInput(options);
|
|
20
20
|
return installGatewayService({ ...planInput,
|
|
21
|
-
writeDefinition: async (path, value) => {
|
|
21
|
+
writeDefinition: async (path, value) => {
|
|
22
|
+
await mkdir(dirname(path), { recursive: true });
|
|
23
|
+
const contents = planInput.platform === "win32"
|
|
24
|
+
? Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(value, "utf16le")])
|
|
25
|
+
: Buffer.from(value, "utf8");
|
|
26
|
+
await writeFile(path, contents, { mode: 0o600 });
|
|
27
|
+
},
|
|
22
28
|
run: options.run ?? runCommand,
|
|
23
29
|
});
|
|
24
30
|
}
|
|
@@ -53,7 +59,7 @@ export function createGatewayServicePlan(input) {
|
|
|
53
59
|
function windowsPlan(input, id, args) {
|
|
54
60
|
const definitionPath = `${input.serviceHome}\\${id}.xml`;
|
|
55
61
|
const argumentsValue = args.map(windowsArgument).join(" ");
|
|
56
|
-
const definition = `<?xml version="1.0" encoding="UTF-
|
|
62
|
+
const definition = `<?xml version="1.0" encoding="UTF-16"?>
|
|
57
63
|
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
58
64
|
<Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${xml(input.userId)}</UserId></LogonTrigger></Triggers>
|
|
59
65
|
<Principals><Principal id="Author"><UserId>${xml(input.userId)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
|
package/dist/gateway.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash, createPrivateKey, createPublicKey, randomBytes } from "node
|
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { closeSync, openSync } from "node:fs";
|
|
4
4
|
import { access, chmod, mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createServer as createNetServer } from "node:net";
|
|
5
6
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
6
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
8
|
import { loadConnection } from "./credentials.js";
|
|
@@ -196,6 +197,44 @@ export async function upgradeCustomerGatewayRuntime(options) {
|
|
|
196
197
|
}
|
|
197
198
|
return { config: next, generatedFiles: [configPath, clientPath, readmePath, ...created, ...(replacingGeneratedRuntime ? [join(directory, runtimeKit.config.adapterModulePath), join(directory, runtimeKit.config.probeModulePath)] : [])], changed: true, rollback: restore };
|
|
198
199
|
}
|
|
200
|
+
export async function ensureCustomerGatewayPortAvailable(options) {
|
|
201
|
+
const directory = resolve(options.repository, options.dir ?? ".witnora/gateway");
|
|
202
|
+
const configPath = join(directory, "gateway.json");
|
|
203
|
+
const clientPath = join(directory, "client.mjs");
|
|
204
|
+
const readmePath = join(directory, "README.md");
|
|
205
|
+
const [configRaw, clientRaw, readmeRaw] = await Promise.all([
|
|
206
|
+
readFile(configPath, "utf8"), readFile(clientPath, "utf8"), readFile(readmePath, "utf8"),
|
|
207
|
+
]);
|
|
208
|
+
const current = parseConfig(configRaw);
|
|
209
|
+
const health = await localCollector(current.host, current.port, options.fetch ?? fetch);
|
|
210
|
+
if (health === current.collectorId || (!health && await canListen(current.host, current.port))) {
|
|
211
|
+
return { changed: false, port: current.port };
|
|
212
|
+
}
|
|
213
|
+
if (clientRaw !== gatewayClient(current) || readmeRaw !== gatewayReadme(current)) {
|
|
214
|
+
throw new Error(`Gateway port ${current.port} is occupied, and generated Gateway files were modified; refusing to rewrite customer files.`);
|
|
215
|
+
}
|
|
216
|
+
const excluded = new Set([current.port]);
|
|
217
|
+
if (current.runtimeWorker?.sandboxOrigin)
|
|
218
|
+
excluded.add(Number(new URL(current.runtimeWorker.sandboxOrigin).port));
|
|
219
|
+
const port = await nextAvailablePort(current.host, current.port, excluded);
|
|
220
|
+
const next = { ...current, port };
|
|
221
|
+
if (next.runtimeWorker)
|
|
222
|
+
next.runtimeWorker = { ...next.runtimeWorker, configDigestSha256: undefined };
|
|
223
|
+
if (next.runtimeWorker)
|
|
224
|
+
next.runtimeWorker.configDigestSha256 = gatewayConfigDigest(next);
|
|
225
|
+
try {
|
|
226
|
+
await atomicWrite(configPath, `${JSON.stringify(next, null, 2)}\n`, 0o644);
|
|
227
|
+
await atomicWrite(clientPath, gatewayClient(next), 0o644);
|
|
228
|
+
await atomicWrite(readmePath, gatewayReadme(next), 0o644);
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
await Promise.all([
|
|
232
|
+
atomicWrite(configPath, configRaw, 0o644), atomicWrite(clientPath, clientRaw, 0o644), atomicWrite(readmePath, readmeRaw, 0o644),
|
|
233
|
+
]).catch(() => undefined);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
return { changed: true, port };
|
|
237
|
+
}
|
|
199
238
|
function sameRuntimeGeneration(left, right) {
|
|
200
239
|
return JSON.stringify({ ...left, configDigestSha256: undefined }) === JSON.stringify({ ...right, configDigestSha256: undefined });
|
|
201
240
|
}
|
|
@@ -1412,6 +1451,33 @@ async function atomicWrite(path, content, mode) {
|
|
|
1412
1451
|
await rename(temporary, path);
|
|
1413
1452
|
await chmod(path, mode).catch(() => undefined);
|
|
1414
1453
|
}
|
|
1454
|
+
async function localCollector(host, port, requestFetch) {
|
|
1455
|
+
try {
|
|
1456
|
+
const response = await requestFetch(`http://${host}:${port}/healthz`, { signal: AbortSignal.timeout(750) });
|
|
1457
|
+
if (!response.ok)
|
|
1458
|
+
return "occupied";
|
|
1459
|
+
const value = await response.json();
|
|
1460
|
+
return typeof value.collectorId === "string" ? value.collectorId : "occupied";
|
|
1461
|
+
}
|
|
1462
|
+
catch {
|
|
1463
|
+
return undefined;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
async function canListen(host, port) {
|
|
1467
|
+
const server = createNetServer();
|
|
1468
|
+
return new Promise((resolvePromise) => {
|
|
1469
|
+
server.once("error", () => resolvePromise(false));
|
|
1470
|
+
server.listen(port, host, () => server.close(() => resolvePromise(true)));
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
async function nextAvailablePort(host, preferred, excluded) {
|
|
1474
|
+
for (let offset = 1; offset <= 1_000; offset += 1) {
|
|
1475
|
+
const candidate = 1_024 + ((preferred - 1_024 + offset) % (65_535 - 1_024));
|
|
1476
|
+
if (!excluded.has(candidate) && await canListen(host, candidate))
|
|
1477
|
+
return candidate;
|
|
1478
|
+
}
|
|
1479
|
+
throw new Error("No available localhost port was found for the customer-owned Gateway.");
|
|
1480
|
+
}
|
|
1415
1481
|
async function exists(path) {
|
|
1416
1482
|
try {
|
|
1417
1483
|
await access(path);
|
package/dist/onboard.js
CHANGED
|
@@ -8,7 +8,7 @@ import { verifyControlPlaneConnection } from "./control-plane.js";
|
|
|
8
8
|
import { inferPrivateCapabilities, runPrivateCapabilityDiscovery } from "./private-discovery.js";
|
|
9
9
|
import { parseAgentTemplate, starterAdapter, starterProfile, starterTripwireConfig } from "./onboarding-templates.js";
|
|
10
10
|
import { writeTryEvidence } from "./try.js";
|
|
11
|
-
import { doctorCustomerGateway, activateManagedWorkflowHarness, initializeCustomerGateway, inspectLocalRuntimeBinding, inspectCustomerGatewayFiles, startManagedCustomerGateway, stopManagedCustomerGateway, upgradeCustomerGatewayRuntime, RuntimeSetupNotReadyError, } from "./gateway.js";
|
|
11
|
+
import { doctorCustomerGateway, activateManagedWorkflowHarness, ensureCustomerGatewayPortAvailable, initializeCustomerGateway, inspectLocalRuntimeBinding, inspectCustomerGatewayFiles, startManagedCustomerGateway, stopManagedCustomerGateway, upgradeCustomerGatewayRuntime, RuntimeSetupNotReadyError, } from "./gateway.js";
|
|
12
12
|
import { discoverRuntimeReferences, planRuntimeSandboxModules } from "./runtime-sandbox-kit.js";
|
|
13
13
|
import { automaticRuntimeReferencesUsable, bootstrapLocalRuntime, verifyAutomaticRuntimeProbe } from "./runtime-bootstrap.js";
|
|
14
14
|
import { activateRealPathIntegrations } from "./real-path-activation.js";
|
|
@@ -217,6 +217,9 @@ export async function runOnboard(options) {
|
|
|
217
217
|
throw new Error("The prior managed Gateway process did not stop before Assurance Harness activation.");
|
|
218
218
|
}
|
|
219
219
|
if (!options.gatewayLifecycle) {
|
|
220
|
+
const port = await ensureCustomerGatewayPortAvailable({ repository: repositoryPath });
|
|
221
|
+
if (port.changed)
|
|
222
|
+
output(`Rebound this repository's generated Gateway to available localhost port ${port.port}; another project remains untouched.\n`);
|
|
220
223
|
const installed = await installCurrentGatewayService({ repository: repositoryPath, cliEntry: fileURLToPath(new URL("./cli.js", import.meta.url)) });
|
|
221
224
|
continuousService = { state: "INSTALLED", kind: installed.plan.kind, id: installed.plan.id };
|
|
222
225
|
}
|
|
@@ -390,7 +393,8 @@ export async function inspectRepository(repositoryPath, explicitTemplate) {
|
|
|
390
393
|
...Object.keys(record(packageJson.devDependencies)),
|
|
391
394
|
...Object.keys(record(packageJson.peerDependencies)),
|
|
392
395
|
] : [];
|
|
393
|
-
const
|
|
396
|
+
const manifest = await optionalJson(join(repositoryPath, "witnora.discovery.json"));
|
|
397
|
+
const capabilities = inferPrivateCapabilities({ dependencyNames, topLevelNames: names, manifest });
|
|
394
398
|
return { kind, name, slug, template, fingerprintSha256, capabilities };
|
|
395
399
|
}
|
|
396
400
|
async function generateRepositoryConfig(repositoryPath, template, subject) {
|
|
@@ -31,7 +31,7 @@ export async function runPrivateCapabilityDiscovery(input) {
|
|
|
31
31
|
localRedactionApplied: true,
|
|
32
32
|
},
|
|
33
33
|
capabilities: input.repository.capabilities,
|
|
34
|
-
unknownCapabilityCount: input.repository.capabilities.length,
|
|
34
|
+
unknownCapabilityCount: input.repository.capabilities.filter((item) => item.disposition === "pending").length,
|
|
35
35
|
};
|
|
36
36
|
const payloadSha256 = sha256(canonicalJson(payload));
|
|
37
37
|
const privateKey = createPrivateKey(identity.privateKeyPem);
|
|
@@ -62,12 +62,13 @@ export async function runPrivateCapabilityDiscovery(input) {
|
|
|
62
62
|
export function inferPrivateCapabilities(input) {
|
|
63
63
|
const searchable = [...input.dependencyNames, ...input.topLevelNames].join(" ").toLowerCase();
|
|
64
64
|
const detected = new Map();
|
|
65
|
-
const add = (key, observedName, transport) => detected.set(key, {
|
|
65
|
+
const add = (key, observedName, transport, capabilityId, disposition = "pending") => detected.set(key, {
|
|
66
66
|
key,
|
|
67
67
|
observedName,
|
|
68
68
|
transport,
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
...(capabilityId ? { capabilityId } : {}),
|
|
70
|
+
disposition,
|
|
71
|
+
schemaSha256: sha256(canonicalJson({ key, observedName, transport, capabilityId, disposition, source: "repository_metadata" })),
|
|
71
72
|
source: "repository_metadata",
|
|
72
73
|
});
|
|
73
74
|
add("workflow:orchestration", "Workflow orchestration", "workflow");
|
|
@@ -83,8 +84,41 @@ export function inferPrivateCapabilities(input) {
|
|
|
83
84
|
add("data:structured", "Structured data access", "data");
|
|
84
85
|
if (/slack|discord|twilio|resend|sendgrid|postmark|nodemailer|email/.test(searchable))
|
|
85
86
|
add("messaging:external", "External messaging", "messaging");
|
|
87
|
+
for (const capability of repositoryManifestCapabilities(input.manifest))
|
|
88
|
+
add(capability.key, capability.observedName, capability.transport, capability.capabilityId, capability.disposition);
|
|
86
89
|
return [...detected.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
87
90
|
}
|
|
91
|
+
function repositoryManifestCapabilities(value) {
|
|
92
|
+
if (value === undefined)
|
|
93
|
+
return [];
|
|
94
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
95
|
+
throw new Error("Repository discovery manifest is invalid.");
|
|
96
|
+
const manifest = value;
|
|
97
|
+
if (Object.keys(manifest).some((key) => key !== "schemaVersion" && key !== "capabilities") || manifest.schemaVersion !== "witnora.repository_discovery.v0.1" || !Array.isArray(manifest.capabilities) || manifest.capabilities.length > 100) {
|
|
98
|
+
throw new Error("Repository discovery manifest failed its bounded contract.");
|
|
99
|
+
}
|
|
100
|
+
const transports = new Set(["tool", "mcp", "http", "browser", "coding", "workflow", "data", "messaging"]);
|
|
101
|
+
return manifest.capabilities.map((item, index) => {
|
|
102
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
103
|
+
throw new Error(`Repository discovery manifest capability ${index} is invalid.`);
|
|
104
|
+
const capability = item;
|
|
105
|
+
if (Object.keys(capability).some((key) => !["key", "observedName", "transport", "capabilityId", "disposition"].includes(key))
|
|
106
|
+
|| typeof capability.key !== "string" || !/^[A-Za-z0-9._:-]{1,120}$/.test(capability.key)
|
|
107
|
+
|| typeof capability.observedName !== "string" || !/^[A-Za-z0-9._:-]{1,120}$/.test(capability.observedName)
|
|
108
|
+
|| !transports.has(capability.transport)
|
|
109
|
+
|| capability.capabilityId !== undefined && (typeof capability.capabilityId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(capability.capabilityId))
|
|
110
|
+
|| capability.disposition !== undefined && !["allowed", "approval_required", "denied", "pending"].includes(String(capability.disposition))) {
|
|
111
|
+
throw new Error(`Repository discovery manifest capability ${index} failed its bounded contract.`);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
key: capability.key,
|
|
115
|
+
observedName: capability.observedName,
|
|
116
|
+
transport: capability.transport,
|
|
117
|
+
...(capability.capabilityId ? { capabilityId: capability.capabilityId } : {}),
|
|
118
|
+
disposition: capability.disposition ?? "pending",
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
}
|
|
88
122
|
function discoveryIdentityPath(projectId, configHome) {
|
|
89
123
|
const root = configHome ?? process.env.WITNORA_CONFIG_HOME ?? process.env.AGENTCERT_CONFIG_HOME
|
|
90
124
|
?? join(process.env.USERPROFILE ?? process.env.HOME ?? ".", ".witnora");
|
|
@@ -12,7 +12,7 @@ export async function activateRealPathIntegrations(options) {
|
|
|
12
12
|
throw new Error(`Could not load approved real-path integrations (${response.status}).`);
|
|
13
13
|
const body = await boundedJson(response);
|
|
14
14
|
const approved = Array.isArray(body.integrations) ? body.integrations.map(parsePlan).filter((plan) => plan.status === "READY_TO_ACTIVATE") : [];
|
|
15
|
-
const plans = approved.filter((plan) => ["STRIPE_REFUND", "ZENDESK_TICKET", "SALESFORCE_RECORD", "HUBSPOT_CRM_RECORD", "POSTGRES_RECORD"].includes(plan.generated.providerPackId) && plan.environment === "sandbox" && plan.customerSummary.evaluationMode === "SHADOW");
|
|
15
|
+
const plans = approved.filter((plan) => ["STRIPE_REFUND", "SHOPIFY_DISPUTE", "ZENDESK_TICKET", "SALESFORCE_RECORD", "HUBSPOT_CRM_RECORD", "POSTGRES_RECORD"].includes(plan.generated.providerPackId) && plan.environment === "sandbox" && plan.customerSummary.evaluationMode === "SHADOW");
|
|
16
16
|
if (!plans.length)
|
|
17
17
|
return { state: "NOT_CONFIGURED", created: false, activations: [], generatedFiles: [] };
|
|
18
18
|
const environment = options.env ?? process.env;
|
|
@@ -71,6 +71,8 @@ async function providerPreflight(request, env, plan, now, postgresClientFactory)
|
|
|
71
71
|
throw new Error("Stripe test-mode activation requires STRIPE_SECRET_KEY to reference an sk_test_ credential in the customer environment.");
|
|
72
72
|
return stripePreflight(request, secret, plan, now);
|
|
73
73
|
}
|
|
74
|
+
if (plan.generated.providerPackId === "SHOPIFY_DISPUTE")
|
|
75
|
+
return shopifyDisputePreflight(request, env, plan, now);
|
|
74
76
|
if (plan.generated.providerPackId === "ZENDESK_TICKET")
|
|
75
77
|
return zendeskPreflight(request, env, plan, now);
|
|
76
78
|
if (plan.generated.providerPackId === "SALESFORCE_RECORD")
|
|
@@ -81,6 +83,26 @@ async function providerPreflight(request, env, plan, now, postgresClientFactory)
|
|
|
81
83
|
return postgresPreflight(env, plan, now, postgresClientFactory);
|
|
82
84
|
throw new Error(`${plan.generated.providerPackId} activation is not implemented by this CLI version.`);
|
|
83
85
|
}
|
|
86
|
+
async function shopifyDisputePreflight(request, env, plan, now) {
|
|
87
|
+
const token = env.SHOPIFY_READ_ACCESS_TOKEN;
|
|
88
|
+
if (!token || token.length < 8)
|
|
89
|
+
throw new Error("Shopify activation requires the read-only SHOPIFY_READ_ACCESS_TOKEN in the customer environment.");
|
|
90
|
+
const origin = providerOrigin(plan, "Shopify", ".myshopify.com");
|
|
91
|
+
if (!/^[a-z0-9][a-z0-9-]*\.myshopify\.com$/.test(new URL(origin).hostname))
|
|
92
|
+
throw new Error("Shopify integration requires the exact myshopify.com shop origin.");
|
|
93
|
+
const response = await request(`${origin}/admin/api/2025-07/shopify_payments/disputes.json?limit=1`, { method: "GET", headers: { accept: "application/json", "x-shopify-access-token": token }, redirect: "error", signal: AbortSignal.timeout(5_000) });
|
|
94
|
+
if (!response.ok)
|
|
95
|
+
throw new Error(`Shopify read-only dispute preflight failed (${response.status}).`);
|
|
96
|
+
const body = await boundedJson(response);
|
|
97
|
+
const disputes = Array.isArray(body.disputes) ? body.disputes : [];
|
|
98
|
+
const first = record(disputes[0], "Shopify dispute");
|
|
99
|
+
const id = first.id;
|
|
100
|
+
if (typeof id !== "string" && !Number.isInteger(id))
|
|
101
|
+
throw new Error("Shopify preflight requires one existing sandbox dispute.");
|
|
102
|
+
const observation = pick(first, ["status", "type", "amount", "currency", "evidence_due_by"]);
|
|
103
|
+
assertCriterion(plan, observation);
|
|
104
|
+
return preflightResult(plan, now, "shopify", id, observation);
|
|
105
|
+
}
|
|
84
106
|
async function stripePreflight(request, secret, plan, now) {
|
|
85
107
|
const response = await request("https://api.stripe.com/v1/refunds?limit=1", { headers: { authorization: `Bearer ${secret}` }, redirect: "error", signal: AbortSignal.timeout(5_000) });
|
|
86
108
|
if (!response.ok)
|
|
@@ -199,7 +221,7 @@ import {join} from "node:path";
|
|
|
199
221
|
const contracts=${JSON.stringify(contracts)};
|
|
200
222
|
const sha=(value)=>createHash("sha256").update(typeof value==="string"?value:JSON.stringify(value)).digest("hex");
|
|
201
223
|
const request=async(url,key)=>{const response=await fetch(url,{method:"GET",headers:{accept:"application/json",authorization:"Bearer "+key},redirect:"error",signal:AbortSignal.timeout(5000)});if(!response.ok)throw new Error("Provider read-only observation failed ("+response.status+").");return response.json();};
|
|
202
|
-
const observe=async(contract,resourceId,repository)=>{const key=(await readFile(join(repository,contract.credentialHandle),"utf8")).trim();if(!key)throw new Error("The local read-only Provider credential is unavailable.");if(contract.providerPackId==="STRIPE_REFUND"){if(!key.startsWith("sk_test_"))throw new Error("Stripe test-mode credential is unavailable.");return request("https://api.stripe.com/v1/refunds/"+encodeURIComponent(resourceId),key);}if(contract.providerPackId==="ZENDESK_TICKET")return (await request(contract.providerConfiguration.origin+"/api/v2/tickets/"+encodeURIComponent(resourceId)+".json",key)).ticket;if(contract.providerPackId==="SALESFORCE_RECORD")return request(contract.providerConfiguration.origin+"/services/data/v61.0/sobjects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId),key);if(contract.providerPackId==="HUBSPOT_CRM_RECORD")return (await request("https://api.hubapi.com/crm/v3/objects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId)+"?properties="+encodeURIComponent(contract.criterion.field),key)).properties;if(contract.providerPackId==="POSTGRES_RECORD"){for(const name of [contract.providerConfiguration.viewName,contract.providerConfiguration.idColumn,contract.criterion.field])if(!/^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(name))throw new Error("PostgreSQL identifier is invalid.");const {Client}=await import("pg");const client=new Client({connectionString:key,application_name:"witnora-read-only-probe"});await client.connect();try{await client.query("BEGIN READ ONLY");const result=await client.query({name:"witnora-provider-observe",text:'SELECT "'+contract.criterion.field+'" FROM "'+contract.providerConfiguration.viewName+'" WHERE "'+contract.providerConfiguration.idColumn+'" = $1 LIMIT 1',values:[resourceId]});await client.query("ROLLBACK");return result.rows[0]??{};}catch(error){await client.query("ROLLBACK").catch(()=>{});throw error;}finally{await client.end();}}throw new Error("Provider Harness contract is unsupported.");};
|
|
224
|
+
const observe=async(contract,resourceId,repository)=>{const key=(await readFile(join(repository,contract.credentialHandle),"utf8")).trim();if(!key)throw new Error("The local read-only Provider credential is unavailable.");if(contract.providerPackId==="STRIPE_REFUND"){if(!key.startsWith("sk_test_"))throw new Error("Stripe test-mode credential is unavailable.");return request("https://api.stripe.com/v1/refunds/"+encodeURIComponent(resourceId),key);}if(contract.providerPackId==="SHOPIFY_DISPUTE"){const response=await fetch(contract.providerConfiguration.origin+"/admin/api/2025-07/shopify_payments/disputes/"+encodeURIComponent(resourceId)+".json",{method:"GET",headers:{accept:"application/json","x-shopify-access-token":key},redirect:"error",signal:AbortSignal.timeout(5000)});if(!response.ok)throw new Error("Provider read-only observation failed ("+response.status+").");return (await response.json()).dispute;}if(contract.providerPackId==="ZENDESK_TICKET")return (await request(contract.providerConfiguration.origin+"/api/v2/tickets/"+encodeURIComponent(resourceId)+".json",key)).ticket;if(contract.providerPackId==="SALESFORCE_RECORD")return request(contract.providerConfiguration.origin+"/services/data/v61.0/sobjects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId),key);if(contract.providerPackId==="HUBSPOT_CRM_RECORD")return (await request("https://api.hubapi.com/crm/v3/objects/"+encodeURIComponent(contract.providerConfiguration.resourceType)+"/"+encodeURIComponent(resourceId)+"?properties="+encodeURIComponent(contract.criterion.field),key)).properties;if(contract.providerPackId==="POSTGRES_RECORD"){for(const name of [contract.providerConfiguration.viewName,contract.providerConfiguration.idColumn,contract.criterion.field])if(!/^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(name))throw new Error("PostgreSQL identifier is invalid.");const {Client}=await import("pg");const client=new Client({connectionString:key,application_name:"witnora-read-only-probe"});await client.connect();try{await client.query("BEGIN READ ONLY");const result=await client.query({name:"witnora-provider-observe",text:'SELECT "'+contract.criterion.field+'" FROM "'+contract.providerConfiguration.viewName+'" WHERE "'+contract.providerConfiguration.idColumn+'" = $1 LIMIT 1',values:[resourceId]});await client.query("ROLLBACK");return result.rows[0]??{};}catch(error){await client.query("ROLLBACK").catch(()=>{});throw error;}finally{await client.end();}}throw new Error("Provider Harness contract is unsupported.");};
|
|
203
225
|
export function createWitnoraBusinessTaskEvaluatorOptions(context){return {
|
|
204
226
|
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;},
|
|
205
227
|
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(!/^[A-Za-z0-9._:-]{1,200}$/.test(resourceId))throw new Error("Provider resourceId is invalid.");for(const pathId of contract.actionPathIds)candidate.propose({pathId,parametersDigestSha256:sha(resourceId)});const raw=await observe(contract,resourceId,context.repository);const actual=raw?.[contract.criterion.field];const observed=actual===undefined?{}:{[contract.criterion.field]:actual};return {resultDigestSha256:sha(observed),criteria:[{id:contract.criterion.id,passed:actual===contract.criterion.expected}]};}
|
|
@@ -224,6 +246,8 @@ async function persistProviderCredential(repository, plan, environment) {
|
|
|
224
246
|
function providerCredentialEnvironmentName(packId) {
|
|
225
247
|
if (packId === "STRIPE_REFUND")
|
|
226
248
|
return "STRIPE_SECRET_KEY";
|
|
249
|
+
if (packId === "SHOPIFY_DISPUTE")
|
|
250
|
+
return "SHOPIFY_READ_ACCESS_TOKEN";
|
|
227
251
|
if (packId === "ZENDESK_TICKET")
|
|
228
252
|
return "ZENDESK_API_TOKEN";
|
|
229
253
|
if (packId === "SALESFORCE_RECORD")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const PRODUCTION_EVALUATOR_OBSERVATION_VERSION: "witnora.production_evaluator_observation.v0.1";
|
|
2
|
-
export type ProductionEvaluatorTemplate = "CRM_RECORD_STATE" | "TICKET_STATUS" | "EMAIL_DELIVERY" | "DATABASE_READ_ONLY" | "WEBHOOK_DELIVERY" | "QUEUE_JOB_COMPLETION" | "PAYMENT_REFUND_RESULT" | "BROWSER_WORKFLOW_OUTCOME";
|
|
2
|
+
export type ProductionEvaluatorTemplate = "CRM_RECORD_STATE" | "TICKET_STATUS" | "EMAIL_DELIVERY" | "DATABASE_READ_ONLY" | "WEBHOOK_DELIVERY" | "QUEUE_JOB_COMPLETION" | "PAYMENT_REFUND_RESULT" | "DISPUTE_STATUS" | "BROWSER_WORKFLOW_OUTCOME";
|
|
3
3
|
type SafeScalar = string | number | boolean | null;
|
|
4
4
|
type SafeFields = Record<string, SafeScalar>;
|
|
5
5
|
export interface ProductionEvaluatorRequest {
|
|
@@ -79,6 +79,7 @@ export declare const createEmailDeliveryEvaluator: (options: HttpProductionEvalu
|
|
|
79
79
|
export declare const createWebhookDeliveryEvaluator: (options: HttpProductionEvaluatorOptions) => ProductionOutcomeEvaluator;
|
|
80
80
|
export declare const createQueueJobCompletionEvaluator: (options: HttpProductionEvaluatorOptions) => ProductionOutcomeEvaluator;
|
|
81
81
|
export declare const createPaymentRefundResultEvaluator: (options: HttpProductionEvaluatorOptions) => ProductionOutcomeEvaluator;
|
|
82
|
+
export declare const createDisputeStatusEvaluator: (options: HttpProductionEvaluatorOptions) => ProductionOutcomeEvaluator;
|
|
82
83
|
export declare function createDatabaseReadOnlyEvaluator(options: DatabaseReadOnlyEvaluatorOptions): ProductionOutcomeEvaluator;
|
|
83
84
|
export declare function createBrowserWorkflowOutcomeEvaluator(options: BrowserWorkflowOutcomeEvaluatorOptions): ProductionOutcomeEvaluator;
|
|
84
85
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"production-evaluator-kit.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/production-evaluator-kit.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,wCAAwC,EAAG,+CAAwD,CAAC;AACjH,MAAM,MAAM,2BAA2B,GAAG,kBAAkB,GAAG,eAAe,GAAG,gBAAgB,GAAG,oBAAoB,GAAG,kBAAkB,GAAG,sBAAsB,GAAG,uBAAuB,GAAG,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"production-evaluator-kit.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/production-evaluator-kit.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ export const createEmailDeliveryEvaluator = (options) => createHttpEvaluator("EM
|
|
|
8
8
|
export const createWebhookDeliveryEvaluator = (options) => createHttpEvaluator("WEBHOOK_DELIVERY", options);
|
|
9
9
|
export const createQueueJobCompletionEvaluator = (options) => createHttpEvaluator("QUEUE_JOB_COMPLETION", options);
|
|
10
10
|
export const createPaymentRefundResultEvaluator = (options) => createHttpEvaluator("PAYMENT_REFUND_RESULT", options);
|
|
11
|
+
export const createDisputeStatusEvaluator = (options) => createHttpEvaluator("DISPUTE_STATUS", options);
|
|
11
12
|
export function createDatabaseReadOnlyEvaluator(options) {
|
|
12
13
|
validateCommon(options);
|
|
13
14
|
identifier(options.statementId, "statementId");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type BrowserWorkflowOutcomeEvaluatorOptions, type DatabaseReadOnlyEvaluatorOptions, type HttpProductionEvaluatorOptions, type ProductionOutcomeEvaluator, type ProductionEvaluatorTemplate } from "./production-evaluator-kit.js";
|
|
2
2
|
export declare const PROVIDER_INTEGRATION_PACK_SCHEMA_VERSION: "witnora.provider_integration_pack.v0.1";
|
|
3
|
-
export type ProviderIntegrationPackId = "STRIPE_REFUND" | "SALESFORCE_RECORD" | "HUBSPOT_CRM_RECORD" | "ZENDESK_TICKET" | "INTERCOM_TICKET" | "POSTGRES_RECORD" | "MYSQL_RECORD" | "WEBHOOK_DELIVERY" | "QUEUE_JOB" | "EMAIL_DELIVERY" | "BROWSER_WORKFLOW";
|
|
3
|
+
export type ProviderIntegrationPackId = "STRIPE_REFUND" | "SHOPIFY_DISPUTE" | "SALESFORCE_RECORD" | "HUBSPOT_CRM_RECORD" | "ZENDESK_TICKET" | "INTERCOM_TICKET" | "POSTGRES_RECORD" | "MYSQL_RECORD" | "WEBHOOK_DELIVERY" | "QUEUE_JOB" | "EMAIL_DELIVERY" | "BROWSER_WORKFLOW";
|
|
4
4
|
export interface ProviderIntegrationPack {
|
|
5
5
|
schemaVersion: typeof PROVIDER_INTEGRATION_PACK_SCHEMA_VERSION;
|
|
6
6
|
id: ProviderIntegrationPackId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-integration-packs.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/provider-integration-packs.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"provider-integration-packs.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/provider-integration-packs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { createBrowserWorkflowOutcomeEvaluator, createCrmRecordStateEvaluator, createDatabaseReadOnlyEvaluator, createEmailDeliveryEvaluator, createPaymentRefundResultEvaluator, createQueueJobCompletionEvaluator, createTicketStatusEvaluator, createWebhookDeliveryEvaluator, } from "./production-evaluator-kit.js";
|
|
2
|
+
import { createBrowserWorkflowOutcomeEvaluator, createCrmRecordStateEvaluator, createDatabaseReadOnlyEvaluator, createDisputeStatusEvaluator, createEmailDeliveryEvaluator, createPaymentRefundResultEvaluator, createQueueJobCompletionEvaluator, createTicketStatusEvaluator, createWebhookDeliveryEvaluator, } from "./production-evaluator-kit.js";
|
|
3
3
|
import { canonicalJson } from "./trust-crypto.js";
|
|
4
4
|
export const PROVIDER_INTEGRATION_PACK_SCHEMA_VERSION = "witnora.provider_integration_pack.v0.1";
|
|
5
5
|
const BASE = [
|
|
6
6
|
pack("STRIPE_REFUND", "stripe", "Stripe refund result", "PAYMENT_REFUND_RESULT", ["sandbox", "production"], ["refunds:read"], "env://STRIPE_SECRET_KEY", ["status", "amount", "currency", "failure_reason"], "/v1/refunds/{resourceId}", "https://api.stripe.com", [criterion("refund-complete", "Which Stripe status means the refund completed?", "status", ["succeeded"])]),
|
|
7
|
+
pack("SHOPIFY_DISPUTE", "shopify", "Shopify Payments dispute status", "DISPUTE_STATUS", ["sandbox", "production"], ["shopify_payments_disputes:read"], "env://SHOPIFY_READ_ACCESS_TOKEN", ["status", "type", "amount", "currency", "evidence_due_by"], "/admin/api/2025-07/shopify_payments/disputes/{resourceId}.json", undefined, [criterion("dispute-decided", "Which Shopify dispute state establishes the final business result?", "status", ["won", "lost"])]),
|
|
7
8
|
pack("SALESFORCE_RECORD", "salesforce", "Salesforce record state", "CRM_RECORD_STATE", ["sandbox", "production"], ["API enabled with object and field read-only permission set"], "env://SALESFORCE_ACCESS_TOKEN", ["Status__c", "Refund_Status__c", "IsClosed", "StageName"], "/services/data/v61.0/sobjects/{object}/{resourceId}", undefined, [criterion("crm-state", "Which Salesforce field and value establish success?", "Status__c", ["Completed", "Approved"])]),
|
|
8
9
|
pack("HUBSPOT_CRM_RECORD", "hubspot", "HubSpot CRM record", "CRM_RECORD_STATE", ["sandbox", "production"], ["crm.objects.read"], "env://HUBSPOT_ACCESS_TOKEN", ["hs_pipeline_stage", "hs_status", "closedate", "amount"], "/crm/v3/objects/{object}/{resourceId}", "https://api.hubapi.com", [criterion("crm-state", "Which HubSpot property means the business task completed?", "hs_status", ["closed", "completed"])]),
|
|
9
10
|
pack("ZENDESK_TICKET", "zendesk", "Zendesk ticket status", "TICKET_STATUS", ["sandbox", "production"], ["tickets:read"], "env://ZENDESK_API_TOKEN", ["status", "priority", "type", "via.channel"], "/api/v2/tickets/{resourceId}.json", undefined, [criterion("ticket-complete", "Which Zendesk status means the ticket is complete?", "status", ["solved", "closed"])]),
|
|
@@ -47,6 +48,7 @@ export function createProviderPackEvaluator(options) {
|
|
|
47
48
|
case "WEBHOOK_DELIVERY": return createWebhookDeliveryEvaluator(input);
|
|
48
49
|
case "QUEUE_JOB_COMPLETION": return createQueueJobCompletionEvaluator(input);
|
|
49
50
|
case "PAYMENT_REFUND_RESULT": return createPaymentRefundResultEvaluator(input);
|
|
51
|
+
case "DISPUTE_STATUS": return createDisputeStatusEvaluator(input);
|
|
50
52
|
default: throw new Error("Provider pack evaluator template is unsupported.");
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -80,6 +82,8 @@ function resourcePathBuilder(packId, pattern, resourceType, fields) {
|
|
|
80
82
|
};
|
|
81
83
|
}
|
|
82
84
|
function providerPayload(packId, value) {
|
|
85
|
+
if (packId === "SHOPIFY_DISPUTE")
|
|
86
|
+
return nestedRecord(value, "dispute", "Shopify dispute response");
|
|
83
87
|
if (packId === "ZENDESK_TICKET")
|
|
84
88
|
return nestedRecord(value, "ticket", "Zendesk ticket response");
|
|
85
89
|
if (packId === "HUBSPOT_CRM_RECORD")
|
|
@@ -88,6 +92,8 @@ function providerPayload(packId, value) {
|
|
|
88
92
|
}
|
|
89
93
|
function validateProviderOrigin(packId, value) {
|
|
90
94
|
const url = new URL(value);
|
|
95
|
+
if (packId === "SHOPIFY_DISPUTE" && (url.protocol !== "https:" || !/^[a-z0-9][a-z0-9-]*\.myshopify\.com$/.test(url.hostname)))
|
|
96
|
+
throw new Error("Shopify origin must be the exact HTTPS myshopify.com shop domain.");
|
|
91
97
|
if (packId === "ZENDESK_TICKET" && (url.protocol !== "https:" || !url.hostname.endsWith(".zendesk.com")))
|
|
92
98
|
throw new Error("Zendesk origin must be an HTTPS customer subdomain of zendesk.com.");
|
|
93
99
|
if (packId === "SALESFORCE_RECORD" && (url.protocol !== "https:" || !url.hostname.endsWith(".my.salesforce.com")))
|