witnora 0.16.0 → 0.18.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/gateway.js CHANGED
@@ -635,8 +635,13 @@ export async function createConfiguredWorkflowHarness(input) {
635
635
  let closing = false;
636
636
  let lastTickAt;
637
637
  let lastError;
638
+ let lastProviderHealthAt = 0;
638
639
  const tick = async () => {
639
640
  try {
641
+ if (managedEvaluator?.healthCheck && Date.now() - lastProviderHealthAt >= 30_000) {
642
+ lastProviderHealthAt = Date.now();
643
+ await managedEvaluator.healthCheck();
644
+ }
640
645
  const result = await worker.tick();
641
646
  lastTickAt = new Date().toISOString();
642
647
  lastError = (result.evaluationsFailed ?? 0) > 0 ? result.limitations?.[0] ?? "One or more evaluations failed closed." : undefined;
@@ -699,10 +704,11 @@ async function startManagedLocalEvaluator(directory, config, evaluatorKit) {
699
704
  if (typeof imported.createWitnoraBusinessTaskEvaluatorOptions !== "function") {
700
705
  throw new Error("Managed evaluator module did not export createWitnoraBusinessTaskEvaluatorOptions().");
701
706
  }
702
- const customerOptions = await imported.createWitnoraBusinessTaskEvaluatorOptions({
707
+ const evaluatorContext = {
703
708
  repository,
704
709
  dataDirectory: join(directory, "data", "business-task-evaluator"),
705
- });
710
+ };
711
+ const customerOptions = await imported.createWitnoraBusinessTaskEvaluatorOptions(evaluatorContext);
706
712
  if (!customerOptions || typeof customerOptions !== "object" || Array.isArray(customerOptions)) {
707
713
  throw new Error("Managed evaluator module returned invalid Evaluator Kit options.");
708
714
  }
@@ -715,7 +721,7 @@ async function startManagedLocalEvaluator(directory, config, evaluatorKit) {
715
721
  port: 0,
716
722
  });
717
723
  const started = await evaluator.start();
718
- return { origin: started.origin, credential, close: () => evaluator.close() };
724
+ return { origin: started.origin, credential, ...(imported.checkWitnoraProviderHealth ? { healthCheck: async () => { await imported.checkWitnoraProviderHealth(evaluatorContext); } } : {}), close: () => evaluator.close() };
719
725
  }
720
726
  async function runtimeSandboxFixtureReady(config) {
721
727
  try {
@@ -12,23 +12,22 @@ 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) => plan.generated.providerPackId === "STRIPE_REFUND" && plan.environment === "sandbox" && plan.customerSummary.evaluationMode === "SHADOW");
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");
16
16
  if (!plans.length)
17
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.");
18
+ const environment = options.env ?? process.env;
21
19
  const activations = [];
22
20
  const scenarios = [];
23
21
  for (const plan of plans) {
24
- const preflight = await stripePreflight(request, secret, plan, options.now?.() ?? new Date());
22
+ const preflight = await providerPreflight(request, environment, plan, options.now?.() ?? new Date(), options.postgresClientFactory);
25
23
  const acceptance = preflight.acceptance;
24
+ await persistProviderCredential(repository, plan, environment);
26
25
  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
26
  scenarios.push({ plan, value: [preflight.scenario] });
28
27
  }
29
28
  const modulePath = join(repository, "witnora.assurance-harness.mjs");
30
29
  const manifestPath = join(repository, ".witnora", "gateway", "real-path-activations.json");
31
- const source = generatedStripeHarness(plans);
30
+ const source = generatedProviderHarness(plans);
32
31
  const moduleDigestSha256 = sha(source);
33
32
  let created = false;
34
33
  const generatedFiles = [];
@@ -65,6 +64,23 @@ export async function activateRealPathIntegrations(options) {
65
64
  }
66
65
  return { state: "READY_TO_START", created, modulePath: "witnora.assurance-harness.mjs", activations, generatedFiles };
67
66
  }
67
+ async function providerPreflight(request, env, plan, now, postgresClientFactory) {
68
+ if (plan.generated.providerPackId === "STRIPE_REFUND") {
69
+ const secret = env.STRIPE_SECRET_KEY;
70
+ if (!secret?.startsWith("sk_test_") || secret.length < 12)
71
+ throw new Error("Stripe test-mode activation requires STRIPE_SECRET_KEY to reference an sk_test_ credential in the customer environment.");
72
+ return stripePreflight(request, secret, plan, now);
73
+ }
74
+ if (plan.generated.providerPackId === "ZENDESK_TICKET")
75
+ return zendeskPreflight(request, env, plan, now);
76
+ if (plan.generated.providerPackId === "SALESFORCE_RECORD")
77
+ return salesforcePreflight(request, env, plan, now);
78
+ if (plan.generated.providerPackId === "HUBSPOT_CRM_RECORD")
79
+ return hubspotPreflight(request, env, plan, now);
80
+ if (plan.generated.providerPackId === "POSTGRES_RECORD")
81
+ return postgresPreflight(env, plan, now, postgresClientFactory);
82
+ throw new Error(`${plan.generated.providerPackId} activation is not implemented by this CLI version.`);
83
+ }
68
84
  async function stripePreflight(request, secret, plan, now) {
69
85
  const response = await request("https://api.stripe.com/v1/refunds?limit=1", { headers: { authorization: `Bearer ${secret}` }, redirect: "error", signal: AbortSignal.timeout(5_000) });
70
86
  if (!response.ok)
@@ -82,9 +98,141 @@ async function stripePreflight(request, secret, plan, now) {
82
98
  const resultDigest = sha(canonical(observation));
83
99
  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
100
  }
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`;
101
+ async function zendeskPreflight(request, env, plan, now) {
102
+ const token = env.ZENDESK_API_TOKEN;
103
+ if (!token || token.length < 8)
104
+ throw new Error("Zendesk activation requires the read-only ZENDESK_API_TOKEN in the customer environment.");
105
+ const origin = providerOrigin(plan, "Zendesk", ".zendesk.com");
106
+ const response = await request(`${origin}/api/v2/tickets.json?per_page=1&sort_by=updated_at&sort_order=desc`, { method: "GET", headers: { accept: "application/json", authorization: `Bearer ${token}` }, redirect: "error", signal: AbortSignal.timeout(5_000) });
107
+ if (!response.ok)
108
+ throw new Error(`Zendesk read-only preflight failed (${response.status}).`);
109
+ const body = await boundedJson(response);
110
+ const tickets = Array.isArray(body.tickets) ? body.tickets : [];
111
+ const first = record(tickets[0], "Zendesk ticket");
112
+ const id = first.id;
113
+ if (typeof id !== "number" && typeof id !== "string")
114
+ throw new Error("Zendesk preflight requires one existing sandbox ticket.");
115
+ const observation = pick(first, ["status", "priority", "type", "via.channel"]);
116
+ assertCriterion(plan, observation);
117
+ return preflightResult(plan, now, "zendesk", id, observation);
118
+ }
119
+ async function salesforcePreflight(request, env, plan, now) {
120
+ const token = env.SALESFORCE_ACCESS_TOKEN;
121
+ if (!token || token.length < 8)
122
+ throw new Error("Salesforce activation requires the read-only SALESFORCE_ACCESS_TOKEN in the customer environment.");
123
+ const origin = providerOrigin(plan, "Salesforce", ".my.salesforce.com");
124
+ const resourceType = providerIdentifier(plan.generated.providerConfiguration?.resourceType, "Salesforce record type");
125
+ const field = providerIdentifier(plan.generated.criterion.field, "Salesforce criterion field");
126
+ const query = `SELECT Id,${field} FROM ${resourceType} ORDER BY LastModifiedDate DESC LIMIT 1`;
127
+ const response = await request(`${origin}/services/data/v61.0/query?${new URLSearchParams({ q: query })}`, { method: "GET", headers: { accept: "application/json", authorization: `Bearer ${token}` }, redirect: "error", signal: AbortSignal.timeout(5_000) });
128
+ if (!response.ok)
129
+ throw new Error(`Salesforce read-only preflight failed (${response.status}).`);
130
+ const body = await boundedJson(response);
131
+ const records = Array.isArray(body.records) ? body.records : [];
132
+ const first = record(records[0], "Salesforce record");
133
+ const id = first.Id;
134
+ if (typeof id !== "string")
135
+ throw new Error("Salesforce preflight requires one existing sandbox record.");
136
+ const observation = pick(first, [field]);
137
+ assertCriterion(plan, observation);
138
+ return preflightResult(plan, now, "salesforce", id, observation);
139
+ }
140
+ async function hubspotPreflight(request, env, plan, now) {
141
+ const token = env.HUBSPOT_ACCESS_TOKEN;
142
+ if (!token || token.length < 8)
143
+ throw new Error("HubSpot activation requires the read-only HUBSPOT_ACCESS_TOKEN in the customer environment.");
144
+ const resourceType = providerIdentifier(plan.generated.providerConfiguration?.resourceType, "HubSpot record type");
145
+ const field = providerIdentifier(plan.generated.criterion.field, "HubSpot criterion field");
146
+ const url = `https://api.hubapi.com/crm/v3/objects/${encodeURIComponent(resourceType)}?${new URLSearchParams({ limit: "1", properties: field })}`;
147
+ const response = await request(url, { method: "GET", headers: { accept: "application/json", authorization: `Bearer ${token}` }, redirect: "error", signal: AbortSignal.timeout(5_000) });
148
+ if (!response.ok)
149
+ throw new Error(`HubSpot read-only preflight failed (${response.status}).`);
150
+ const body = await boundedJson(response);
151
+ const results = Array.isArray(body.results) ? body.results : [];
152
+ const first = record(results[0], "HubSpot record");
153
+ const id = first.id;
154
+ if (typeof id !== "string")
155
+ throw new Error("HubSpot preflight requires one existing sandbox record.");
156
+ const properties = record(first.properties, "HubSpot properties");
157
+ const observation = pick(properties, [field]);
158
+ assertCriterion(plan, observation);
159
+ return preflightResult(plan, now, "hubspot", id, observation);
160
+ }
161
+ async function postgresPreflight(env, plan, now, clientFactory) {
162
+ const connectionString = env.WITNORA_POSTGRES_READ_URL;
163
+ if (!connectionString)
164
+ throw new Error("PostgreSQL activation requires WITNORA_POSTGRES_READ_URL in the customer environment.");
165
+ const view = providerIdentifier(plan.generated.providerConfiguration?.viewName, "PostgreSQL approved view");
166
+ const idColumn = providerIdentifier(plan.generated.providerConfiguration?.idColumn, "PostgreSQL record ID column");
167
+ const field = providerIdentifier(plan.generated.criterion.field, "PostgreSQL criterion field");
168
+ const client = clientFactory ? clientFactory(connectionString) : await defaultPostgresClient(connectionString);
169
+ await client.connect();
170
+ let first;
171
+ try {
172
+ await client.query("BEGIN READ ONLY");
173
+ const result = await client.query({ text: `SELECT "${idColumn}", "${field}" FROM "${view}" ORDER BY "${idColumn}" DESC LIMIT 1` });
174
+ first = result.rows[0];
175
+ await client.query("ROLLBACK");
176
+ }
177
+ catch (error) {
178
+ await client.query("ROLLBACK").catch(() => undefined);
179
+ throw error;
180
+ }
181
+ finally {
182
+ await client.end();
183
+ }
184
+ if (!first)
185
+ throw new Error("PostgreSQL preflight requires one existing record in the approved read-only view.");
186
+ const id = first[idColumn];
187
+ if (typeof id !== "string" && typeof id !== "number")
188
+ throw new Error("PostgreSQL preflight returned an invalid record ID.");
189
+ const observation = pick(first, [field]);
190
+ assertCriterion(plan, observation);
191
+ return preflightResult(plan, now, "postgres", id, observation);
192
+ }
193
+ async function defaultPostgresClient(connectionString) { const imported = await import("pg"); return new imported.Client({ connectionString, application_name: "witnora-read-only-preflight" }); }
194
+ function generatedProviderHarness(plans) {
195
+ const contracts = plans.map((plan) => ({ taskContractId: plan.taskContractId, providerPackId: plan.generated.providerPackId, providerConfiguration: plan.generated.providerConfiguration ?? {}, credentialHandle: `.witnora/provider-credentials/${plan.id}.secret`, criterion: plan.generated.criterion, actionPathIds: plan.generated.actionPathBindings.map((item) => item.actionPathId) }));
196
+ return `import {createHash} from "node:crypto";
197
+ import {readFile} from "node:fs/promises";
198
+ import {join} from "node:path";
199
+ const contracts=${JSON.stringify(contracts)};
200
+ const sha=(value)=>createHash("sha256").update(typeof value==="string"?value:JSON.stringify(value)).digest("hex");
201
+ 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.");};
203
+ export function createWitnoraBusinessTaskEvaluatorOptions(context){return {
204
+ 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
+ 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}]};}
206
+ };}
207
+ export async function checkWitnoraProviderHealth(context){for(const contract of contracts){const path=join(context.repository,".witnora","scenarios",contract.taskContractId+".json");const scenarios=JSON.parse(await readFile(path,"utf8"));const resourceId=String(scenarios?.[0]?.input?.resourceId??"");if(!/^[A-Za-z0-9._:-]{1,200}$/.test(resourceId))throw new Error("Provider health scenario is unavailable.");const raw=await observe(contract,resourceId,context.repository);if(raw?.[contract.criterion.field]===undefined)throw new Error("The approved Provider result field is no longer observable.");}return {ready:true,checked:contracts.length};}
208
+ `;
209
+ }
210
+ async function persistProviderCredential(repository, plan, environment) {
211
+ const name = providerCredentialEnvironmentName(plan.generated.providerPackId);
212
+ const value = environment[name];
213
+ if (!value)
214
+ throw new Error(`${plan.generated.providerPackId} activation requires its read-only credential in the customer environment.`);
215
+ const directory = join(repository, ".witnora", "provider-credentials");
216
+ const target = join(directory, `${plan.id}.secret`);
217
+ const temporary = `${target}.${randomUUID()}.tmp`;
218
+ await mkdir(directory, { recursive: true });
219
+ await writeFile(join(directory, ".gitignore"), "*\n!.gitignore\n", { encoding: "utf8", mode: 0o600, flag: "wx" }).catch((error) => { if (error.code !== "EEXIST")
220
+ throw error; });
221
+ await writeFile(temporary, value, { encoding: "utf8", mode: 0o600, flag: "wx" });
222
+ await rename(temporary, target);
223
+ }
224
+ function providerCredentialEnvironmentName(packId) {
225
+ if (packId === "STRIPE_REFUND")
226
+ return "STRIPE_SECRET_KEY";
227
+ if (packId === "ZENDESK_TICKET")
228
+ return "ZENDESK_API_TOKEN";
229
+ if (packId === "SALESFORCE_RECORD")
230
+ return "SALESFORCE_ACCESS_TOKEN";
231
+ if (packId === "HUBSPOT_CRM_RECORD")
232
+ return "HUBSPOT_ACCESS_TOKEN";
233
+ if (packId === "POSTGRES_RECORD")
234
+ return "WITNORA_POSTGRES_READ_URL";
235
+ throw new Error(`Provider credential mapping is not supported for ${packId}.`);
88
236
  }
89
237
  function parsePlan(value) { if (!value || typeof value !== "object" || Array.isArray(value))
90
238
  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))
