agentcert 0.2.7 → 0.4.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.
Files changed (42) hide show
  1. package/README.md +37 -0
  2. package/dist/cli.js +8 -0
  3. package/dist/command-help.js +2 -0
  4. package/dist/index.js +1 -0
  5. package/dist/sandbox.js +323 -0
  6. package/dist/vendor/onegent-runtime/mock-procurement.d.ts +6 -0
  7. package/dist/vendor/onegent-runtime/mock-procurement.d.ts.map +1 -0
  8. package/dist/vendor/onegent-runtime/mock-procurement.js +33 -0
  9. package/dist/vendor/onegent-runtime/policies.d.ts +6 -0
  10. package/dist/vendor/onegent-runtime/policies.d.ts.map +1 -0
  11. package/dist/vendor/onegent-runtime/policies.js +109 -0
  12. package/dist/vendor/onegent-runtime/risk.d.ts +3 -0
  13. package/dist/vendor/onegent-runtime/risk.d.ts.map +1 -0
  14. package/dist/vendor/onegent-runtime/risk.js +54 -0
  15. package/dist/vendor/onegent-runtime/sandbox-adapter-kit.d.ts +45 -0
  16. package/dist/vendor/onegent-runtime/sandbox-adapter-kit.d.ts.map +1 -0
  17. package/dist/vendor/onegent-runtime/sandbox-adapter-kit.js +148 -0
  18. package/dist/vendor/onegent-runtime/sandbox-harness.d.ts +167 -0
  19. package/dist/vendor/onegent-runtime/sandbox-harness.d.ts.map +1 -0
  20. package/dist/vendor/onegent-runtime/sandbox-harness.js +729 -0
  21. package/dist/vendor/onegent-runtime/sandbox-hosted.d.ts +77 -0
  22. package/dist/vendor/onegent-runtime/sandbox-hosted.d.ts.map +1 -0
  23. package/dist/vendor/onegent-runtime/sandbox-hosted.js +133 -0
  24. package/dist/vendor/onegent-runtime/sdk.d.ts +29 -0
  25. package/dist/vendor/onegent-runtime/sdk.d.ts.map +1 -0
  26. package/dist/vendor/onegent-runtime/sdk.js +140 -0
  27. package/dist/vendor/onegent-runtime/service.d.ts +27 -0
  28. package/dist/vendor/onegent-runtime/service.d.ts.map +1 -0
  29. package/dist/vendor/onegent-runtime/service.js +421 -0
  30. package/dist/vendor/onegent-runtime/store.d.ts +16 -0
  31. package/dist/vendor/onegent-runtime/store.d.ts.map +1 -0
  32. package/dist/vendor/onegent-runtime/store.js +29 -0
  33. package/dist/vendor/onegent-runtime/stripe-test-readonly.d.ts +79 -0
  34. package/dist/vendor/onegent-runtime/stripe-test-readonly.d.ts.map +1 -0
  35. package/dist/vendor/onegent-runtime/stripe-test-readonly.js +163 -0
  36. package/dist/vendor/onegent-runtime/types.d.ts +284 -0
  37. package/dist/vendor/onegent-runtime/types.d.ts.map +1 -0
  38. package/dist/vendor/onegent-runtime/types.js +1 -0
  39. package/dist/vendor/onegent-runtime/vendor-sandbox-egress.d.ts +49 -0
  40. package/dist/vendor/onegent-runtime/vendor-sandbox-egress.d.ts.map +1 -0
  41. package/dist/vendor/onegent-runtime/vendor-sandbox-egress.js +152 -0
  42. package/package.json +2 -2
package/README.md CHANGED
@@ -57,6 +57,43 @@ or mismatched artifacts remain observable as `partial` or `rejected`.
57
57
  Project API keys can create runs, record events, and upload evidence, but
58
58
  cannot approve their own runtime actions.
59
59
 
60
+ ## Sandbox onboarding
61
+
62
+ Create and certify a synthetic SandboxSystem adapter with the same public CLI:
63
+
64
+ ```bash
65
+ npx agentcert sandbox init
66
+ npx agentcert sandbox certify --adapter ./agentcert.sandbox.mjs
67
+ ```
68
+
69
+ The first command writes one dependency-free JavaScript file. The second runs
70
+ the bundled deterministic adapter contract and writes
71
+ `.agentcert/sandbox/sandbox-adapter-conformance.json`. No `@agentcert` scoped
72
+ package is required.
73
+
74
+ After `agentcert connect`, certify and upload in one command:
75
+
76
+ ```bash
77
+ npx agentcert sandbox push --adapter ./agentcert.sandbox.mjs
78
+ ```
79
+
80
+ Passing and failing reports are both retained for review, while failed
81
+ certifications return a non-zero exit status. The generated template is
82
+ synthetic and network-denied; it must not contain production credentials or
83
+ connect to live systems.
84
+
85
+ Run one bounded read against an existing Stripe sandbox PaymentIntent:
86
+
87
+ ```bash
88
+ STRIPE_RESTRICTED_TEST_KEY="rk_test_..." npx agentcert sandbox stripe-readonly --payment-intent pi_...
89
+ ```
90
+
91
+ Add `--push` to retain the redacted report in AgentCert Hosted. The command
92
+ allows only `https://api.stripe.com`, `GET`, and allowlisted PaymentIntent
93
+ routes, with a 5-second timeout and 10-request-per-minute process-local cap.
94
+ The Stripe key is environment-only and is never written to output or evidence.
95
+ See the [Bounded Vendor Sandbox Egress guide](https://github.com/Kakarottoooo/agentcert/blob/main/docs/bounded-vendor-sandbox-egress.md).
96
+
60
97
  Review/export helpers:
61
98
 
62
99
  ```bash
package/dist/cli.js CHANGED
@@ -22,6 +22,7 @@ import { parseSchemaId, validateAgentCertSchema } from "./schema-validator.js";
22
22
  import { applyRunOverrides, loadRunProfile, profileFromArtifactFlags, renderRunSummary, runAgentCertProfile, } from "./runner.js";
23
23
  import { buildRobustnessLabSnapshot, readRobustnessLabConfig, renderRobustnessLabSummary, writeRobustnessLabSnapshot } from "./lab.js";
24
24
  import { renderCommandHelp } from "./command-help.js";
25
+ import { runSandboxCommand } from "./sandbox.js";
25
26
  process.on("uncaughtException", reportFatalError);
26
27
  process.on("unhandledRejection", reportFatalError);
27
28
  const command = process.argv[2] ?? "help";
@@ -120,6 +121,10 @@ Saved connections are reused by agentcert push and agentcert run --push.
120
121
  process.stdout.write(`Credentials: ${path}\n`);
121
122
  }
122
123
  }
124
+ else if (command === "sandbox") {
125
+ const result = await runSandboxCommand(process.argv.slice(3));
126
+ process.exitCode = result.exitCode;
127
+ }
123
128
  else if (command === "report") {
124
129
  const config = await loadConfig(readFlag("--config"));
125
130
  const subject = readFlag("--subject") ?? config?.subject.name ?? "agentcert-subject";
@@ -546,6 +551,9 @@ else {
546
551
  process.stdout.write(`Usage:
547
552
  agentcert init --subject my-browser-agent
548
553
  agentcert connect --server https://agentcert-control-plane.onrender.com --project <project-id>
554
+ agentcert sandbox init
555
+ agentcert sandbox certify --adapter ./agentcert.sandbox.mjs
556
+ agentcert sandbox push --adapter ./agentcert.sandbox.mjs
549
557
  agentcert init --out agentcert.config.json --tripwire-config tripwire.yml --force
550
558
  agentcert init --subject my-browser-agent --github-action
551
559
  agentcert report --mcpbench .mcpbench/latest/results.json --tripwire .tripwire/latest/tripwire-result.json --onegent .onegent/procurement/audit-packet.json --out .agentcert/latest --subject my-agent
@@ -1,4 +1,6 @@
1
1
  export function renderCommandHelp(command) {
2
+ if (command === "sandbox")
3
+ return undefined;
2
4
  if (command === "conformance")
3
5
  return `Usage:
4
6
  agentcert conformance <evidence.json> --artifact-root <directory> [--implementation <name>] [--out <report.json>]
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export * from "./bundle.js";
2
2
  export * from "./corpus.js";
3
3
  export * from "./corpus-store.js";
4
4
  export * from "./control-plane.js";
5
+ export * from "./sandbox.js";
5
6
  export * from "./conformance.js";
6
7
  export * from "./credentials.js";
7
8
  export * from "./failure-review.js";
@@ -0,0 +1,323 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { resolveConnection } from "./credentials.js";
5
+ const DEFAULT_ADAPTER_PATH = "agentcert.sandbox.mjs";
6
+ const DEFAULT_REPORT_PATH = ".agentcert/sandbox/sandbox-adapter-conformance.json";
7
+ const DEFAULT_STRIPE_REPORT_PATH = ".agentcert/sandbox/stripe-readonly-report.json";
8
+ export async function runSandboxCommand(args) {
9
+ const action = args[0] ?? "help";
10
+ if (action === "help" || args.includes("--help") || args.includes("-h")) {
11
+ process.stdout.write(renderSandboxHelp(action === "help" ? undefined : action));
12
+ return { exitCode: 0 };
13
+ }
14
+ if (action === "init")
15
+ return initializeSandboxAdapter(args.slice(1));
16
+ if (action === "certify")
17
+ return certifySandboxAdapter(args.slice(1));
18
+ if (action === "push")
19
+ return pushSandboxCertification(args.slice(1));
20
+ if (action === "stripe-readonly")
21
+ return runStripeSandboxReadOnly(args.slice(1));
22
+ throw new Error(`Unknown sandbox command ${JSON.stringify(action)}. Run \`npx agentcert sandbox --help\`.`);
23
+ }
24
+ export async function initializeSandboxAdapter(args) {
25
+ const requestedPath = flag(args, "--adapter") ?? flag(args, "--out") ?? DEFAULT_ADAPTER_PATH;
26
+ const outPath = resolve(requestedPath);
27
+ await writeStarterFile(outPath, sandboxAdapterTemplate(), args.includes("--force"));
28
+ process.stdout.write(`Wrote ${outPath}\n\nNext:\n`);
29
+ process.stdout.write(` npx agentcert sandbox certify --adapter ${JSON.stringify(requestedPath)}\n`);
30
+ process.stdout.write("\nReplace the in-memory handlers with your sandbox implementation. Keep production credentials and live systems out of this adapter.\n");
31
+ return { exitCode: 0 };
32
+ }
33
+ export async function certifySandboxAdapter(args, runtimeOverride) {
34
+ const runtime = runtimeOverride ?? await loadSandboxRuntime();
35
+ const adapterPath = resolve(flag(args, "--adapter") ?? DEFAULT_ADAPTER_PATH);
36
+ const reportPath = resolve(flag(args, "--out") ?? DEFAULT_REPORT_PATH);
37
+ const system = await loadSandboxAdapter(adapterPath);
38
+ const report = await runtime.runSandboxAdapterConformanceSuite({
39
+ system,
40
+ implementation: flag(args, "--implementation"),
41
+ targetSystem: flag(args, "--target-system"),
42
+ });
43
+ await runtime.writeSandboxAdapterConformanceReport(report, reportPath);
44
+ renderCertificationResult(report, reportPath);
45
+ return { exitCode: report.verdict.passed ? 0 : 1, reportPath, report };
46
+ }
47
+ export async function pushSandboxCertification(args, runtimeOverride) {
48
+ const runtime = runtimeOverride ?? await loadSandboxRuntime();
49
+ const certified = await certifySandboxAdapter(args, runtime);
50
+ if (!certified.report || !certified.reportPath)
51
+ throw new Error("Sandbox certification did not produce a report.");
52
+ const connection = await resolveConnection({
53
+ name: flag(args, "--connection"),
54
+ server: flag(args, "--server"),
55
+ projectId: flag(args, "--project"),
56
+ apiKey: flag(args, "--api-key"),
57
+ });
58
+ const uploaded = await runtime.uploadSandboxCertificationReport(certified.report, {
59
+ baseUrl: connection.server,
60
+ projectId: connection.projectId,
61
+ apiKey: connection.apiKey,
62
+ externalId: flag(args, "--external-id"),
63
+ });
64
+ process.stdout.write(`Hosted sandbox run: ${String(uploaded.run.id ?? "created")}\n`);
65
+ process.stdout.write(`Hosted evidence: ${String(uploaded.evidence.id ?? "created")}\n`);
66
+ return certified;
67
+ }
68
+ export async function runStripeSandboxReadOnly(args, runtimeOverride) {
69
+ if (args.includes("--stripe-key") || args.includes("--restricted-key")) {
70
+ throw new Error("Stripe credentials are accepted only through STRIPE_RESTRICTED_TEST_KEY, never a CLI flag.");
71
+ }
72
+ const paymentIntentId = flag(args, "--payment-intent");
73
+ if (!paymentIntentId)
74
+ throw new Error("Stripe sandbox read requires --payment-intent <pi_...>.");
75
+ const restrictedApiKey = process.env.STRIPE_RESTRICTED_TEST_KEY?.trim();
76
+ if (!restrictedApiKey) {
77
+ throw new Error("Set STRIPE_RESTRICTED_TEST_KEY to a read-only Stripe rk_test_ restricted key.");
78
+ }
79
+ const runtime = runtimeOverride ?? await loadSandboxRuntime();
80
+ if (!runtime.runStripeSandboxReadOnlyCertification) {
81
+ throw new Error("AgentCert Stripe sandbox runtime is missing. Reinstall the agentcert package and retry.");
82
+ }
83
+ const report = await runtime.runStripeSandboxReadOnlyCertification({ restrictedApiKey, paymentIntentId });
84
+ const reportPath = resolve(flag(args, "--out") ?? DEFAULT_STRIPE_REPORT_PATH);
85
+ await mkdir(dirname(reportPath), { recursive: true });
86
+ await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
87
+ renderVendorReadResult(report, reportPath);
88
+ if (args.includes("--push")) {
89
+ const connection = await resolveConnection({
90
+ name: flag(args, "--connection"),
91
+ server: flag(args, "--server"),
92
+ projectId: flag(args, "--project"),
93
+ apiKey: flag(args, "--api-key"),
94
+ });
95
+ const uploaded = await runtime.uploadSandboxCertificationReport(report, {
96
+ baseUrl: connection.server,
97
+ projectId: connection.projectId,
98
+ apiKey: connection.apiKey,
99
+ externalId: flag(args, "--external-id"),
100
+ });
101
+ process.stdout.write(`Hosted sandbox run: ${String(uploaded.run.id ?? "created")}\n`);
102
+ process.stdout.write(`Hosted evidence: ${String(uploaded.evidence.id ?? "created")}\n`);
103
+ }
104
+ return { exitCode: report.verdict.passed ? 0 : 1, reportPath, report };
105
+ }
106
+ export async function loadSandboxAdapter(adapterPath) {
107
+ let module;
108
+ try {
109
+ module = await import(pathToFileURL(adapterPath).href);
110
+ }
111
+ catch (error) {
112
+ const message = error instanceof Error ? error.message : String(error);
113
+ throw new Error(`Could not load sandbox adapter ${adapterPath}: ${message}`);
114
+ }
115
+ const candidate = module.sandboxSystem ?? module.default ?? module.system;
116
+ if (!isSandboxSystem(candidate)) {
117
+ throw new Error(`Sandbox adapter ${adapterPath} must export \`sandboxSystem\`, \`system\`, or a default SandboxSystem object.`);
118
+ }
119
+ return candidate;
120
+ }
121
+ export function renderSandboxHelp(action) {
122
+ if (action === "init")
123
+ return `Usage:
124
+ agentcert sandbox init [--adapter agentcert.sandbox.mjs] [--force]
125
+
126
+ Writes one dependency-free synthetic SandboxSystem adapter template.
127
+ `;
128
+ if (action === "certify")
129
+ return `Usage:
130
+ agentcert sandbox certify --adapter ./my-sandbox-adapter.js [--out .agentcert/sandbox/report.json]
131
+
132
+ Runs the deterministic sandbox adapter conformance suite locally.
133
+ `;
134
+ if (action === "push")
135
+ return `Usage:
136
+ agentcert sandbox push --adapter ./my-sandbox-adapter.js
137
+
138
+ Runs certification, writes the local report, and uploads it through the saved AgentCert connection.
139
+ Use \`agentcert connect\` first, or pass --server, --project, and --api-key.
140
+ `;
141
+ if (action === "stripe-readonly")
142
+ return `Usage:
143
+ STRIPE_RESTRICTED_TEST_KEY=rk_test_... agentcert sandbox stripe-readonly --payment-intent pi_...
144
+ agentcert sandbox stripe-readonly --payment-intent pi_... --push
145
+
146
+ Reads one Stripe sandbox PaymentIntent through a fixed GET/resource allowlist,
147
+ writes a redacted evidence report, and optionally uploads it to AgentCert Hosted.
148
+ The Stripe key is read only from STRIPE_RESTRICTED_TEST_KEY.
149
+ `;
150
+ return `Usage:
151
+ agentcert sandbox init [--adapter agentcert.sandbox.mjs]
152
+ agentcert sandbox certify --adapter ./my-sandbox-adapter.js
153
+ agentcert sandbox push --adapter ./my-sandbox-adapter.js
154
+ agentcert sandbox stripe-readonly --payment-intent pi_... [--push]
155
+
156
+ Commands:
157
+ init Write one dependency-free adapter template
158
+ certify Run deterministic local conformance checks
159
+ push Certify and upload the report to AgentCert Hosted
160
+ stripe-readonly Run one bounded Stripe sandbox read and write evidence
161
+
162
+ Common options:
163
+ --adapter <path> Adapter module (default: agentcert.sandbox.mjs)
164
+ --out <path> Local report path
165
+ --implementation <id> Stable adapter implementation name
166
+ --target-system <name> Target system exercised by the suite
167
+ `;
168
+ }
169
+ async function loadSandboxRuntime() {
170
+ const adapterKitUrl = new URL("./vendor/onegent-runtime/sandbox-adapter-kit.js", import.meta.url);
171
+ const hostedUrl = new URL("./vendor/onegent-runtime/sandbox-hosted.js", import.meta.url);
172
+ const stripeUrl = new URL("./vendor/onegent-runtime/stripe-test-readonly.js", import.meta.url);
173
+ try {
174
+ const [adapterKit, hosted, stripe] = await Promise.all([
175
+ import(adapterKitUrl.href),
176
+ import(hostedUrl.href),
177
+ import(stripeUrl.href),
178
+ ]);
179
+ return { ...adapterKit, ...hosted, ...stripe };
180
+ }
181
+ catch (error) {
182
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : undefined;
183
+ if (code === "ERR_MODULE_NOT_FOUND") {
184
+ throw new Error("AgentCert sandbox runtime is missing. Reinstall the agentcert package and retry.");
185
+ }
186
+ throw error;
187
+ }
188
+ }
189
+ function isSandboxSystem(value) {
190
+ if (!value || typeof value !== "object" || Array.isArray(value))
191
+ return false;
192
+ const system = value;
193
+ const safety = system.safety;
194
+ return typeof system.name === "string"
195
+ && safety?.mode === "sandbox"
196
+ && safety.networkAccess === false
197
+ && safety.syntheticDataOnly === true
198
+ && Array.isArray(safety.allowedTargetSystems)
199
+ && ["createTenant", "deleteTenant", "resetTenant", "seedTenant", "hasTenant", "snapshotTenant", "adapterForTenant"]
200
+ .every((method) => typeof system[method] === "function");
201
+ }
202
+ function flag(args, name) {
203
+ const index = args.indexOf(name);
204
+ return index >= 0 ? args[index + 1] : undefined;
205
+ }
206
+ async function writeStarterFile(path, content, force) {
207
+ await mkdir(dirname(path), { recursive: true });
208
+ try {
209
+ await writeFile(path, content, { flag: force ? "w" : "wx" });
210
+ }
211
+ catch (error) {
212
+ if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
213
+ throw new Error(`${path} already exists. Re-run with --force to overwrite it.`);
214
+ }
215
+ throw error;
216
+ }
217
+ }
218
+ function renderCertificationResult(report, reportPath) {
219
+ process.stdout.write(`${report.verdict.passed ? "PASS" : "FAIL"} ${report.verdict.score}/100 sandbox adapter conformance\n`);
220
+ for (const check of report.checks) {
221
+ process.stdout.write(`- ${check.status === "passed" ? "PASS" : "FAIL"} ${check.id}: ${check.message}\n`);
222
+ }
223
+ process.stdout.write(`Report: ${reportPath}\n`);
224
+ }
225
+ function renderVendorReadResult(report, reportPath) {
226
+ process.stdout.write(`${report.verdict.passed ? "PASS" : "FAIL"} ${report.verdict.score}/100 Stripe sandbox bounded read\n`);
227
+ for (const check of report.checks) {
228
+ process.stdout.write(`- ${check.status === "passed" ? "PASS" : "FAIL"} ${check.id}: ${check.message}\n`);
229
+ }
230
+ process.stdout.write(`Requests audited: ${report.audit.length}\n`);
231
+ process.stdout.write(`Report: ${reportPath}\n`);
232
+ }
233
+ function sandboxAdapterTemplate() {
234
+ return `// Synthetic local state only. Do not import production credentials or call live systems here.
235
+ const tenants = new Map();
236
+
237
+ export const sandboxSystem = {
238
+ name: "my-sandbox-adapter",
239
+ safety: {
240
+ mode: "sandbox",
241
+ networkAccess: false,
242
+ syntheticDataOnly: true,
243
+ allowedTargetSystems: ["MySandboxSystem"],
244
+ },
245
+ createTenant(input) {
246
+ if (input.synthetic !== true) throw new Error("Synthetic tenants only.");
247
+ if (tenants.has(input.id)) throw new Error(\`Tenant \${input.id} already exists.\`);
248
+ const seed = syntheticSeed(input.seed ?? {});
249
+ tenants.set(input.id, { seed, state: structuredClone(seed) });
250
+ },
251
+ deleteTenant(tenantId) {
252
+ tenants.delete(tenantId);
253
+ },
254
+ resetTenant(tenantId) {
255
+ const tenant = requiredTenant(tenantId);
256
+ tenant.state = structuredClone(tenant.seed);
257
+ },
258
+ seedTenant(tenantId, seed) {
259
+ const tenant = requiredTenant(tenantId);
260
+ tenant.seed = syntheticSeed(seed);
261
+ tenant.state = structuredClone(seed);
262
+ },
263
+ hasTenant(tenantId) {
264
+ return tenants.has(tenantId);
265
+ },
266
+ snapshotTenant(tenantId) {
267
+ return structuredClone(requiredTenant(tenantId).state);
268
+ },
269
+ adapterForTenant(tenantId) {
270
+ requiredTenant(tenantId);
271
+ return {
272
+ name: \`my-sandbox-adapter:\${tenantId}\`,
273
+ safety: { mode: "sandbox", networkAccess: false, allowedTargetSystems: ["MySandboxSystem"] },
274
+ execute(action) {
275
+ const tenant = requiredTenant(tenantId);
276
+ const previousState = structuredClone(tenant.state[action.businessObjectId] ?? action.beforeState);
277
+ const observedState = structuredClone(action.proposedAfterState);
278
+ tenant.state[action.businessObjectId] = observedState;
279
+ return { method: "SYNTHETIC_SANDBOX", targetSystem: action.targetSystem, previousState, observedState };
280
+ },
281
+ rollback(action, execution) {
282
+ const restored = structuredClone(execution.previousState ?? action.beforeState);
283
+ requiredTenant(tenantId).state[action.businessObjectId] = restored;
284
+ return { success: true, observedState: restored };
285
+ },
286
+ };
287
+ },
288
+ };
289
+
290
+ export default sandboxSystem;
291
+
292
+ function requiredTenant(tenantId) {
293
+ const tenant = tenants.get(tenantId);
294
+ if (!tenant) throw new Error(\`Tenant \${tenantId} does not exist.\`);
295
+ return tenant;
296
+ }
297
+
298
+ function syntheticSeed(value) {
299
+ assertSynthetic(value, "seed", new Set());
300
+ return structuredClone(value);
301
+ }
302
+
303
+ function assertSynthetic(value, path, seen) {
304
+ if (value === null || ["boolean", "number"].includes(typeof value)) return;
305
+ if (typeof value === "string") {
306
+ if (/^(?:sk-(?:proj-)?|npm_|gh[pousr]_)[A-Za-z0-9_-]{16,}$/.test(value)) {
307
+ throw new Error(\`Credential-like value is not allowed at \${path}.\`);
308
+ }
309
+ return;
310
+ }
311
+ if (typeof value !== "object") throw new Error(\`Synthetic data must be JSON-compatible at \${path}.\`);
312
+ if (seen.has(value)) throw new Error(\`Synthetic data cannot contain cycles at \${path}.\`);
313
+ seen.add(value);
314
+ for (const [key, item] of Object.entries(value)) {
315
+ if (/(?:password|passwd|secret|token|api[_-]?key|credential|authorization|cookie)/i.test(key)) {
316
+ throw new Error(\`Credential-like field \${path}.\${key} is not allowed.\`);
317
+ }
318
+ assertSynthetic(item, \`\${path}.\${key}\`, seen);
319
+ }
320
+ seen.delete(value);
321
+ }
322
+ `;
323
+ }
@@ -0,0 +1,6 @@
1
+ import type { ActionIntent, MockPurchaseOrder } from "./types.js";
2
+ export declare const PROCUREMENT_DEMO_PO_ID = "PO-DEMO-4850";
3
+ export declare function createProcurementDemoPurchaseOrder(): MockPurchaseOrder;
4
+ export declare function getPurchaseOrder(id: string): MockPurchaseOrder | undefined;
5
+ export declare function submitMockPurchaseOrder(action: ActionIntent): MockPurchaseOrder;
6
+ //# sourceMappingURL=mock-procurement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock-procurement.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/mock-procurement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGlE,eAAO,MAAM,sBAAsB,iBAAiB,CAAC;AAErD,wBAAgB,kCAAkC,IAAI,iBAAiB,CActE;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAE1E;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,YAAY,GAAG,iBAAiB,CAc/E"}
@@ -0,0 +1,33 @@
1
+ import { nowIso, store } from "./store.js";
2
+ export const PROCUREMENT_DEMO_PO_ID = "PO-DEMO-4850";
3
+ export function createProcurementDemoPurchaseOrder() {
4
+ const purchaseOrder = {
5
+ id: PROCUREMENT_DEMO_PO_ID,
6
+ vendor: "Acme Industrial Supply",
7
+ amount: 4_850,
8
+ currency: "USD",
9
+ status: "DRAFT",
10
+ vendorApproved: true,
11
+ lineItem: "Replacement conveyor motor assembly",
12
+ lastUpdatedAt: nowIso(),
13
+ };
14
+ store.purchaseOrders.set(purchaseOrder.id, purchaseOrder);
15
+ return purchaseOrder;
16
+ }
17
+ export function getPurchaseOrder(id) {
18
+ return store.purchaseOrders.get(id);
19
+ }
20
+ export function submitMockPurchaseOrder(action) {
21
+ const purchaseOrder = store.purchaseOrders.get(action.businessObjectId);
22
+ if (!purchaseOrder) {
23
+ throw new Error(`Mock purchase order ${action.businessObjectId} was not found.`);
24
+ }
25
+ const updated = {
26
+ ...purchaseOrder,
27
+ status: "SUBMITTED",
28
+ lastUpdatedAt: nowIso(),
29
+ actionIntentId: action.id,
30
+ };
31
+ store.purchaseOrders.set(updated.id, updated);
32
+ return updated;
33
+ }
@@ -0,0 +1,6 @@
1
+ import type { ActionIntent, PolicyEvaluation, PolicyRule, RiskAssessment } from "./types.js";
2
+ export declare const DEFAULT_POLICY_RULES: PolicyRule[];
3
+ export declare function evaluatePolicy(action: ActionIntent, risk: RiskAssessment, policyRules?: PolicyRule[]): PolicyEvaluation;
4
+ export declare function getPolicyRules(ids: string[], policyRules?: PolicyRule[]): PolicyRule[];
5
+ export declare function matchesRule(action: ActionIntent, rule: PolicyRule): boolean;
6
+ //# sourceMappingURL=policies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policies.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/policies.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAmB,gBAAgB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE9G,eAAO,MAAM,oBAAoB,EAAE,UAAU,EAmC5C,CAAC;AAEF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,cAAc,EACpB,WAAW,GAAE,UAAU,EAAyB,GAC/C,gBAAgB,CA6ClB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,UAAU,EAAyB,GAAG,UAAU,EAAE,CAG5G;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAQ3E"}
@@ -0,0 +1,109 @@
1
+ export const DEFAULT_POLICY_RULES = [
2
+ {
3
+ id: "po-over-1000-requires-approval",
4
+ name: "Purchase orders over $1,000 require approval",
5
+ description: "High-value purchase order submissions must be reviewed before mock execution.",
6
+ actionTypes: ["SUBMIT"],
7
+ effect: "REQUIRE_APPROVAL",
8
+ enabled: true,
9
+ conditions: [{ field: "amount", operator: "greaterThan", value: 1_000 }],
10
+ },
11
+ {
12
+ id: "payment-actions-require-approval",
13
+ name: "Payment actions require approval",
14
+ description: "The demo runtime never executes real payments and requires approval for mock payment actions.",
15
+ actionTypes: ["PAY"],
16
+ effect: "REQUIRE_APPROVAL",
17
+ enabled: true,
18
+ },
19
+ {
20
+ id: "external-send-requires-approval",
21
+ name: "External send actions require approval",
22
+ description: "Outbound communications to non-demo recipients require approval.",
23
+ actionTypes: ["SEND"],
24
+ effect: "REQUIRE_APPROVAL",
25
+ enabled: true,
26
+ },
27
+ {
28
+ id: "production-actions-blocked-in-demo",
29
+ name: "Production actions are blocked in demo mode",
30
+ description: "This package is local-only and will not perform production actions.",
31
+ actionTypes: ["SUBMIT", "PAY", "SEND", "UPDATE"],
32
+ effect: "BLOCK",
33
+ enabled: true,
34
+ conditions: [{ field: "environment", operator: "equals", value: "production" }],
35
+ },
36
+ ];
37
+ export function evaluatePolicy(action, risk, policyRules = DEFAULT_POLICY_RULES) {
38
+ if (action.environment === "production") {
39
+ const productionBlock = policyRules.find((rule) => rule.id === "production-actions-blocked-in-demo") ??
40
+ DEFAULT_POLICY_RULES.find((rule) => rule.id === "production-actions-blocked-in-demo");
41
+ return {
42
+ effect: "BLOCK",
43
+ triggeredPolicies: productionBlock ? [productionBlock.id] : [],
44
+ reasons: [productionBlock?.description ?? "Production actions are blocked in this local demo runtime."],
45
+ requiresHumanApproval: false,
46
+ blocked: true,
47
+ };
48
+ }
49
+ const triggeredRules = policyRules.filter((rule) => matchesRule(action, rule));
50
+ const blockRules = triggeredRules.filter((rule) => rule.effect === "BLOCK");
51
+ if (blockRules.length > 0) {
52
+ return {
53
+ effect: "BLOCK",
54
+ triggeredPolicies: blockRules.map((rule) => rule.id),
55
+ reasons: blockRules.map((rule) => rule.description),
56
+ requiresHumanApproval: false,
57
+ blocked: true,
58
+ };
59
+ }
60
+ const approvalRules = triggeredRules.filter((rule) => rule.effect === "REQUIRE_APPROVAL");
61
+ if (approvalRules.length > 0) {
62
+ return {
63
+ effect: "REQUIRE_APPROVAL",
64
+ triggeredPolicies: approvalRules.map((rule) => rule.id),
65
+ reasons: approvalRules.map((rule) => rule.description),
66
+ requiresHumanApproval: true,
67
+ blocked: false,
68
+ };
69
+ }
70
+ return {
71
+ effect: "ALLOW",
72
+ triggeredPolicies: triggeredRules.map((rule) => rule.id),
73
+ reasons: risk.reasons,
74
+ requiresHumanApproval: false,
75
+ blocked: false,
76
+ };
77
+ }
78
+ export function getPolicyRules(ids, policyRules = DEFAULT_POLICY_RULES) {
79
+ const idSet = new Set(ids);
80
+ return policyRules.filter((rule) => idSet.has(rule.id));
81
+ }
82
+ export function matchesRule(action, rule) {
83
+ if (!rule.enabled || !rule.actionTypes.includes(action.actionType)) {
84
+ return false;
85
+ }
86
+ if (!rule.conditions || rule.conditions.length === 0) {
87
+ return true;
88
+ }
89
+ return rule.conditions.every((condition) => matchesCondition(action, condition));
90
+ }
91
+ function matchesCondition(action, condition) {
92
+ const actual = action[condition.field];
93
+ switch (condition.operator) {
94
+ case "equals":
95
+ return actual === condition.value;
96
+ case "notEquals":
97
+ return actual !== condition.value;
98
+ case "greaterThan":
99
+ return typeof actual === "number" && typeof condition.value === "number" && actual > condition.value;
100
+ case "greaterThanOrEqual":
101
+ return typeof actual === "number" && typeof condition.value === "number" && actual >= condition.value;
102
+ case "lessThan":
103
+ return typeof actual === "number" && typeof condition.value === "number" && actual < condition.value;
104
+ case "lessThanOrEqual":
105
+ return typeof actual === "number" && typeof condition.value === "number" && actual <= condition.value;
106
+ case "includes":
107
+ return typeof actual === "string" && typeof condition.value === "string" && actual.includes(condition.value);
108
+ }
109
+ }
@@ -0,0 +1,3 @@
1
+ import type { ActionIntent, RiskAssessment } from "./types.js";
2
+ export declare function assessRisk(action: ActionIntent): RiskAssessment;
3
+ //# sourceMappingURL=risk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/risk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAa,MAAM,YAAY,CAAC;AAK1E,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAsD/D"}
@@ -0,0 +1,54 @@
1
+ import { nextId, nowIso } from "./store.js";
2
+ const PURCHASE_ORDER_APPROVAL_THRESHOLD = 1_000;
3
+ export function assessRisk(action) {
4
+ const reasons = [];
5
+ const triggeredPolicies = [];
6
+ let riskLevel = "LOW";
7
+ let riskScore = 20;
8
+ if (action.actionType === "PAY") {
9
+ riskLevel = "HIGH";
10
+ riskScore = 85;
11
+ reasons.push("Payment actions can move money and require human approval in the demo runtime.");
12
+ triggeredPolicies.push("payment-actions-require-approval");
13
+ }
14
+ if (action.actionType === "SEND" && action.recipient && !action.recipient.endsWith("@example.local")) {
15
+ riskLevel = maxRisk(riskLevel, "HIGH");
16
+ riskScore = Math.max(riskScore, 75);
17
+ reasons.push("External send actions can disclose business data and require human approval.");
18
+ triggeredPolicies.push("external-send-requires-approval");
19
+ }
20
+ if (action.actionType === "SUBMIT" && (action.amount ?? 0) > PURCHASE_ORDER_APPROVAL_THRESHOLD) {
21
+ riskLevel = maxRisk(riskLevel, "HIGH");
22
+ riskScore = Math.max(riskScore, 80);
23
+ reasons.push("Purchase orders over $1,000 require human approval.");
24
+ triggeredPolicies.push("po-over-1000-requires-approval");
25
+ }
26
+ if (action.actionType === "UPDATE" && action.fieldsChanged.length > 0) {
27
+ riskLevel = maxRisk(riskLevel, "MEDIUM");
28
+ riskScore = Math.max(riskScore, 45);
29
+ reasons.push("Update actions modify business records and require verification.");
30
+ }
31
+ if (action.environment === "production") {
32
+ riskLevel = maxRisk(riskLevel, "CRITICAL");
33
+ riskScore = Math.max(riskScore, 95);
34
+ reasons.push("Production actions are blocked in this local demo runtime.");
35
+ triggeredPolicies.push("production-actions-blocked-in-demo");
36
+ }
37
+ if (reasons.length === 0) {
38
+ reasons.push("No high-risk demo policy matched this action.");
39
+ }
40
+ return {
41
+ id: nextId("risk"),
42
+ actionIntentId: action.id,
43
+ riskLevel,
44
+ riskScore,
45
+ reasons,
46
+ triggeredPolicies,
47
+ requiresHumanApproval: riskLevel === "HIGH" || riskLevel === "CRITICAL",
48
+ createdAt: nowIso(),
49
+ };
50
+ }
51
+ function maxRisk(left, right) {
52
+ const order = ["LOW", "MEDIUM", "HIGH", "CRITICAL"];
53
+ return order[Math.max(order.indexOf(left), order.indexOf(right))];
54
+ }