@@ -93,6 +241,23 @@ async function boundedJson(response) { const text = await response.text(); if (t
93
241
  throw new Error("Real-path response exceeded the size limit."); const value = JSON.parse(text); if (!value || typeof value !== "object" || Array.isArray(value))
94
242
  throw new Error("Real-path response is invalid."); return value; }
95
243
  function scalar(value) { return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean"; }
244
+ function providerOrigin(plan, provider, suffix) { const value = plan.generated.providerConfiguration?.origin; try {
245
+ const url = new URL(value ?? "");
246
+ if (url.protocol !== "https:" || !url.hostname.endsWith(suffix) || url.origin !== (value ?? "").replace(/\/$/, ""))
247
+ throw new Error();
248
+ return url.origin;
249
+ }
250
+ catch {
251
+ throw new Error(`${provider} integration has an invalid trusted origin.`);
252
+ } }
253
+ function providerIdentifier(value, field) { if (!value || !/^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(value))
254
+ throw new Error(`${field} is invalid.`); return value; }
255
+ function record(value, field) { if (!value || typeof value !== "object" || Array.isArray(value))
256
+ throw new Error(`${field} response is invalid.`); return value; }
257
+ function pick(value, fields) { return Object.fromEntries(fields.flatMap((field) => { const selected = field.split(".").reduce((current, key) => current && typeof current === "object" ? current[key] : undefined, value); return scalar(selected) ? [[field, selected]] : []; })); }
258
+ function assertCriterion(plan, observation) { if (observation[plan.generated.criterion.field] !== plan.generated.criterion.expected)
259
+ throw new Error("The provider sandbox record does not satisfy the approved business success definition."); }
260
+ function preflightResult(plan, now, provider, resourceId, observation) { const resultDigest = sha(canonical(observation)); return { acceptance: { kind: "READ_ONLY_PROVIDER_PREFLIGHT", passedAt: now.toISOString(), productionWrites: 0, observationDigestSha256: resultDigest }, scenario: { id: `${provider}:${sha(String(resourceId)).slice(0, 16)}`, source: "LIVE_SHADOW", sanitized: true, input: { resourceId }, inputDigestSha256: sha(canonical({ resourceId })), baseline: { resultDigestSha256: resultDigest, actionIntents: plan.generated.actionPathBindings.map((item) => ({ pathId: item.actionPathId, parametersDigestSha256: sha(String(resourceId)) })) } } }; }
96
261
  function digest(value) { return typeof value === "string" && /^[a-f0-9]{64}$/.test(value); }
97
262
  function canonical(value) { if (value === null || typeof value !== "object")
98
263
  return JSON.stringify(value); if (Array.isArray(value))
@@ -40,6 +40,7 @@ type HttpPackOptions = {
40
40
  credentialHandle: string;
41
41
  resolveCredential: HttpProductionEvaluatorOptions["resolveCredential"];
42
42
  allowedOrigin?: string;
43
+ resourceType?: string;
43
44
  fetch?: typeof fetch;
44
45
  timeoutMs?: number;
45
46
  now?: () => Date;
@@ -1 +1 @@
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
+ {"version":3,"file":"provider-integration-packs.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/provider-integration-packs.ts"],"names":[],"mappings":"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"}
@@ -26,7 +26,8 @@ export function createProviderPackEvaluator(options) {
26
26
  const spec = getProviderIntegrationPack(options.packId);
27
27
  if (!spec.environments.includes(options.environment))
28
28
  throw new Error("Provider pack does not support this environment.");
29
- const select = selector(spec.fieldAllowlist);
29
+ const selectFields = selector(spec.fieldAllowlist);
30
+ const select = (value) => selectFields(providerPayload(options.packId, value));
30
31
  const common = { provider: spec.provider, environment: options.environment, credentialHandle: options.credentialHandle, resolveCredential: options.resolveCredential, select, timeoutMs: options.timeoutMs, now: options.now };
31
32
  if (options.packId === "POSTGRES_RECORD" || options.packId === "MYSQL_RECORD")
32
33
  return createDatabaseReadOnlyEvaluator({ ...common, statementId: options.statementId, executePrepared: options.executePrepared });
@@ -36,7 +37,8 @@ export function createProviderPackEvaluator(options) {
36
37
  const origin = http.allowedOrigin ?? spec.endpoint.origin;
37
38
  if (!origin)
38
39
  throw new Error("Provider pack requires the customer-specific HTTPS origin discovered during local setup.");
39
- const resourcePath = resourcePathBuilder(spec.endpoint.resourcePattern);
40
+ validateProviderOrigin(options.packId, origin);
41
+ const resourcePath = resourcePathBuilder(options.packId, spec.endpoint.resourcePattern, providerResourceType(options.packId, http.resourceType), spec.fieldAllowlist);
40
42
  const input = { ...common, allowedOrigin: origin, resourcePath, fetch: http.fetch };
41
43
  switch (spec.template) {
42
44
  case "CRM_RECORD_STATE": return createCrmRecordStateEvaluator(input);
@@ -71,6 +73,38 @@ function criterion(id, question, field, suggestedValues) { return { id, question
71
73
  function selector(fields) {
72
74
  return (value) => Object.fromEntries(fields.flatMap((field) => { const selected = field.split(".").reduce((current, key) => current && typeof current === "object" ? current[key] : undefined, value); return selected === undefined ? [] : [[field, selected]]; }));
73
75
  }
74
- function resourcePathBuilder(pattern) {
75
- return (resourceId) => pattern.replace("{resourceId}", encodeURIComponent(resourceId)).replace("{object}", "records");
76
+ function resourcePathBuilder(packId, pattern, resourceType, fields) {
77
+ return (resourceId) => {
78
+ const path = pattern.replace("{resourceId}", encodeURIComponent(resourceId)).replace("{object}", encodeURIComponent(resourceType));
79
+ return packId === "HUBSPOT_CRM_RECORD" ? `${path}?${new URLSearchParams({ properties: fields.join(",") })}` : path;
80
+ };
81
+ }
82
+ function providerPayload(packId, value) {
83
+ if (packId === "ZENDESK_TICKET")
84
+ return nestedRecord(value, "ticket", "Zendesk ticket response");
85
+ if (packId === "HUBSPOT_CRM_RECORD")
86
+ return nestedRecord(value, "properties", "HubSpot record response");
87
+ return value;
88
+ }
89
+ function validateProviderOrigin(packId, value) {
90
+ const url = new URL(value);
91
+ if (packId === "ZENDESK_TICKET" && (url.protocol !== "https:" || !url.hostname.endsWith(".zendesk.com")))
92
+ throw new Error("Zendesk origin must be an HTTPS customer subdomain of zendesk.com.");
93
+ if (packId === "SALESFORCE_RECORD" && (url.protocol !== "https:" || !url.hostname.endsWith(".my.salesforce.com")))
94
+ throw new Error("Salesforce origin must be the customer's HTTPS my.salesforce.com domain.");
95
+ if (packId === "HUBSPOT_CRM_RECORD" && (url.protocol !== "https:" || url.hostname !== "api.hubapi.com"))
96
+ throw new Error("HubSpot origin must be the fixed HTTPS api.hubapi.com endpoint.");
97
+ }
98
+ function providerResourceType(packId, value) {
99
+ if (packId !== "SALESFORCE_RECORD" && packId !== "HUBSPOT_CRM_RECORD")
100
+ return "records";
101
+ if (!value || !/^[A-Za-z][A-Za-z0-9_]{0,99}$/.test(value))
102
+ throw new Error(`${packId === "SALESFORCE_RECORD" ? "Salesforce" : "HubSpot"} resource type is invalid.`);
103
+ return value;
104
+ }
105
+ function nestedRecord(value, key, field) {
106
+ const nested = value[key];
107
+ if (!nested || typeof nested !== "object" || Array.isArray(nested))
108
+ throw new Error(`${field} is missing.`);
109
+ return nested;
76
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Independent assurance for covered agent action paths across models and frameworks.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",