@synapsor/runner 1.5.0 → 1.5.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +92 -3
  2. package/README.md +96 -109
  3. package/dist/authoring.d.ts +23 -0
  4. package/dist/authoring.d.ts.map +1 -0
  5. package/dist/authoring.mjs +1318 -0
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +4 -5
  8. package/dist/local-ui.d.ts +14 -0
  9. package/dist/local-ui.d.ts.map +1 -1
  10. package/dist/runner.mjs +10845 -5598
  11. package/dist/runtime.mjs +29 -1
  12. package/dist/shadow.d.ts +36 -0
  13. package/dist/shadow.d.ts.map +1 -0
  14. package/dist/shadow.mjs +5262 -0
  15. package/docs/README.md +25 -2
  16. package/docs/alternatives.md +122 -0
  17. package/docs/capability-authoring.md +43 -0
  18. package/docs/client-recipes.md +218 -0
  19. package/docs/contract-testing.md +9 -7
  20. package/docs/cursor-plugin.md +78 -0
  21. package/docs/effect-regression.md +39 -2
  22. package/docs/fresh-developer-usability.md +110 -0
  23. package/docs/getting-started-own-database.md +96 -2
  24. package/docs/host-compatibility.md +59 -0
  25. package/docs/local-mode.md +6 -5
  26. package/docs/mcp-audit.md +166 -2
  27. package/docs/mcp-client-setup.md +4 -0
  28. package/docs/mcp-clients.md +33 -4
  29. package/docs/oss-vs-cloud.md +3 -0
  30. package/docs/release-notes.md +90 -4
  31. package/docs/security-boundary.md +20 -5
  32. package/docs/shadow-studies.md +47 -4
  33. package/docs/troubleshooting-first-run.md +46 -0
  34. package/examples/support-billing-agent/README.md +18 -0
  35. package/examples/support-billing-agent/app/README.md +6 -0
  36. package/examples/support-billing-agent/app/contract.ts +28 -0
  37. package/examples/support-billing-agent/app/effect-adapter.mjs +23 -0
  38. package/examples/support-billing-agent/app/record-shadow-outcomes.mjs +44 -0
  39. package/examples/support-billing-agent/scripts/run-evaluation.sh +6 -5
  40. package/examples/support-plan-credit/README.md +48 -2
  41. package/examples/support-plan-credit/mcp-client-examples/README.md +23 -0
  42. package/examples/support-plan-credit/mcp-client-examples/claude-code.sh +34 -0
  43. package/examples/support-plan-credit/mcp-client-examples/codex.config.toml +24 -0
  44. package/examples/support-plan-credit/mcp-client-examples/generic-stdio.mjs +35 -0
  45. package/examples/support-plan-credit/mcp-client-examples/generic-streamable-http.mjs +31 -0
  46. package/examples/support-plan-credit/mcp-client-examples/google-adk.py +40 -0
  47. package/examples/support-plan-credit/mcp-client-examples/langchain.mjs +29 -0
  48. package/examples/support-plan-credit/mcp-client-examples/llamaindex.py +36 -0
  49. package/examples/support-plan-credit/mcp-client-examples/openai-agents-stdio.ts +1 -1
  50. package/examples/support-plan-credit/mcp-client-examples/openai-agents-streamable-http.ts +1 -1
  51. package/examples/support-plan-credit/mcp-client-examples/vscode.mcp.json +20 -0
  52. package/examples/support-plan-credit/synapsor/actions/support.propose_plan_credit.contract-tests.generated.json +221 -0
  53. package/examples/support-plan-credit/synapsor/actions/support.propose_plan_credit.ts +34 -0
  54. package/fixtures/mcp-audit/README.md +14 -0
  55. package/fixtures/mcp-audit/cursor-bypass-config.json +38 -0
  56. package/fixtures/mcp-audit/dangerous-tools-list.json +33 -0
  57. package/fixtures/mcp-audit/reviewed-proposal-tools-list.json +60 -0
  58. package/package.json +18 -3
  59. package/schemas/mcp-audit-report.schema.json +100 -1
  60. package/schemas/synapsor.contract-tests.schema.json +1 -1
@@ -0,0 +1,1318 @@
1
+ // node_modules/.pnpm/@synapsor+spec@1.4.2/node_modules/@synapsor/spec/dist/version.js
2
+ var SPEC_VERSION = "0.1";
3
+
4
+ // node_modules/.pnpm/@synapsor+spec@1.4.2/node_modules/@synapsor/spec/dist/errors.js
5
+ var SynapsorSpecValidationError = class extends Error {
6
+ issues;
7
+ constructor(issues) {
8
+ super(`Invalid Synapsor contract:
9
+ ${issues.map((issue) => `${issue.path} ${issue.code}: ${issue.message}`).join("\n")}`);
10
+ this.issues = issues;
11
+ this.name = "SynapsorSpecValidationError";
12
+ }
13
+ };
14
+
15
+ // node_modules/.pnpm/@synapsor+spec@1.4.2/node_modules/@synapsor/spec/dist/validate.js
16
+ var TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
17
+ "spec_version",
18
+ "kind",
19
+ "metadata",
20
+ "resources",
21
+ "contexts",
22
+ "capabilities",
23
+ "workflows",
24
+ "policies",
25
+ "evidence",
26
+ "proposals",
27
+ "receipts",
28
+ "replay",
29
+ "external_actions"
30
+ ]);
31
+ var METADATA_KEYS = /* @__PURE__ */ new Set(["name", "description", "version", "tags"]);
32
+ var RESOURCE_KEYS = /* @__PURE__ */ new Set(["name", "engine", "schema", "table", "type", "primary_key", "tenant_key", "conflict_key", "single_tenant_dev"]);
33
+ var CONTEXT_KEYS = /* @__PURE__ */ new Set(["name", "description", "bindings", "tenant_binding", "principal_binding"]);
34
+ var BINDING_KEYS = /* @__PURE__ */ new Set(["name", "source", "key", "required"]);
35
+ var CAPABILITY_KEYS = /* @__PURE__ */ new Set(["name", "description", "returns_hint", "kind", "context", "source", "subject", "args", "lookup", "visible_fields", "kept_out_fields", "evidence", "max_rows", "proposal", "aggregate"]);
36
+ var SUBJECT_KEYS = /* @__PURE__ */ new Set(["resource", "schema", "table", "primary_key", "tenant_key", "principal_scope_key", "conflict_key", "single_tenant_dev"]);
37
+ var ARG_KEYS = /* @__PURE__ */ new Set(["type", "description", "required", "max_length", "minimum", "maximum", "enum", "max_items", "fields"]);
38
+ var LOOKUP_KEYS = /* @__PURE__ */ new Set(["id_from_arg"]);
39
+ var EVIDENCE_KEYS = /* @__PURE__ */ new Set(["required", "sources", "query_audit", "handle_prefix"]);
40
+ var PROPOSAL_KEYS = /* @__PURE__ */ new Set(["action", "operation", "allowed_fields", "patch", "numeric_bounds", "transition_guards", "reversibility", "conflict_guard", "approval", "writeback"]);
41
+ var OPERATION_KEYS = /* @__PURE__ */ new Set(["kind", "cardinality", "selection", "max_rows", "aggregate_bounds", "batch", "deduplication", "version_advance"]);
42
+ var SELECTION_KEYS = /* @__PURE__ */ new Set(["all"]);
43
+ var PREDICATE_TERM_KEYS = /* @__PURE__ */ new Set(["column", "operator", "value"]);
44
+ var AGGREGATE_BOUND_KEYS = /* @__PURE__ */ new Set(["column", "measure", "maximum"]);
45
+ var BATCH_KEYS = /* @__PURE__ */ new Set(["items_from_arg"]);
46
+ var DEDUPLICATION_KEYS = /* @__PURE__ */ new Set(["components"]);
47
+ var DEDUPLICATION_COMPONENT_KEYS = /* @__PURE__ */ new Set(["column", "source", "fixed", "item_field"]);
48
+ var VERSION_ADVANCE_KEYS = /* @__PURE__ */ new Set(["column", "strategy"]);
49
+ var PATCH_KEYS = /* @__PURE__ */ new Set(["fixed", "from_arg", "from_item"]);
50
+ var SET_MAX_ROWS_HARD_CEILING = 100;
51
+ var NUMERIC_BOUND_KEYS = /* @__PURE__ */ new Set(["minimum", "maximum"]);
52
+ var TRANSITION_GUARD_KEYS = /* @__PURE__ */ new Set(["from_column", "allowed"]);
53
+ var CONFLICT_GUARD_KEYS = /* @__PURE__ */ new Set(["column", "weak_guard_ack"]);
54
+ var APPROVAL_KEYS = /* @__PURE__ */ new Set(["mode", "required_role", "required_approvals", "policy"]);
55
+ var WRITEBACK_KEYS = /* @__PURE__ */ new Set(["mode", "executor", "idempotency_key"]);
56
+ var REVERSIBILITY_KEYS = /* @__PURE__ */ new Set(["mode"]);
57
+ var AGGREGATE_READ_KEYS = /* @__PURE__ */ new Set(["function", "count_mode", "column", "selection", "minimum_group_size"]);
58
+ var WORKFLOW_KEYS = /* @__PURE__ */ new Set(["name", "description", "context", "allowed_capabilities", "required_evidence", "approval", "settlement", "replay"]);
59
+ var POLICY_KEYS = /* @__PURE__ */ new Set(["name", "kind", "mode", "rules", "limits"]);
60
+ var APPROVAL_POLICY_LIMIT_KEYS = /* @__PURE__ */ new Set(["kind", "max", "period", "field", "scope"]);
61
+ var EVIDENCE_RECORD_KEYS = /* @__PURE__ */ new Set(["handle", "capability", "query_fingerprint", "items"]);
62
+ var PROPOSAL_RECORD_KEYS = /* @__PURE__ */ new Set(["id", "capability", "subject", "status", "diff", "evidence_handle"]);
63
+ var RECEIPT_KEYS = /* @__PURE__ */ new Set(["id", "proposal_id", "status", "idempotency_key", "source_database_mutated", "rows_affected"]);
64
+ var REPLAY_KEYS = /* @__PURE__ */ new Set(["id", "proposal_id", "run_id", "events"]);
65
+ var EXTERNAL_ACTION_KEYS = /* @__PURE__ */ new Set(["id", "action", "handler", "idempotency_key", "receipt"]);
66
+ var TRUSTED_ARG_NAMES = /* @__PURE__ */ new Set([
67
+ "tenant_id",
68
+ "tenantId",
69
+ "principal",
70
+ "principal_id",
71
+ "principalId",
72
+ "schema",
73
+ "table",
74
+ "column",
75
+ "columns",
76
+ "database_url",
77
+ "write_url",
78
+ "read_url",
79
+ "expected_version",
80
+ "row_version"
81
+ ]);
82
+ function validateContract(input) {
83
+ const errors = [];
84
+ const warnings = [];
85
+ if (!isRecord(input)) {
86
+ return {
87
+ ok: false,
88
+ errors: [{ path: "$", code: "CONTRACT_NOT_OBJECT", message: "Contract must be a JSON object." }],
89
+ warnings
90
+ };
91
+ }
92
+ checkUnknownKeys(input, TOP_LEVEL_KEYS, "$", errors);
93
+ if (input.spec_version !== SPEC_VERSION) {
94
+ errors.push({ path: "$.spec_version", code: "UNSUPPORTED_SPEC_VERSION", message: `spec_version must be ${SPEC_VERSION}.` });
95
+ }
96
+ if (input.kind !== "SynapsorContract") {
97
+ errors.push({ path: "$.kind", code: "INVALID_KIND", message: "kind must be SynapsorContract." });
98
+ }
99
+ validateMetadata(input.metadata, "$.metadata", errors);
100
+ const resourceNames = validateResources(input.resources, errors, warnings);
101
+ const contextNames = validateContexts(input.contexts, errors, warnings);
102
+ const capabilities = Array.isArray(input.capabilities) ? input.capabilities : [];
103
+ const policies = Array.isArray(input.policies) ? input.policies : [];
104
+ const capabilityNames = validateCapabilities(input.capabilities, contextNames, resourceNames, errors, warnings);
105
+ validatePrincipalScopes(input.capabilities, input.contexts, input.resources, errors);
106
+ validateWorkflows(input.workflows, contextNames, capabilityNames, errors);
107
+ const policyByName = validatePolicies(input.policies, errors);
108
+ validateCapabilityApprovalPolicies(capabilities, policies, policyByName, errors);
109
+ validateEvidenceRecords(input.evidence, errors);
110
+ validateProposalRecords(input.proposals, capabilityNames, errors);
111
+ validateReceiptRecords(input.receipts, errors);
112
+ validateReplay(input.replay, errors);
113
+ validateExternalActions(input.external_actions, errors);
114
+ scanForInlineSecrets(input, "$", errors);
115
+ return { ok: errors.length === 0, errors, warnings };
116
+ }
117
+ function assertValidContract(input) {
118
+ const result = validateContract(input);
119
+ if (!result.ok)
120
+ throw new SynapsorSpecValidationError(result.errors);
121
+ }
122
+ function validateMetadata(value, path, errors) {
123
+ if (value === void 0)
124
+ return;
125
+ if (!isRecord(value)) {
126
+ errors.push({ path, code: "METADATA_NOT_OBJECT", message: "metadata must be an object." });
127
+ return;
128
+ }
129
+ checkUnknownKeys(value, METADATA_KEYS, path, errors);
130
+ if (value.name !== void 0 && !isNonEmptyString(value.name))
131
+ errors.push({ path: `${path}.name`, code: "INVALID_METADATA_NAME", message: "metadata.name must be a non-empty string." });
132
+ if (value.tags !== void 0 && (!Array.isArray(value.tags) || value.tags.some((tag) => !isNonEmptyString(tag)))) {
133
+ errors.push({ path: `${path}.tags`, code: "INVALID_METADATA_TAGS", message: "metadata.tags must be an array of non-empty strings." });
134
+ }
135
+ }
136
+ function validateResources(value, errors, warnings) {
137
+ const names = /* @__PURE__ */ new Set();
138
+ if (value === void 0)
139
+ return names;
140
+ if (!Array.isArray(value)) {
141
+ errors.push({ path: "$.resources", code: "RESOURCES_NOT_ARRAY", message: "resources must be an array." });
142
+ return names;
143
+ }
144
+ value.forEach((resource, index) => {
145
+ const path = `$.resources[${index}]`;
146
+ if (!isRecord(resource)) {
147
+ errors.push({ path, code: "RESOURCE_NOT_OBJECT", message: "resource must be an object." });
148
+ return;
149
+ }
150
+ checkUnknownKeys(resource, RESOURCE_KEYS, path, errors);
151
+ if (!isQualifiedOrSafeName(resource.name))
152
+ errors.push({ path: `${path}.name`, code: "INVALID_RESOURCE_NAME", message: "resource name must be a safe identifier or qualified name." });
153
+ else
154
+ addUnique(names, resource.name, `${path}.name`, "DUPLICATE_RESOURCE_NAME", errors);
155
+ if (!["postgres", "mysql", "synapsor"].includes(String(resource.engine)))
156
+ errors.push({ path: `${path}.engine`, code: "INVALID_RESOURCE_ENGINE", message: "engine must be postgres, mysql, or synapsor." });
157
+ for (const key of ["schema", "table", "primary_key"]) {
158
+ if (!isSafeIdentifier(resource[key]))
159
+ errors.push({ path: `${path}.${key}`, code: "INVALID_RESOURCE_IDENTIFIER", message: `${key} must be a fixed safe identifier.` });
160
+ }
161
+ if (resource.tenant_key !== void 0 && !isSafeIdentifier(resource.tenant_key))
162
+ errors.push({ path: `${path}.tenant_key`, code: "INVALID_TENANT_KEY", message: "tenant_key must be a fixed safe identifier." });
163
+ if (!isSafeIdentifier(resource.tenant_key) && resource.single_tenant_dev !== true) {
164
+ errors.push({ path: `${path}.tenant_key`, code: "TENANT_KEY_REQUIRED", message: "tenant_key is required unless single_tenant_dev is explicitly true." });
165
+ }
166
+ if (resource.single_tenant_dev === true)
167
+ warnings.push({ path: `${path}.single_tenant_dev`, code: "SINGLE_TENANT_DEV", message: "single_tenant_dev is only for local demos and must not be used for shared tenant data." });
168
+ if (resource.conflict_key !== void 0 && !isSafeIdentifier(resource.conflict_key))
169
+ errors.push({ path: `${path}.conflict_key`, code: "INVALID_CONFLICT_KEY", message: "conflict_key must be a fixed safe identifier." });
170
+ });
171
+ return names;
172
+ }
173
+ function validateContexts(value, errors, warnings) {
174
+ const names = /* @__PURE__ */ new Set();
175
+ if (!Array.isArray(value) || value.length === 0) {
176
+ errors.push({ path: "$.contexts", code: "CONTEXTS_REQUIRED", message: "At least one context is required." });
177
+ return names;
178
+ }
179
+ value.forEach((context, index) => {
180
+ const path = `$.contexts[${index}]`;
181
+ if (!isRecord(context)) {
182
+ errors.push({ path, code: "CONTEXT_NOT_OBJECT", message: "context must be an object." });
183
+ return;
184
+ }
185
+ checkUnknownKeys(context, CONTEXT_KEYS, path, errors);
186
+ if (!isQualifiedOrSafeName(context.name))
187
+ errors.push({ path: `${path}.name`, code: "INVALID_CONTEXT_NAME", message: "context name must be a safe identifier or qualified name." });
188
+ else
189
+ addUnique(names, context.name, `${path}.name`, "DUPLICATE_CONTEXT_NAME", errors);
190
+ if (!Array.isArray(context.bindings) || context.bindings.length === 0) {
191
+ errors.push({ path: `${path}.bindings`, code: "BINDINGS_REQUIRED", message: "context.bindings must contain trusted/session bindings." });
192
+ return;
193
+ }
194
+ const bindingNames = /* @__PURE__ */ new Set();
195
+ context.bindings.forEach((binding, bindingIndex) => {
196
+ const bindingPath = `${path}.bindings[${bindingIndex}]`;
197
+ if (!isRecord(binding)) {
198
+ errors.push({ path: bindingPath, code: "BINDING_NOT_OBJECT", message: "binding must be an object." });
199
+ return;
200
+ }
201
+ checkUnknownKeys(binding, BINDING_KEYS, bindingPath, errors);
202
+ if (!isSafeIdentifier(binding.name))
203
+ errors.push({ path: `${bindingPath}.name`, code: "INVALID_BINDING_NAME", message: "binding name must be a safe identifier." });
204
+ else
205
+ addUnique(bindingNames, binding.name, `${bindingPath}.name`, "DUPLICATE_BINDING_NAME", errors);
206
+ if (!["session", "environment", "cloud_session", "static_dev", "http_claim"].includes(String(binding.source)))
207
+ errors.push({ path: `${bindingPath}.source`, code: "INVALID_BINDING_SOURCE", message: "binding source must be session, environment, cloud_session, static_dev, or http_claim." });
208
+ if (!isNonEmptyString(binding.key))
209
+ errors.push({ path: `${bindingPath}.key`, code: "INVALID_BINDING_KEY", message: "binding key must be a non-empty string." });
210
+ if (binding.source === "static_dev")
211
+ warnings.push({ path: `${bindingPath}.source`, code: "STATIC_DEV_BINDING", message: "static_dev bindings are for local demos only." });
212
+ });
213
+ for (const key of ["tenant_binding", "principal_binding"]) {
214
+ if (context[key] !== void 0 && (!isSafeIdentifier(context[key]) || !bindingNames.has(String(context[key])))) {
215
+ errors.push({ path: `${path}.${key}`, code: "UNKNOWN_CONTEXT_BINDING", message: `${key} must reference a binding name.` });
216
+ }
217
+ }
218
+ });
219
+ return names;
220
+ }
221
+ function validateCapabilities(value, contextNames, resourceNames, errors, warnings) {
222
+ const names = /* @__PURE__ */ new Set();
223
+ if (!Array.isArray(value) || value.length === 0) {
224
+ errors.push({ path: "$.capabilities", code: "CAPABILITIES_REQUIRED", message: "At least one capability is required." });
225
+ return names;
226
+ }
227
+ value.forEach((capability, index) => {
228
+ const path = `$.capabilities[${index}]`;
229
+ if (!isRecord(capability)) {
230
+ errors.push({ path, code: "CAPABILITY_NOT_OBJECT", message: "capability must be an object." });
231
+ return;
232
+ }
233
+ checkUnknownKeys(capability, CAPABILITY_KEYS, path, errors);
234
+ if (!isQualifiedName(capability.name))
235
+ errors.push({ path: `${path}.name`, code: "INVALID_CAPABILITY_NAME", message: "capability name must be namespace.name." });
236
+ else
237
+ addUnique(names, capability.name, `${path}.name`, "DUPLICATE_CAPABILITY_NAME", errors);
238
+ if (capability.returns_hint !== void 0 && !isNonEmptyString(capability.returns_hint))
239
+ errors.push({ path: `${path}.returns_hint`, code: "INVALID_RETURNS_HINT", message: "returns_hint must be a non-empty string." });
240
+ if (!["read", "aggregate_read", "proposal", "external_action", "answer_with_evidence"].includes(String(capability.kind)))
241
+ errors.push({ path: `${path}.kind`, code: "INVALID_CAPABILITY_KIND", message: "kind must be read, aggregate_read, proposal, external_action, or answer_with_evidence." });
242
+ if (!isNonEmptyString(capability.context) || !contextNames.has(capability.context))
243
+ errors.push({ path: `${path}.context`, code: "UNKNOWN_CONTEXT", message: "capability.context must reference a declared context." });
244
+ validateSubject(capability.subject, `${path}.subject`, resourceNames, errors, warnings);
245
+ validateArgs(capability.args, `${path}.args`, errors, capability.kind === "aggregate_read");
246
+ if (capability.lookup !== void 0)
247
+ validateLookup(capability.lookup, `${path}.lookup`, capability.args, errors);
248
+ validateFieldList(capability.visible_fields, `${path}.visible_fields`, "VISIBLE_FIELDS_REQUIRED", errors, capability.kind === "aggregate_read");
249
+ if (capability.kept_out_fields !== void 0)
250
+ validateFieldList(capability.kept_out_fields, `${path}.kept_out_fields`, "INVALID_KEPT_OUT_FIELDS", errors, true);
251
+ validateKeptOutExclusion(capability.visible_fields, capability.kept_out_fields, path, errors);
252
+ if (capability.evidence !== void 0)
253
+ validateEvidenceRequirement(capability.evidence, `${path}.evidence`, errors);
254
+ if (capability.max_rows !== void 0 && !isPositiveInteger(capability.max_rows))
255
+ errors.push({ path: `${path}.max_rows`, code: "INVALID_MAX_ROWS", message: "max_rows must be a positive integer." });
256
+ if (capability.kind === "proposal") {
257
+ validateProposalAction(capability.proposal, capability.subject, `${path}.proposal`, errors);
258
+ validateSetCapabilityArgs(capability, path, errors);
259
+ }
260
+ if (capability.kind !== "proposal" && capability.proposal !== void 0)
261
+ errors.push({ path: `${path}.proposal`, code: "PROPOSAL_ONLY_FOR_PROPOSAL_KIND", message: "proposal is only valid for proposal capabilities." });
262
+ if (capability.kind === "aggregate_read")
263
+ validateAggregateRead(capability, path, errors);
264
+ else if (capability.aggregate !== void 0)
265
+ errors.push({ path: `${path}.aggregate`, code: "AGGREGATE_ONLY_FOR_AGGREGATE_READ", message: "aggregate is valid only for aggregate_read capabilities." });
266
+ });
267
+ return names;
268
+ }
269
+ function validateSetCapabilityArgs(capability, path, errors) {
270
+ if (!isRecord(capability.proposal) || !isRecord(capability.proposal.operation) || capability.proposal.operation.cardinality !== "set")
271
+ return;
272
+ const operation = capability.proposal.operation;
273
+ const args = isRecord(capability.args) ? capability.args : {};
274
+ const patch = isRecord(capability.proposal.patch) ? capability.proposal.patch : {};
275
+ const visible = new Set(Array.isArray(capability.visible_fields) ? capability.visible_fields.filter((field) => typeof field === "string") : []);
276
+ const requiredReadFields = [
277
+ ...Array.isArray(operation.aggregate_bounds) ? operation.aggregate_bounds.filter(isRecord).map((bound) => bound.column) : [],
278
+ ...isRecord(operation.selection) && Array.isArray(operation.selection.all) ? operation.selection.all.filter(isRecord).map((term) => term.column) : [],
279
+ ...isRecord(capability.proposal.conflict_guard) ? [capability.proposal.conflict_guard.column] : []
280
+ ].filter(isSafeIdentifier);
281
+ for (const field of requiredReadFields)
282
+ if (!visible.has(field))
283
+ errors.push({ path: `${path}.visible_fields`, code: "SET_REVIEW_FIELD_NOT_VISIBLE", message: `bounded set review requires visible field ${field}.` });
284
+ if (operation.kind === "insert") {
285
+ const itemsArg = isRecord(operation.batch) ? operation.batch.items_from_arg : void 0;
286
+ const arg = isSafeIdentifier(itemsArg) ? args[itemsArg] : void 0;
287
+ if (!isRecord(arg) || arg.type !== "object_array") {
288
+ errors.push({ path: `${path}.proposal.operation.batch.items_from_arg`, code: "BATCH_ITEMS_ARG_NOT_OBJECT_ARRAY", message: "batch.items_from_arg must reference an object_array argument." });
289
+ return;
290
+ }
291
+ if (Number(arg.max_items) > Number(operation.max_rows))
292
+ errors.push({ path: `${path}.args.${String(itemsArg)}.max_items`, code: "BATCH_ITEMS_EXCEED_MAX_ROWS", message: "object_array max_items must not exceed operation.max_rows." });
293
+ const fields = isRecord(arg.fields) ? arg.fields : {};
294
+ for (const [column, binding] of Object.entries(patch)) {
295
+ if (!isRecord(binding) || !isSafeIdentifier(binding.from_item) || !Object.prototype.hasOwnProperty.call(fields, binding.from_item)) {
296
+ errors.push({ path: `${path}.proposal.patch.${column}.from_item`, code: "UNKNOWN_BATCH_ITEM_FIELD", message: "batch INSERT patch fields must bind a declared item field." });
297
+ }
298
+ }
299
+ const components = isRecord(operation.deduplication) && Array.isArray(operation.deduplication.components) ? operation.deduplication.components : [];
300
+ const primaryKey = isRecord(capability.subject) ? capability.subject.primary_key : void 0;
301
+ if (isSafeIdentifier(primaryKey) && !components.some((component) => isRecord(component) && component.source === "item_field" && component.column === primaryKey)) {
302
+ errors.push({ path: `${path}.proposal.operation.deduplication.components`, code: "BATCH_PRIMARY_KEY_REQUIRED", message: `batch INSERT must derive primary key ${primaryKey} from a typed item field.` });
303
+ }
304
+ for (const [index, component] of components.entries()) {
305
+ if (isRecord(component) && component.source === "item_field" && (!isSafeIdentifier(component.item_field) || !Object.prototype.hasOwnProperty.call(fields, component.item_field))) {
306
+ errors.push({ path: `${path}.proposal.operation.deduplication.components[${index}].item_field`, code: "UNKNOWN_DEDUP_ITEM_FIELD", message: "item_field deduplication must reference a declared batch item field." });
307
+ }
308
+ }
309
+ } else {
310
+ for (const [column, binding] of Object.entries(patch))
311
+ if (isRecord(binding) && binding.from_item !== void 0)
312
+ errors.push({ path: `${path}.proposal.patch.${column}.from_item`, code: "FROM_ITEM_BATCH_INSERT_ONLY", message: "from_item is valid only for batch INSERT." });
313
+ }
314
+ }
315
+ function validateSubject(value, path, resourceNames, errors, warnings) {
316
+ if (!isRecord(value)) {
317
+ errors.push({ path, code: "SUBJECT_REQUIRED", message: "subject must be an object." });
318
+ return;
319
+ }
320
+ checkUnknownKeys(value, SUBJECT_KEYS, path, errors);
321
+ if (value.resource !== void 0 && (!isQualifiedOrSafeName(value.resource) || resourceNames.size > 0 && !resourceNames.has(String(value.resource)))) {
322
+ errors.push({ path: `${path}.resource`, code: "UNKNOWN_RESOURCE", message: "subject.resource must reference a declared resource." });
323
+ }
324
+ if (value.resource === void 0) {
325
+ for (const key of ["schema", "table", "primary_key"]) {
326
+ if (!isSafeIdentifier(value[key]))
327
+ errors.push({ path: `${path}.${key}`, code: "INVALID_SUBJECT_IDENTIFIER", message: `${key} must be provided as a fixed safe identifier when resource is omitted.` });
328
+ }
329
+ if (!isSafeIdentifier(value.tenant_key) && value.single_tenant_dev !== true) {
330
+ errors.push({ path: `${path}.tenant_key`, code: "TENANT_KEY_REQUIRED", message: "tenant_key is required unless single_tenant_dev is explicitly true." });
331
+ }
332
+ }
333
+ if (value.principal_scope_key !== void 0 && !isSafeIdentifier(value.principal_scope_key)) {
334
+ errors.push({ path: `${path}.principal_scope_key`, code: "INVALID_PRINCIPAL_SCOPE_KEY", message: "principal_scope_key must be a fixed safe identifier." });
335
+ }
336
+ if (value.principal_scope_key !== void 0 && value.resource === void 0 && !isSafeIdentifier(value.tenant_key)) {
337
+ errors.push({ path: `${path}.principal_scope_key`, code: "PRINCIPAL_SCOPE_TENANT_REQUIRED", message: "principal_scope_key can only narrow a capability that also declares tenant_key." });
338
+ }
339
+ if (value.principal_scope_key !== void 0 && value.single_tenant_dev === true) {
340
+ errors.push({ path: `${path}.principal_scope_key`, code: "PRINCIPAL_SCOPE_TENANT_REQUIRED", message: "principal_scope_key cannot be used with single_tenant_dev; declare a reviewed tenant_key." });
341
+ }
342
+ if (value.single_tenant_dev === true)
343
+ warnings.push({ path: `${path}.single_tenant_dev`, code: "SINGLE_TENANT_DEV", message: "single_tenant_dev is only for local demos and must not be used for shared tenant data." });
344
+ }
345
+ function validatePrincipalScopes(capabilities, contexts, resources, errors) {
346
+ if (!Array.isArray(capabilities) || !Array.isArray(contexts))
347
+ return;
348
+ const contextByName = /* @__PURE__ */ new Map();
349
+ const resourceByName = /* @__PURE__ */ new Map();
350
+ for (const context of contexts) {
351
+ if (isRecord(context) && isNonEmptyString(context.name))
352
+ contextByName.set(context.name, context);
353
+ }
354
+ if (Array.isArray(resources)) {
355
+ for (const resource of resources) {
356
+ if (isRecord(resource) && isNonEmptyString(resource.name))
357
+ resourceByName.set(resource.name, resource);
358
+ }
359
+ }
360
+ capabilities.forEach((capability, index) => {
361
+ if (!isRecord(capability) || !isRecord(capability.subject) || capability.subject.principal_scope_key === void 0)
362
+ return;
363
+ const path = `$.capabilities[${index}]`;
364
+ const resource = typeof capability.subject.resource === "string" ? resourceByName.get(capability.subject.resource) : void 0;
365
+ const tenantKey = capability.subject.tenant_key ?? resource?.tenant_key;
366
+ const singleTenantDev = capability.subject.single_tenant_dev ?? resource?.single_tenant_dev;
367
+ if (!isSafeIdentifier(tenantKey) || singleTenantDev === true) {
368
+ errors.push({ path: `${path}.subject.principal_scope_key`, code: "PRINCIPAL_SCOPE_TENANT_REQUIRED", message: "principal_scope_key can only narrow a capability with a reviewed tenant_key." });
369
+ }
370
+ const context = contextByName.get(String(capability.context));
371
+ if (!context || !isSafeIdentifier(context.principal_binding)) {
372
+ errors.push({ path: `${path}.context`, code: "PRINCIPAL_SCOPE_BINDING_REQUIRED", message: "principal-scoped capabilities require a context with principal_binding." });
373
+ return;
374
+ }
375
+ const binding = Array.isArray(context.bindings) ? context.bindings.find((item) => isRecord(item) && item.name === context.principal_binding) : void 0;
376
+ if (!isRecord(binding) || binding.required !== true) {
377
+ errors.push({ path: `${path}.context`, code: "PRINCIPAL_SCOPE_BINDING_REQUIRED", message: "principal_binding must reference a required trusted context binding." });
378
+ }
379
+ const scopeKey = capability.subject.principal_scope_key;
380
+ if (isRecord(capability.args) && Object.prototype.hasOwnProperty.call(capability.args, String(scopeKey))) {
381
+ errors.push({ path: `${path}.args.${String(scopeKey)}`, code: "MODEL_CONTROLLED_PRINCIPAL_SCOPE", message: "the reviewed principal scope column cannot also be a model-facing argument." });
382
+ }
383
+ if (isRecord(capability.proposal)) {
384
+ if (Array.isArray(capability.proposal.allowed_fields) && capability.proposal.allowed_fields.includes(scopeKey)) {
385
+ errors.push({ path: `${path}.proposal.allowed_fields`, code: "PRINCIPAL_SCOPE_WRITE_FORBIDDEN", message: "the reviewed principal scope column cannot be model-writeable." });
386
+ }
387
+ if (isRecord(capability.proposal.patch) && Object.prototype.hasOwnProperty.call(capability.proposal.patch, String(scopeKey))) {
388
+ errors.push({ path: `${path}.proposal.patch.${String(scopeKey)}`, code: "PRINCIPAL_SCOPE_WRITE_FORBIDDEN", message: "the reviewed principal scope column is forced from trusted context and cannot be patched by the model." });
389
+ }
390
+ }
391
+ });
392
+ }
393
+ function validateArgs(value, path, errors, allowEmpty = false) {
394
+ if (!isRecord(value) || !allowEmpty && Object.keys(value).length === 0) {
395
+ errors.push({ path, code: "ARGS_REQUIRED", message: "args must define at least one model-facing business argument." });
396
+ return;
397
+ }
398
+ for (const [name, arg] of Object.entries(value)) {
399
+ const argPath = `${path}.${name}`;
400
+ if (!isSafeIdentifier(name))
401
+ errors.push({ path: argPath, code: "INVALID_ARG_NAME", message: "arg names must be safe identifiers." });
402
+ if (TRUSTED_ARG_NAMES.has(name))
403
+ errors.push({ path: argPath, code: "MODEL_CONTROLLED_TRUST_ARG", message: "model-facing args cannot include trust scope, dynamic identifiers, or version authority." });
404
+ if (!isRecord(arg)) {
405
+ errors.push({ path: argPath, code: "ARG_NOT_OBJECT", message: "arg definition must be an object." });
406
+ continue;
407
+ }
408
+ checkUnknownKeys(arg, ARG_KEYS, argPath, errors);
409
+ if (!["string", "number", "boolean", "object_array"].includes(String(arg.type)))
410
+ errors.push({ path: `${argPath}.type`, code: "INVALID_ARG_TYPE", message: "arg type must be string, number, boolean, or object_array." });
411
+ if (arg.description !== void 0 && !isNonEmptyString(arg.description))
412
+ errors.push({ path: `${argPath}.description`, code: "INVALID_ARG_DESCRIPTION", message: "arg description must be a non-empty string." });
413
+ if (arg.type === "object_array") {
414
+ if (!Number.isSafeInteger(arg.max_items) || Number(arg.max_items) < 1 || Number(arg.max_items) > SET_MAX_ROWS_HARD_CEILING)
415
+ errors.push({ path: `${argPath}.max_items`, code: "INVALID_OBJECT_ARRAY_MAX_ITEMS", message: `object_array max_items must be 1 through ${SET_MAX_ROWS_HARD_CEILING}.` });
416
+ if (!isRecord(arg.fields) || Object.keys(arg.fields).length === 0 || Object.keys(arg.fields).length > 64) {
417
+ errors.push({ path: `${argPath}.fields`, code: "OBJECT_ARRAY_FIELDS_REQUIRED", message: "object_array fields must define 1 through 64 typed item fields." });
418
+ } else {
419
+ validateArgs(arg.fields, `${argPath}.fields`, errors);
420
+ for (const [fieldName, field] of Object.entries(arg.fields)) {
421
+ if (isRecord(field) && field.type === "object_array")
422
+ errors.push({ path: `${argPath}.fields.${fieldName}`, code: "NESTED_OBJECT_ARRAY_FORBIDDEN", message: "object_array item fields must be scalar." });
423
+ }
424
+ }
425
+ for (const key of ["max_length", "minimum", "maximum", "enum"])
426
+ if (arg[key] !== void 0)
427
+ errors.push({ path: `${argPath}.${key}`, code: "OBJECT_ARRAY_SCALAR_OPTION_FORBIDDEN", message: `${key} is valid only on scalar arguments or item fields.` });
428
+ continue;
429
+ }
430
+ if (arg.max_items !== void 0 || arg.fields !== void 0)
431
+ errors.push({ path: argPath, code: "OBJECT_ARRAY_OPTIONS_REQUIRE_OBJECT_ARRAY", message: "max_items and fields require type object_array." });
432
+ if (arg.max_length !== void 0 && (!Number.isInteger(arg.max_length) || Number(arg.max_length) <= 0))
433
+ errors.push({ path: `${argPath}.max_length`, code: "INVALID_MAX_LENGTH", message: "max_length must be a positive integer." });
434
+ if ((arg.minimum !== void 0 || arg.maximum !== void 0) && arg.type !== "number")
435
+ errors.push({ path: argPath, code: "NUMERIC_BOUNDS_REQUIRE_NUMBER", message: "minimum/maximum can only be used with number args." });
436
+ if (arg.minimum !== void 0 && !isFiniteNumber(arg.minimum))
437
+ errors.push({ path: `${argPath}.minimum`, code: "INVALID_MINIMUM", message: "minimum must be a finite number." });
438
+ if (arg.maximum !== void 0 && !isFiniteNumber(arg.maximum))
439
+ errors.push({ path: `${argPath}.maximum`, code: "INVALID_MAXIMUM", message: "maximum must be a finite number." });
440
+ if (isFiniteNumber(arg.minimum) && isFiniteNumber(arg.maximum) && Number(arg.minimum) > Number(arg.maximum))
441
+ errors.push({ path: argPath, code: "INVALID_ARG_NUMERIC_RANGE", message: "minimum must be less than or equal to maximum." });
442
+ if (arg.enum !== void 0) {
443
+ if (!Array.isArray(arg.enum) || arg.enum.length === 0 || arg.enum.length > 64) {
444
+ errors.push({ path: `${argPath}.enum`, code: "INVALID_ARG_ENUM", message: "enum must contain 1 through 64 scalar values." });
445
+ } else {
446
+ const expectedType = arg.type;
447
+ const keys = /* @__PURE__ */ new Set();
448
+ arg.enum.forEach((item, enumIndex) => {
449
+ if (item === null || typeof item !== expectedType)
450
+ errors.push({ path: `${argPath}.enum[${enumIndex}]`, code: "ARG_ENUM_TYPE_MISMATCH", message: `enum values must match argument type ${String(expectedType)} and cannot be null.` });
451
+ const key = `${typeof item}:${JSON.stringify(item)}`;
452
+ if (keys.has(key))
453
+ errors.push({ path: `${argPath}.enum[${enumIndex}]`, code: "ARG_ENUM_DUPLICATE_VALUE", message: "enum values must be unique after canonicalization." });
454
+ keys.add(key);
455
+ });
456
+ }
457
+ }
458
+ }
459
+ }
460
+ function validateAggregateRead(capability, path, errors) {
461
+ const aggregate = capability.aggregate;
462
+ if (!isRecord(aggregate)) {
463
+ errors.push({ path: `${path}.aggregate`, code: "AGGREGATE_READ_REQUIRED", message: "aggregate_read requires an aggregate definition." });
464
+ return;
465
+ }
466
+ checkUnknownKeys(aggregate, AGGREGATE_READ_KEYS, `${path}.aggregate`, errors);
467
+ const fn = String(aggregate.function);
468
+ if (!["count", "sum", "avg"].includes(fn))
469
+ errors.push({ path: `${path}.aggregate.function`, code: "INVALID_AGGREGATE_FUNCTION", message: "aggregate function must be count, sum, or avg." });
470
+ if (!Number.isSafeInteger(aggregate.minimum_group_size) || Number(aggregate.minimum_group_size) < 2 || Number(aggregate.minimum_group_size) > 1e6) {
471
+ errors.push({ path: `${path}.aggregate.minimum_group_size`, code: "AGGREGATE_MINIMUM_GROUP_SIZE_REQUIRED", message: "aggregate reads require minimum_group_size from 2 through 1000000." });
472
+ }
473
+ if (fn === "count") {
474
+ if (aggregate.count_mode !== "rows" && aggregate.count_mode !== "non_null")
475
+ errors.push({ path: `${path}.aggregate.count_mode`, code: "COUNT_MODE_REQUIRED", message: "COUNT requires count_mode rows or non_null." });
476
+ if (aggregate.count_mode === "rows" && aggregate.column !== void 0)
477
+ errors.push({ path: `${path}.aggregate.column`, code: "COUNT_ROWS_COLUMN_FORBIDDEN", message: "COUNT rows must not declare a column." });
478
+ if (aggregate.count_mode === "non_null" && !isSafeIdentifier(aggregate.column))
479
+ errors.push({ path: `${path}.aggregate.column`, code: "COUNT_COLUMN_REQUIRED", message: "COUNT non_null requires a fixed column." });
480
+ } else {
481
+ if (!isSafeIdentifier(aggregate.column))
482
+ errors.push({ path: `${path}.aggregate.column`, code: "AGGREGATE_NUMERIC_COLUMN_REQUIRED", message: "SUM/AVG require a fixed reviewed numeric column." });
483
+ if (aggregate.count_mode !== void 0)
484
+ errors.push({ path: `${path}.aggregate.count_mode`, code: "COUNT_MODE_COUNT_ONLY", message: "count_mode is valid only for COUNT." });
485
+ }
486
+ if (aggregate.selection !== void 0)
487
+ validateFixedSelection(aggregate.selection, `${path}.aggregate.selection`, errors);
488
+ if (isRecord(capability.args) && Object.keys(capability.args).length > 0)
489
+ errors.push({ path: `${path}.args`, code: "AGGREGATE_MODEL_ARGS_FORBIDDEN", message: "the first aggregate-read release permits no model-controlled predicate arguments." });
490
+ if (capability.lookup !== void 0)
491
+ errors.push({ path: `${path}.lookup`, code: "AGGREGATE_LOOKUP_FORBIDDEN", message: "aggregate reads use only trusted scope and contract-fixed selection." });
492
+ if (Array.isArray(capability.visible_fields) && capability.visible_fields.length > 0)
493
+ errors.push({ path: `${path}.visible_fields`, code: "AGGREGATE_VISIBLE_ROWS_FORBIDDEN", message: "aggregate reads return no source row fields." });
494
+ if (!isRecord(capability.evidence) || capability.evidence.required !== true || capability.evidence.query_audit !== true)
495
+ errors.push({ path: `${path}.evidence`, code: "AGGREGATE_EVIDENCE_REQUIRED", message: "aggregate reads require evidence and query audit." });
496
+ }
497
+ function validateFixedSelection(value, path, errors) {
498
+ if (!isRecord(value)) {
499
+ errors.push({ path, code: "FIXED_SELECTION_REQUIRED", message: "selection must be a reviewed object." });
500
+ return;
501
+ }
502
+ checkUnknownKeys(value, SELECTION_KEYS, path, errors);
503
+ if (!Array.isArray(value.all) || value.all.length < 1 || value.all.length > 8) {
504
+ errors.push({ path: `${path}.all`, code: "INVALID_FIXED_SELECTION", message: "selection.all must contain 1 through 8 fixed equality terms." });
505
+ return;
506
+ }
507
+ value.all.forEach((term, index) => {
508
+ const termPath = `${path}.all[${index}]`;
509
+ if (!isRecord(term)) {
510
+ errors.push({ path: termPath, code: "PREDICATE_TERM_NOT_OBJECT", message: "predicate term must be an object." });
511
+ return;
512
+ }
513
+ checkUnknownKeys(term, PREDICATE_TERM_KEYS, termPath, errors);
514
+ if (!isSafeIdentifier(term.column))
515
+ errors.push({ path: `${termPath}.column`, code: "INVALID_PREDICATE_COLUMN", message: "predicate column must be a fixed safe identifier." });
516
+ if (term.operator !== "eq")
517
+ errors.push({ path: `${termPath}.operator`, code: "INVALID_PREDICATE_OPERATOR", message: "only literal equality predicates are supported." });
518
+ if (!("value" in term) || !isJsonScalar(term.value))
519
+ errors.push({ path: `${termPath}.value`, code: "FIXED_PREDICATE_VALUE_REQUIRED", message: "predicate value must be a contract literal." });
520
+ });
521
+ }
522
+ function validateLookup(value, path, args, errors) {
523
+ if (!isRecord(value)) {
524
+ errors.push({ path, code: "LOOKUP_NOT_OBJECT", message: "lookup must be an object." });
525
+ return;
526
+ }
527
+ checkUnknownKeys(value, LOOKUP_KEYS, path, errors);
528
+ if (!isSafeIdentifier(value.id_from_arg) || !isRecord(args) || !Object.prototype.hasOwnProperty.call(args, String(value.id_from_arg))) {
529
+ errors.push({ path: `${path}.id_from_arg`, code: "UNKNOWN_LOOKUP_ARG", message: "lookup.id_from_arg must reference a model-facing arg." });
530
+ }
531
+ }
532
+ function validateEvidenceRequirement(value, path, errors) {
533
+ if (!isRecord(value)) {
534
+ errors.push({ path, code: "EVIDENCE_NOT_OBJECT", message: "evidence must be an object." });
535
+ return;
536
+ }
537
+ checkUnknownKeys(value, EVIDENCE_KEYS, path, errors);
538
+ if (typeof value.required !== "boolean")
539
+ errors.push({ path: `${path}.required`, code: "INVALID_EVIDENCE_REQUIRED", message: "evidence.required must be boolean." });
540
+ if (value.sources !== void 0 && (!Array.isArray(value.sources) || value.sources.some((source) => !isNonEmptyString(source))))
541
+ errors.push({ path: `${path}.sources`, code: "INVALID_EVIDENCE_SOURCES", message: "evidence.sources must be non-empty strings." });
542
+ }
543
+ function validateProposalAction(value, subject, path, errors) {
544
+ if (!isRecord(value)) {
545
+ errors.push({ path, code: "PROPOSAL_REQUIRED", message: "proposal capabilities must define proposal action semantics." });
546
+ return;
547
+ }
548
+ checkUnknownKeys(value, PROPOSAL_KEYS, path, errors);
549
+ if (!isQualifiedOrSafeName(value.action))
550
+ errors.push({ path: `${path}.action`, code: "INVALID_PROPOSAL_ACTION", message: "proposal.action must be a safe action name." });
551
+ const operation = validateProposalOperation(value.operation, subject, value.patch, value.conflict_guard, `${path}.operation`, errors);
552
+ const deleteOperation = operation === "delete";
553
+ validateFieldList(value.allowed_fields, `${path}.allowed_fields`, "ALLOWED_FIELDS_REQUIRED", errors, deleteOperation);
554
+ if (!isRecord(value.patch) || !deleteOperation && Object.keys(value.patch).length === 0) {
555
+ errors.push({ path: `${path}.patch`, code: "PATCH_REQUIRED", message: "UPDATE and INSERT proposals must map allowed fields to fixed or arg values." });
556
+ } else if (deleteOperation && Object.keys(value.patch).length > 0) {
557
+ errors.push({ path: `${path}.patch`, code: "DELETE_PATCH_FORBIDDEN", message: "DELETE proposals must not contain a model-generated patch." });
558
+ } else {
559
+ for (const [field, patch] of Object.entries(value.patch)) {
560
+ const patchPath = `${path}.patch.${field}`;
561
+ if (!isSafeIdentifier(field))
562
+ errors.push({ path: patchPath, code: "INVALID_PATCH_FIELD", message: "patch field must be a safe identifier." });
563
+ if (Array.isArray(value.allowed_fields) && !value.allowed_fields.includes(field))
564
+ errors.push({ path: patchPath, code: "PATCH_FIELD_NOT_ALLOWED", message: "patch field must be listed in allowed_fields." });
565
+ if (!isRecord(patch)) {
566
+ errors.push({ path: patchPath, code: "PATCH_BINDING_NOT_OBJECT", message: "patch binding must be an object." });
567
+ continue;
568
+ }
569
+ checkUnknownKeys(patch, PATCH_KEYS, patchPath, errors);
570
+ const bindingCount = [patch.fixed !== void 0, isSafeIdentifier(patch.from_arg), isSafeIdentifier(patch.from_item)].filter(Boolean).length;
571
+ if (bindingCount !== 1)
572
+ errors.push({ path: patchPath, code: "PATCH_BINDING_REQUIRED", message: "patch binding must include exactly one of fixed, from_arg, or from_item." });
573
+ }
574
+ }
575
+ validateNumericBounds(value.numeric_bounds, value.patch, `${path}.numeric_bounds`, errors);
576
+ validateTransitionGuards(value.transition_guards, value.patch, `${path}.transition_guards`, errors);
577
+ if (value.reversibility !== void 0) {
578
+ if (!isRecord(value.reversibility)) {
579
+ errors.push({ path: `${path}.reversibility`, code: "REVERSIBILITY_NOT_OBJECT", message: "reversibility must be an object." });
580
+ } else {
581
+ checkUnknownKeys(value.reversibility, REVERSIBILITY_KEYS, `${path}.reversibility`, errors);
582
+ if (value.reversibility.mode !== "reviewed_inverse") {
583
+ errors.push({ path: `${path}.reversibility.mode`, code: "INVALID_REVERSIBILITY_MODE", message: "reversibility.mode must be reviewed_inverse." });
584
+ }
585
+ }
586
+ }
587
+ if (value.conflict_guard !== void 0) {
588
+ if (!isRecord(value.conflict_guard))
589
+ errors.push({ path: `${path}.conflict_guard`, code: "CONFLICT_GUARD_NOT_OBJECT", message: "conflict_guard must be an object." });
590
+ else {
591
+ checkUnknownKeys(value.conflict_guard, CONFLICT_GUARD_KEYS, `${path}.conflict_guard`, errors);
592
+ if (value.conflict_guard.column !== void 0 && !isSafeIdentifier(value.conflict_guard.column))
593
+ errors.push({ path: `${path}.conflict_guard.column`, code: "INVALID_CONFLICT_COLUMN", message: "conflict_guard.column must be a safe identifier." });
594
+ if (value.conflict_guard.column === void 0 && value.conflict_guard.weak_guard_ack !== true)
595
+ errors.push({ path: `${path}.conflict_guard`, code: "CONFLICT_GUARD_REQUIRED", message: "proposal needs conflict_guard.column unless weak_guard_ack is explicit." });
596
+ }
597
+ }
598
+ if (value.approval !== void 0) {
599
+ validateNestedObject(value.approval, APPROVAL_KEYS, `${path}.approval`, errors);
600
+ if (isRecord(value.approval) && value.approval.mode !== void 0 && !["human", "operator", "policy"].includes(String(value.approval.mode))) {
601
+ errors.push({ path: `${path}.approval.mode`, code: "INVALID_APPROVAL_MODE", message: "approval.mode must be human, operator, or policy." });
602
+ }
603
+ if (isRecord(value.approval) && value.approval.required_approvals !== void 0 && (!Number.isSafeInteger(value.approval.required_approvals) || Number(value.approval.required_approvals) < 1 || Number(value.approval.required_approvals) > 10)) {
604
+ errors.push({
605
+ path: `${path}.approval.required_approvals`,
606
+ code: "INVALID_REQUIRED_APPROVALS",
607
+ message: "approval.required_approvals must be a safe integer from 1 through 10."
608
+ });
609
+ }
610
+ }
611
+ if (value.writeback !== void 0) {
612
+ validateNestedObject(value.writeback, WRITEBACK_KEYS, `${path}.writeback`, errors);
613
+ if (isRecord(value.writeback) && !["direct_sql", "app_handler", "cloud_worker", "none"].includes(String(value.writeback.mode)))
614
+ errors.push({ path: `${path}.writeback.mode`, code: "INVALID_WRITEBACK_MODE", message: "writeback.mode must be direct_sql, app_handler, cloud_worker, or none." });
615
+ }
616
+ if (deleteOperation && isRecord(value.writeback) && value.writeback.mode === "direct_sql") {
617
+ const approvalMode = isRecord(value.approval) ? value.approval.mode : void 0;
618
+ if (approvalMode !== "human" && approvalMode !== "operator") {
619
+ errors.push({ path: `${path}.approval.mode`, code: "HARD_DELETE_HUMAN_APPROVAL_REQUIRED", message: "Direct hard DELETE must require human/operator approval and cannot use policy auto-approval." });
620
+ }
621
+ }
622
+ if (isRecord(value.operation) && value.operation.cardinality === "set") {
623
+ const approvalMode = isRecord(value.approval) ? value.approval.mode : void 0;
624
+ if (approvalMode !== "human" && approvalMode !== "operator")
625
+ errors.push({ path: `${path}.approval.mode`, code: "SET_WRITE_HUMAN_APPROVAL_REQUIRED", message: "bounded set writes require human/operator approval in the first release." });
626
+ if (!isRecord(value.writeback) || value.writeback.mode !== "direct_sql")
627
+ errors.push({ path: `${path}.writeback.mode`, code: "SET_WRITE_DIRECT_SQL_REQUIRED", message: "bounded set writes require Runner-owned direct_sql writeback." });
628
+ if (value.operation.kind === "update" && (!isRecord(value.operation.version_advance) || value.operation.version_advance.strategy !== "integer_increment"))
629
+ errors.push({ path: `${path}.operation.version_advance`, code: "SET_INTEGER_VERSION_REQUIRED", message: "bounded set UPDATE requires integer_increment version advancement." });
630
+ }
631
+ if (isRecord(value.reversibility) && value.reversibility.mode === "reviewed_inverse") {
632
+ const writebackMode = isRecord(value.writeback) ? value.writeback.mode : void 0;
633
+ const approvalMode = isRecord(value.approval) ? value.approval.mode : void 0;
634
+ if (writebackMode !== "direct_sql") {
635
+ errors.push({ path: `${path}.writeback.mode`, code: "REVERSIBILITY_DIRECT_SQL_REQUIRED", message: "reviewed inverse capture is supported only for Runner-owned direct_sql writeback." });
636
+ }
637
+ if (approvalMode !== "human" && approvalMode !== "operator") {
638
+ errors.push({ path: `${path}.approval.mode`, code: "REVERSIBILITY_HUMAN_APPROVAL_REQUIRED", message: "reversible writes require human/operator approval; policy auto-approval is not allowed." });
639
+ }
640
+ if (operation === "update") {
641
+ if (!isRecord(value.conflict_guard) || !isSafeIdentifier(value.conflict_guard.column)) {
642
+ errors.push({ path: `${path}.conflict_guard.column`, code: "REVERSIBILITY_CONFLICT_GUARD_REQUIRED", message: "reversible UPDATE requires an exact conflict_guard.column." });
643
+ }
644
+ if (!isRecord(value.operation) || !isRecord(value.operation.version_advance) || value.operation.version_advance.strategy !== "integer_increment") {
645
+ errors.push({ path: `${path}.operation.version_advance`, code: "REVERSIBILITY_INTEGER_VERSION_REQUIRED", message: "reversible UPDATE requires integer_increment version advancement so compensation advances rather than rewinds concurrency state." });
646
+ }
647
+ }
648
+ if (operation === "insert" && isRecord(subject)) {
649
+ const primaryKey = subject.primary_key;
650
+ const components = isRecord(value.operation) && isRecord(value.operation.deduplication) && Array.isArray(value.operation.deduplication.components) ? value.operation.deduplication.components : [];
651
+ if (!isSafeIdentifier(primaryKey) || !components.some((component) => isRecord(component) && component.column === primaryKey)) {
652
+ errors.push({ path: `${path}.operation.deduplication.components`, code: "REVERSIBILITY_PRIMARY_KEY_DEDUP_REQUIRED", message: "reversible INSERT requires a deterministic primary-key component in its reviewed deduplication key." });
653
+ }
654
+ }
655
+ }
656
+ }
657
+ function validateProposalOperation(value, subject, patch, conflictGuard, path, errors) {
658
+ if (value === void 0)
659
+ return "update";
660
+ if (!isRecord(value)) {
661
+ errors.push({ path, code: "OPERATION_NOT_OBJECT", message: "proposal.operation must be an object." });
662
+ return "update";
663
+ }
664
+ checkUnknownKeys(value, OPERATION_KEYS, path, errors);
665
+ const kind = value.kind;
666
+ if (kind !== "update" && kind !== "insert" && kind !== "delete") {
667
+ errors.push({ path: `${path}.kind`, code: "INVALID_OPERATION_KIND", message: "operation.kind must be update, insert, or delete." });
668
+ return "update";
669
+ }
670
+ const cardinality = value.cardinality ?? "single";
671
+ if (cardinality !== "single" && cardinality !== "set") {
672
+ errors.push({ path: `${path}.cardinality`, code: "INVALID_OPERATION_CARDINALITY", message: "operation.cardinality must be single or set." });
673
+ }
674
+ if (cardinality === "set") {
675
+ validateSetOperation(value, kind, path, errors);
676
+ } else {
677
+ for (const key of ["selection", "max_rows", "aggregate_bounds", "batch"]) {
678
+ if (value[key] !== void 0)
679
+ errors.push({ path: `${path}.${key}`, code: "SET_FIELD_REQUIRES_SET_CARDINALITY", message: `${key} requires operation.cardinality set.` });
680
+ }
681
+ }
682
+ if (value.version_advance !== void 0) {
683
+ if (!isRecord(value.version_advance)) {
684
+ errors.push({ path: `${path}.version_advance`, code: "VERSION_ADVANCE_NOT_OBJECT", message: "version_advance must be an object." });
685
+ } else {
686
+ checkUnknownKeys(value.version_advance, VERSION_ADVANCE_KEYS, `${path}.version_advance`, errors);
687
+ if (!isSafeIdentifier(value.version_advance.column))
688
+ errors.push({ path: `${path}.version_advance.column`, code: "INVALID_VERSION_ADVANCE_COLUMN", message: "version_advance.column must be a fixed safe identifier." });
689
+ if (value.version_advance.strategy !== "integer_increment" && value.version_advance.strategy !== "database_generated")
690
+ errors.push({ path: `${path}.version_advance.strategy`, code: "INVALID_VERSION_ADVANCE_STRATEGY", message: "version_advance.strategy must be integer_increment or database_generated." });
691
+ const conflictColumn = isRecord(conflictGuard) ? conflictGuard.column : void 0;
692
+ if (isSafeIdentifier(value.version_advance.column) && value.version_advance.column !== conflictColumn)
693
+ errors.push({ path: `${path}.version_advance.column`, code: "VERSION_ADVANCE_GUARD_MISMATCH", message: "version_advance.column must match conflict_guard.column." });
694
+ }
695
+ if (kind !== "update")
696
+ errors.push({ path: `${path}.version_advance`, code: "VERSION_ADVANCE_UPDATE_ONLY", message: "version_advance is valid only for UPDATE." });
697
+ }
698
+ if (kind === "insert") {
699
+ validateDeduplication(value.deduplication, subject, patch, `${path}.deduplication`, errors, cardinality === "set");
700
+ } else if (value.deduplication !== void 0) {
701
+ errors.push({ path: `${path}.deduplication`, code: "DEDUPLICATION_INSERT_ONLY", message: "deduplication is valid only for INSERT." });
702
+ }
703
+ if (kind === "delete") {
704
+ if (!isRecord(conflictGuard) || !isSafeIdentifier(conflictGuard.column))
705
+ errors.push({ path: `${path.replace(/\.operation$/, "")}.conflict_guard.column`, code: "DELETE_CONFLICT_GUARD_REQUIRED", message: "DELETE requires an exact conflict_guard.column." });
706
+ }
707
+ return kind;
708
+ }
709
+ function validateSetOperation(value, kind, path, errors) {
710
+ if (!Number.isSafeInteger(value.max_rows) || Number(value.max_rows) < 1 || Number(value.max_rows) > SET_MAX_ROWS_HARD_CEILING) {
711
+ errors.push({ path: `${path}.max_rows`, code: "SET_MAX_ROWS_REQUIRED", message: `bounded set writes require max_rows from 1 through ${SET_MAX_ROWS_HARD_CEILING}.` });
712
+ }
713
+ if (!Array.isArray(value.aggregate_bounds) || value.aggregate_bounds.length === 0 || value.aggregate_bounds.length > 8) {
714
+ errors.push({ path: `${path}.aggregate_bounds`, code: "SET_AGGREGATE_BOUND_REQUIRED", message: "bounded set writes require 1 through 8 aggregate value bounds." });
715
+ } else {
716
+ value.aggregate_bounds.forEach((bound, index) => {
717
+ const boundPath = `${path}.aggregate_bounds[${index}]`;
718
+ if (!isRecord(bound)) {
719
+ errors.push({ path: boundPath, code: "AGGREGATE_BOUND_NOT_OBJECT", message: "aggregate bound must be an object." });
720
+ return;
721
+ }
722
+ checkUnknownKeys(bound, AGGREGATE_BOUND_KEYS, boundPath, errors);
723
+ if (!isSafeIdentifier(bound.column))
724
+ errors.push({ path: `${boundPath}.column`, code: "INVALID_AGGREGATE_BOUND_COLUMN", message: "aggregate bound column must be a fixed safe identifier." });
725
+ if (!["before", "after", "absolute_delta"].includes(String(bound.measure)))
726
+ errors.push({ path: `${boundPath}.measure`, code: "INVALID_AGGREGATE_BOUND_MEASURE", message: "aggregate bound measure must be before, after, or absolute_delta." });
727
+ if (!isFiniteNumber(bound.maximum) || Number(bound.maximum) < 0)
728
+ errors.push({ path: `${boundPath}.maximum`, code: "INVALID_AGGREGATE_BOUND_MAXIMUM", message: "aggregate bound maximum must be a finite non-negative number." });
729
+ });
730
+ }
731
+ if (kind === "insert") {
732
+ if (value.selection !== void 0)
733
+ errors.push({ path: `${path}.selection`, code: "BATCH_INSERT_SELECTION_FORBIDDEN", message: "batch INSERT reviews explicit items and cannot use a selection predicate." });
734
+ if (!isRecord(value.batch))
735
+ errors.push({ path: `${path}.batch`, code: "BATCH_INSERT_ITEMS_REQUIRED", message: "batch INSERT requires batch.items_from_arg." });
736
+ else {
737
+ checkUnknownKeys(value.batch, BATCH_KEYS, `${path}.batch`, errors);
738
+ if (!isSafeIdentifier(value.batch.items_from_arg))
739
+ errors.push({ path: `${path}.batch.items_from_arg`, code: "INVALID_BATCH_ITEMS_ARG", message: "batch.items_from_arg must be a fixed argument name." });
740
+ }
741
+ return;
742
+ }
743
+ if (value.batch !== void 0)
744
+ errors.push({ path: `${path}.batch`, code: "BATCH_INSERT_ONLY", message: "batch is valid only for set INSERT." });
745
+ if (!isRecord(value.selection)) {
746
+ errors.push({ path: `${path}.selection`, code: "SET_FIXED_SELECTION_REQUIRED", message: "set UPDATE/DELETE requires a contract-fixed typed selection." });
747
+ return;
748
+ }
749
+ checkUnknownKeys(value.selection, SELECTION_KEYS, `${path}.selection`, errors);
750
+ if (!Array.isArray(value.selection.all) || value.selection.all.length === 0 || value.selection.all.length > 8) {
751
+ errors.push({ path: `${path}.selection.all`, code: "INVALID_FIXED_SELECTION", message: "selection.all must contain 1 through 8 fixed predicate terms." });
752
+ return;
753
+ }
754
+ value.selection.all.forEach((term, index) => {
755
+ const termPath = `${path}.selection.all[${index}]`;
756
+ if (!isRecord(term)) {
757
+ errors.push({ path: termPath, code: "PREDICATE_TERM_NOT_OBJECT", message: "predicate term must be an object." });
758
+ return;
759
+ }
760
+ checkUnknownKeys(term, PREDICATE_TERM_KEYS, termPath, errors);
761
+ if (!isSafeIdentifier(term.column))
762
+ errors.push({ path: `${termPath}.column`, code: "INVALID_PREDICATE_COLUMN", message: "predicate column must be a fixed safe identifier." });
763
+ if (term.operator !== "eq")
764
+ errors.push({ path: `${termPath}.operator`, code: "INVALID_PREDICATE_OPERATOR", message: "the first bounded-set release supports only literal equality predicates." });
765
+ if (!("value" in term) || !isJsonScalar(term.value))
766
+ errors.push({ path: `${termPath}.value`, code: "FIXED_PREDICATE_VALUE_REQUIRED", message: "predicate value must be a contract literal." });
767
+ });
768
+ }
769
+ function validateDeduplication(value, subject, patch, path, errors, batch = false) {
770
+ if (!isRecord(value)) {
771
+ errors.push({ path, code: "INSERT_DEDUPLICATION_REQUIRED", message: "INSERT requires source-enforced deduplication components." });
772
+ return;
773
+ }
774
+ checkUnknownKeys(value, DEDUPLICATION_KEYS, path, errors);
775
+ if (!Array.isArray(value.components) || value.components.length === 0 || value.components.length > 8) {
776
+ errors.push({ path: `${path}.components`, code: "INVALID_DEDUPLICATION_COMPONENTS", message: "deduplication.components must contain 1 through 8 fixed components." });
777
+ return;
778
+ }
779
+ const seen = /* @__PURE__ */ new Set();
780
+ let hasProposalId = false;
781
+ let hasItemField = false;
782
+ let hasTrustedTenant = false;
783
+ const patchFields = isRecord(patch) ? new Set(Object.keys(patch)) : /* @__PURE__ */ new Set();
784
+ const tenantKey = isRecord(subject) ? subject.tenant_key : void 0;
785
+ value.components.forEach((component, index) => {
786
+ const componentPath = `${path}.components[${index}]`;
787
+ if (!isRecord(component)) {
788
+ errors.push({ path: componentPath, code: "DEDUPLICATION_COMPONENT_NOT_OBJECT", message: "deduplication component must be an object." });
789
+ return;
790
+ }
791
+ checkUnknownKeys(component, DEDUPLICATION_COMPONENT_KEYS, componentPath, errors);
792
+ if (!isSafeIdentifier(component.column))
793
+ errors.push({ path: `${componentPath}.column`, code: "INVALID_DEDUPLICATION_COLUMN", message: "deduplication column must be a fixed safe identifier." });
794
+ else if (seen.has(component.column))
795
+ errors.push({ path: `${componentPath}.column`, code: "DUPLICATE_DEDUPLICATION_COLUMN", message: "deduplication columns must be unique." });
796
+ else
797
+ seen.add(component.column);
798
+ if (patchFields.has(String(component.column)))
799
+ errors.push({ path: `${componentPath}.column`, code: "DEDUPLICATION_COLUMN_MODEL_CONTROLLED", message: "deduplication columns are Runner-supplied and must not also be patch fields." });
800
+ if (component.source !== "proposal_id" && component.source !== "trusted_tenant" && component.source !== "fixed" && component.source !== "item_field")
801
+ errors.push({ path: `${componentPath}.source`, code: "INVALID_DEDUPLICATION_SOURCE", message: "deduplication source must be proposal_id, trusted_tenant, fixed, or item_field." });
802
+ if (component.source === "proposal_id")
803
+ hasProposalId = true;
804
+ if (component.source === "item_field") {
805
+ hasItemField = true;
806
+ if (!isSafeIdentifier(component.item_field))
807
+ errors.push({ path: `${componentPath}.item_field`, code: "DEDUPLICATION_ITEM_FIELD_REQUIRED", message: "item_field deduplication requires a fixed item field name." });
808
+ }
809
+ if (component.source === "trusted_tenant") {
810
+ if (isSafeIdentifier(tenantKey) && component.column === tenantKey)
811
+ hasTrustedTenant = true;
812
+ else
813
+ errors.push({ path: `${componentPath}.column`, code: "DEDUPLICATION_TENANT_MISMATCH", message: "trusted_tenant deduplication must map to subject.tenant_key." });
814
+ }
815
+ if (component.source === "fixed" && component.fixed === void 0)
816
+ errors.push({ path: `${componentPath}.fixed`, code: "DEDUPLICATION_FIXED_VALUE_REQUIRED", message: "fixed deduplication components require fixed." });
817
+ if (component.source !== "fixed" && component.fixed !== void 0)
818
+ errors.push({ path: `${componentPath}.fixed`, code: "DEDUPLICATION_FIXED_VALUE_FORBIDDEN", message: "fixed is valid only when source is fixed." });
819
+ if (component.source !== "item_field" && component.item_field !== void 0)
820
+ errors.push({ path: `${componentPath}.item_field`, code: "DEDUPLICATION_ITEM_FIELD_FORBIDDEN", message: "item_field is valid only when source is item_field." });
821
+ });
822
+ if (batch ? !hasItemField : !hasProposalId)
823
+ errors.push({ path: `${path}.components`, code: batch ? "ITEM_DEDUPLICATION_REQUIRED" : "PROPOSAL_DEDUPLICATION_REQUIRED", message: batch ? "batch INSERT deduplication must include a source-unique item_field component." : "INSERT deduplication must include a proposal_id component so retries are source-identifiable." });
824
+ if (!hasTrustedTenant)
825
+ errors.push({ path: `${path}.components`, code: "TRUSTED_TENANT_DEDUPLICATION_REQUIRED", message: "INSERT deduplication must include subject.tenant_key from trusted_tenant so retries cannot cross tenant scope." });
826
+ }
827
+ function validateNumericBounds(value, patch, path, errors) {
828
+ if (value === void 0)
829
+ return;
830
+ if (!isRecord(value)) {
831
+ errors.push({ path, code: "NUMERIC_BOUNDS_NOT_OBJECT", message: "numeric_bounds must map patch fields to numeric bounds." });
832
+ return;
833
+ }
834
+ const patchFields = isRecord(patch) ? new Set(Object.keys(patch)) : /* @__PURE__ */ new Set();
835
+ for (const [field, bounds] of Object.entries(value)) {
836
+ const boundPath = `${path}.${field}`;
837
+ if (!isSafeIdentifier(field))
838
+ errors.push({ path: boundPath, code: "INVALID_NUMERIC_BOUND_FIELD", message: "numeric_bounds keys must be safe patch fields." });
839
+ if (!patchFields.has(field))
840
+ errors.push({ path: boundPath, code: "NUMERIC_BOUND_PATCH_FIELD_REQUIRED", message: "numeric_bounds can only constrain fields in proposal.patch." });
841
+ if (!isRecord(bounds)) {
842
+ errors.push({ path: boundPath, code: "NUMERIC_BOUND_NOT_OBJECT", message: "numeric bound must be an object." });
843
+ continue;
844
+ }
845
+ checkUnknownKeys(bounds, NUMERIC_BOUND_KEYS, boundPath, errors);
846
+ const hasMinimum = bounds.minimum !== void 0;
847
+ const hasMaximum = bounds.maximum !== void 0;
848
+ if (!hasMinimum && !hasMaximum)
849
+ errors.push({ path: boundPath, code: "NUMERIC_BOUND_EMPTY", message: "numeric bound must define minimum, maximum, or both." });
850
+ if (hasMinimum && !isFiniteNumber(bounds.minimum))
851
+ errors.push({ path: `${boundPath}.minimum`, code: "INVALID_MINIMUM", message: "minimum must be a finite number." });
852
+ if (hasMaximum && !isFiniteNumber(bounds.maximum))
853
+ errors.push({ path: `${boundPath}.maximum`, code: "INVALID_MAXIMUM", message: "maximum must be a finite number." });
854
+ if (isFiniteNumber(bounds.minimum) && isFiniteNumber(bounds.maximum) && Number(bounds.minimum) > Number(bounds.maximum)) {
855
+ errors.push({ path: boundPath, code: "INVALID_NUMERIC_RANGE", message: "minimum must be less than or equal to maximum." });
856
+ }
857
+ }
858
+ }
859
+ function validateTransitionGuards(value, patch, path, errors) {
860
+ if (value === void 0)
861
+ return;
862
+ if (!isRecord(value)) {
863
+ errors.push({ path, code: "TRANSITION_GUARDS_NOT_OBJECT", message: "transition_guards must map patch fields to allowed state transitions." });
864
+ return;
865
+ }
866
+ const patchFields = isRecord(patch) ? new Set(Object.keys(patch)) : /* @__PURE__ */ new Set();
867
+ for (const [field, guard] of Object.entries(value)) {
868
+ const guardPath = `${path}.${field}`;
869
+ if (!isSafeIdentifier(field))
870
+ errors.push({ path: guardPath, code: "INVALID_TRANSITION_GUARD_FIELD", message: "transition_guards keys must be safe patch fields." });
871
+ if (!patchFields.has(field))
872
+ errors.push({ path: guardPath, code: "TRANSITION_GUARD_PATCH_FIELD_REQUIRED", message: "transition_guards can only constrain fields in proposal.patch." });
873
+ if (!isRecord(guard)) {
874
+ errors.push({ path: guardPath, code: "TRANSITION_GUARD_NOT_OBJECT", message: "transition guard must be an object." });
875
+ continue;
876
+ }
877
+ checkUnknownKeys(guard, TRANSITION_GUARD_KEYS, guardPath, errors);
878
+ if (guard.from_column !== void 0 && !isSafeIdentifier(guard.from_column)) {
879
+ errors.push({ path: `${guardPath}.from_column`, code: "INVALID_TRANSITION_FROM_COLUMN", message: "from_column must be a safe identifier." });
880
+ }
881
+ if (!isRecord(guard.allowed) || Object.keys(guard.allowed).length === 0) {
882
+ errors.push({ path: `${guardPath}.allowed`, code: "TRANSITION_ALLOWED_REQUIRED", message: "transition guard must define allowed transitions." });
883
+ continue;
884
+ }
885
+ for (const [from, toValues] of Object.entries(guard.allowed)) {
886
+ const allowedPath = `${guardPath}.allowed.${from}`;
887
+ if (!isNonEmptyString(from))
888
+ errors.push({ path: allowedPath, code: "TRANSITION_FROM_REQUIRED", message: "transition source state must be a non-empty string." });
889
+ if (!Array.isArray(toValues) || toValues.length === 0 || toValues.some((item) => !isNonEmptyString(item))) {
890
+ errors.push({ path: allowedPath, code: "TRANSITION_TO_VALUES_REQUIRED", message: "transition target states must be non-empty strings." });
891
+ }
892
+ }
893
+ }
894
+ }
895
+ function validateWorkflows(value, contextNames, capabilityNames, errors) {
896
+ if (value === void 0)
897
+ return;
898
+ if (!Array.isArray(value)) {
899
+ errors.push({ path: "$.workflows", code: "WORKFLOWS_NOT_ARRAY", message: "workflows must be an array." });
900
+ return;
901
+ }
902
+ const names = /* @__PURE__ */ new Set();
903
+ value.forEach((workflow, index) => {
904
+ const path = `$.workflows[${index}]`;
905
+ if (!isRecord(workflow)) {
906
+ errors.push({ path, code: "WORKFLOW_NOT_OBJECT", message: "workflow must be an object." });
907
+ return;
908
+ }
909
+ checkUnknownKeys(workflow, WORKFLOW_KEYS, path, errors);
910
+ if (!isQualifiedName(workflow.name))
911
+ errors.push({ path: `${path}.name`, code: "INVALID_WORKFLOW_NAME", message: "workflow name must be namespace.name." });
912
+ else
913
+ addUnique(names, workflow.name, `${path}.name`, "DUPLICATE_WORKFLOW_NAME", errors);
914
+ if (!isNonEmptyString(workflow.context) || !contextNames.has(workflow.context))
915
+ errors.push({ path: `${path}.context`, code: "UNKNOWN_CONTEXT", message: "workflow.context must reference a declared context." });
916
+ if (!Array.isArray(workflow.allowed_capabilities) || workflow.allowed_capabilities.length === 0) {
917
+ errors.push({ path: `${path}.allowed_capabilities`, code: "WORKFLOW_CAPABILITIES_REQUIRED", message: "workflow.allowed_capabilities must list allowed capabilities." });
918
+ } else {
919
+ workflow.allowed_capabilities.forEach((name, allowedIndex) => {
920
+ if (!isNonEmptyString(name) || !capabilityNames.has(name))
921
+ errors.push({ path: `${path}.allowed_capabilities[${allowedIndex}]`, code: "UNKNOWN_CAPABILITY", message: "workflow allowed capability must reference a declared capability." });
922
+ });
923
+ }
924
+ });
925
+ }
926
+ function validatePolicies(value, errors) {
927
+ const names = /* @__PURE__ */ new Map();
928
+ if (value === void 0)
929
+ return names;
930
+ if (!Array.isArray(value)) {
931
+ errors.push({ path: "$.policies", code: "POLICIES_NOT_ARRAY", message: "$.policies must be an array." });
932
+ return names;
933
+ }
934
+ value.forEach((policy, index) => {
935
+ const path = `$.policies[${index}]`;
936
+ if (!isRecord(policy)) {
937
+ errors.push({ path, code: "POLICY_NOT_OBJECT", message: "policy entry must be an object." });
938
+ return;
939
+ }
940
+ checkUnknownKeys(policy, POLICY_KEYS, path, errors);
941
+ if (!isQualifiedOrSafeName(policy.name))
942
+ errors.push({ path: `${path}.name`, code: "INVALID_POLICY_NAME", message: "policy name must be a safe identifier or qualified name." });
943
+ else {
944
+ if (names.has(String(policy.name)))
945
+ errors.push({ path: `${path}.name`, code: "DUPLICATE_POLICY_NAME", message: `Duplicate name: ${String(policy.name)}` });
946
+ names.set(String(policy.name), policy);
947
+ }
948
+ if (!["approval", "settlement", "scope", "custom"].includes(String(policy.kind)))
949
+ errors.push({ path: `${path}.kind`, code: "INVALID_POLICY_KIND", message: "policy.kind must be approval, settlement, scope, or custom." });
950
+ if (policy.mode !== void 0 && !["green", "yellow", "red", "manual", "block"].includes(String(policy.mode)))
951
+ errors.push({ path: `${path}.mode`, code: "INVALID_POLICY_MODE", message: "policy.mode must be green, yellow, red, manual, or block." });
952
+ if (policy.rules !== void 0) {
953
+ if (!Array.isArray(policy.rules)) {
954
+ errors.push({ path: `${path}.rules`, code: "POLICY_RULES_NOT_ARRAY", message: "policy.rules must be an array." });
955
+ } else if (policy.kind === "approval") {
956
+ policy.rules.forEach((rule, ruleIndex) => validateApprovalPolicyRuleShape(rule, `${path}.rules[${ruleIndex}]`, errors));
957
+ }
958
+ }
959
+ if (policy.limits !== void 0) {
960
+ if (!Array.isArray(policy.limits) || policy.limits.length === 0) {
961
+ errors.push({ path: `${path}.limits`, code: "APPROVAL_POLICY_LIMITS_NOT_ARRAY", message: "approval policy limits must be a non-empty array." });
962
+ } else if (policy.kind !== "approval") {
963
+ errors.push({ path: `${path}.limits`, code: "APPROVAL_POLICY_LIMITS_KIND_REQUIRED", message: "aggregate limits are supported only for approval policies." });
964
+ } else {
965
+ policy.limits.forEach((limit, limitIndex) => validateApprovalPolicyLimitShape(limit, `${path}.limits[${limitIndex}]`, errors));
966
+ }
967
+ }
968
+ });
969
+ return names;
970
+ }
971
+ function validateApprovalPolicyLimitShape(limit, path, errors) {
972
+ if (!isRecord(limit)) {
973
+ errors.push({ path, code: "APPROVAL_POLICY_LIMIT_NOT_OBJECT", message: "approval policy limits must be objects." });
974
+ return;
975
+ }
976
+ checkUnknownKeys(limit, APPROVAL_POLICY_LIMIT_KEYS, path, errors);
977
+ if (limit.kind !== "count" && limit.kind !== "total") {
978
+ errors.push({ path: `${path}.kind`, code: "INVALID_APPROVAL_POLICY_LIMIT_KIND", message: "approval policy limit kind must be count or total." });
979
+ }
980
+ if (!Number.isSafeInteger(limit.max) || Number(limit.max) < 0) {
981
+ errors.push({ path: `${path}.max`, code: "INVALID_APPROVAL_POLICY_LIMIT_MAX", message: "approval policy limit max must be a safe non-negative integer." });
982
+ }
983
+ if (limit.period !== "day") {
984
+ errors.push({ path: `${path}.period`, code: "INVALID_APPROVAL_POLICY_LIMIT_PERIOD", message: "approval policy limit period must be day." });
985
+ }
986
+ if (limit.scope !== void 0 && limit.scope !== "tenant_policy" && limit.scope !== "tenant_policy_object") {
987
+ errors.push({ path: `${path}.scope`, code: "INVALID_APPROVAL_POLICY_LIMIT_SCOPE", message: "approval policy limit scope must be tenant_policy or tenant_policy_object." });
988
+ }
989
+ if (limit.kind === "total" && !isSafeIdentifier(limit.field)) {
990
+ errors.push({ path: `${path}.field`, code: "APPROVAL_POLICY_TOTAL_FIELD_REQUIRED", message: "total approval limits require a safe numeric field." });
991
+ }
992
+ if (limit.kind === "count" && limit.field !== void 0) {
993
+ errors.push({ path: `${path}.field`, code: "APPROVAL_POLICY_COUNT_FIELD_FORBIDDEN", message: "count approval limits must not declare a field." });
994
+ }
995
+ }
996
+ function validateApprovalPolicyRuleShape(rule, path, errors) {
997
+ if (!isRecord(rule)) {
998
+ errors.push({ path, code: "APPROVAL_POLICY_RULE_NOT_OBJECT", message: "approval policy rules must be objects." });
999
+ return;
1000
+ }
1001
+ const keys = /* @__PURE__ */ new Set(["field", "max"]);
1002
+ checkUnknownKeys(rule, keys, path, errors);
1003
+ if (!isSafeIdentifier(rule.field))
1004
+ errors.push({ path: `${path}.field`, code: "INVALID_APPROVAL_POLICY_FIELD", message: "approval policy rule field must be a safe identifier." });
1005
+ if (!Number.isInteger(rule.max) || Number(rule.max) < 0)
1006
+ errors.push({ path: `${path}.max`, code: "INVALID_APPROVAL_POLICY_MAX", message: "approval policy rule max must be a non-negative integer." });
1007
+ }
1008
+ function validateCapabilityApprovalPolicies(capabilities, policies, policyByName, errors) {
1009
+ const policyIndexByName = /* @__PURE__ */ new Map();
1010
+ policies.forEach((policy, index) => {
1011
+ if (isRecord(policy) && typeof policy.name === "string")
1012
+ policyIndexByName.set(policy.name, index);
1013
+ });
1014
+ capabilities.forEach((capability, index) => {
1015
+ if (!isRecord(capability) || !isRecord(capability.proposal))
1016
+ return;
1017
+ const proposal = capability.proposal;
1018
+ const approval = proposal.approval;
1019
+ if (!isRecord(approval))
1020
+ return;
1021
+ const path = `$.capabilities[${index}].proposal.approval`;
1022
+ const mode = approval.mode;
1023
+ const policyName = approval.policy;
1024
+ if (mode === "policy") {
1025
+ if (!isNonEmptyString(approval.required_role)) {
1026
+ errors.push({ path: `${path}.required_role`, code: "APPROVAL_POLICY_ROLE_REQUIRED", message: "approval.mode policy still requires required_role for human fallback." });
1027
+ }
1028
+ if (!isQualifiedOrSafeName(policyName)) {
1029
+ errors.push({ path: `${path}.policy`, code: "APPROVAL_POLICY_REQUIRED", message: "approval.mode policy requires approval.policy." });
1030
+ return;
1031
+ }
1032
+ const policy = policyByName.get(policyName);
1033
+ if (!policy) {
1034
+ errors.push({ path: `${path}.policy`, code: "UNKNOWN_APPROVAL_POLICY", message: `approval.policy must reference an existing approval policy: ${policyName}` });
1035
+ return;
1036
+ }
1037
+ if (policy.kind !== "approval") {
1038
+ errors.push({ path: `${path}.policy`, code: "APPROVAL_POLICY_KIND_REQUIRED", message: "approval.policy must reference a policy with kind approval." });
1039
+ return;
1040
+ }
1041
+ validateApprovalPolicyRulesAgainstCapability(policy, policyIndexByName.get(policyName) ?? 0, capability, index, errors);
1042
+ } else if (policyName !== void 0) {
1043
+ errors.push({ path: `${path}.policy`, code: "APPROVAL_POLICY_MODE_REQUIRED", message: "approval.policy can only be set when approval.mode is policy." });
1044
+ }
1045
+ });
1046
+ }
1047
+ function validateApprovalPolicyRulesAgainstCapability(policy, policyIndex, capability, capabilityIndex, errors) {
1048
+ const proposal = capability.proposal;
1049
+ if (!proposal)
1050
+ return;
1051
+ if (Array.isArray(policy.rules))
1052
+ policy.rules.forEach((rule, ruleIndex) => {
1053
+ if (!isRecord(rule))
1054
+ return;
1055
+ const field = rule.field;
1056
+ const max = rule.max;
1057
+ const rulePath = `$.policies[${policyIndex}].rules[${ruleIndex}]`;
1058
+ if (!isSafeIdentifier(field) || !Number.isInteger(max))
1059
+ return;
1060
+ if (!isNumericProposalField(proposal, capability.args, field)) {
1061
+ errors.push({
1062
+ path: `${rulePath}.field`,
1063
+ code: "APPROVAL_POLICY_FIELD_NOT_NUMERIC",
1064
+ message: `approval policy field ${field} must be numeric for capability ${capability.name}.`
1065
+ });
1066
+ }
1067
+ const bound = proposal.numeric_bounds?.[field];
1068
+ if (bound?.maximum !== void 0 && Number(max) > Number(bound.maximum)) {
1069
+ errors.push({
1070
+ path: `${rulePath}.max`,
1071
+ code: "APPROVAL_POLICY_MAX_EXCEEDS_BOUND",
1072
+ message: `approval policy max for ${field} must be <= numeric_bounds maximum on $.capabilities[${capabilityIndex}].proposal.numeric_bounds.${field}.maximum.`
1073
+ });
1074
+ }
1075
+ });
1076
+ if (Array.isArray(policy.limits))
1077
+ policy.limits.forEach((limit, limitIndex) => {
1078
+ if (!isRecord(limit) || limit.kind !== "total" || !isSafeIdentifier(limit.field))
1079
+ return;
1080
+ if (!isNumericProposalField(proposal, capability.args, limit.field)) {
1081
+ errors.push({
1082
+ path: `$.policies[${policyIndex}].limits[${limitIndex}].field`,
1083
+ code: "APPROVAL_POLICY_TOTAL_FIELD_NOT_NUMERIC",
1084
+ message: `aggregate approval total field ${limit.field} must be numeric for capability ${capability.name}.`
1085
+ });
1086
+ }
1087
+ });
1088
+ }
1089
+ function isNumericProposalField(proposal, args, field) {
1090
+ if (proposal.numeric_bounds?.[field] !== void 0)
1091
+ return true;
1092
+ const patch = proposal.patch?.[field];
1093
+ if (!patch)
1094
+ return false;
1095
+ if (typeof patch.fixed === "number" && Number.isInteger(patch.fixed))
1096
+ return true;
1097
+ if (patch.from_arg && args[patch.from_arg]?.type === "number")
1098
+ return true;
1099
+ return false;
1100
+ }
1101
+ function validateEvidenceRecords(value, errors) {
1102
+ if (value === void 0)
1103
+ return;
1104
+ validateArrayRecords(value, "$.evidence", EVIDENCE_RECORD_KEYS, "EVIDENCE", errors);
1105
+ }
1106
+ function validateProposalRecords(value, capabilityNames, errors) {
1107
+ if (value === void 0)
1108
+ return;
1109
+ if (!Array.isArray(value)) {
1110
+ errors.push({ path: "$.proposals", code: "PROPOSALS_NOT_ARRAY", message: "proposals must be an array." });
1111
+ return;
1112
+ }
1113
+ value.forEach((proposal, index) => {
1114
+ const path = `$.proposals[${index}]`;
1115
+ if (!isRecord(proposal)) {
1116
+ errors.push({ path, code: "PROPOSAL_NOT_OBJECT", message: "proposal must be an object." });
1117
+ return;
1118
+ }
1119
+ checkUnknownKeys(proposal, PROPOSAL_RECORD_KEYS, path, errors);
1120
+ if (!isNonEmptyString(proposal.id))
1121
+ errors.push({ path: `${path}.id`, code: "INVALID_PROPOSAL_ID", message: "proposal.id must be non-empty." });
1122
+ if (!isNonEmptyString(proposal.capability) || capabilityNames.size > 0 && !capabilityNames.has(String(proposal.capability)))
1123
+ errors.push({ path: `${path}.capability`, code: "UNKNOWN_CAPABILITY", message: "proposal.capability must reference a declared capability." });
1124
+ });
1125
+ }
1126
+ function validateReceiptRecords(value, errors) {
1127
+ if (value === void 0)
1128
+ return;
1129
+ validateArrayRecords(value, "$.receipts", RECEIPT_KEYS, "RECEIPT", errors);
1130
+ }
1131
+ function validateReplay(value, errors) {
1132
+ if (value === void 0)
1133
+ return;
1134
+ if (!Array.isArray(value)) {
1135
+ errors.push({ path: "$.replay", code: "REPLAY_NOT_ARRAY", message: "replay must be an array." });
1136
+ return;
1137
+ }
1138
+ value.forEach((record, index) => {
1139
+ const path = `$.replay[${index}]`;
1140
+ if (!isRecord(record)) {
1141
+ errors.push({ path, code: "REPLAY_RECORD_NOT_OBJECT", message: "replay record must be an object." });
1142
+ return;
1143
+ }
1144
+ checkUnknownKeys(record, REPLAY_KEYS, path, errors);
1145
+ if (!isNonEmptyString(record.id))
1146
+ errors.push({ path: `${path}.id`, code: "INVALID_REPLAY_ID", message: "replay id must be non-empty." });
1147
+ if (!Array.isArray(record.events))
1148
+ errors.push({ path: `${path}.events`, code: "REPLAY_EVENTS_REQUIRED", message: "replay.events must be an array." });
1149
+ });
1150
+ }
1151
+ function validateExternalActions(value, errors) {
1152
+ if (value === void 0)
1153
+ return;
1154
+ validateArrayRecords(value, "$.external_actions", EXTERNAL_ACTION_KEYS, "EXTERNAL_ACTION", errors);
1155
+ }
1156
+ function validateArrayRecords(value, path, keys, label, errors) {
1157
+ if (!Array.isArray(value)) {
1158
+ errors.push({ path, code: `${label}_NOT_ARRAY`, message: `${path} must be an array.` });
1159
+ return;
1160
+ }
1161
+ value.forEach((record, index) => {
1162
+ const itemPath = `${path}[${index}]`;
1163
+ if (!isRecord(record)) {
1164
+ errors.push({ path: itemPath, code: `${label}_NOT_OBJECT`, message: `${label.toLowerCase()} entry must be an object.` });
1165
+ return;
1166
+ }
1167
+ checkUnknownKeys(record, keys, itemPath, errors);
1168
+ });
1169
+ }
1170
+ function validateFieldList(value, path, code, errors, allowEmpty = false) {
1171
+ if (!Array.isArray(value) || !allowEmpty && value.length === 0) {
1172
+ errors.push({ path, code, message: "field list must be a non-empty array of safe identifiers." });
1173
+ return;
1174
+ }
1175
+ const seen = /* @__PURE__ */ new Set();
1176
+ value.forEach((field, index) => {
1177
+ if (!isSafeIdentifier(field))
1178
+ errors.push({ path: `${path}[${index}]`, code: "INVALID_FIELD_IDENTIFIER", message: "field names must be fixed safe identifiers." });
1179
+ else
1180
+ addUnique(seen, field, `${path}[${index}]`, "DUPLICATE_FIELD", errors);
1181
+ });
1182
+ }
1183
+ function validateKeptOutExclusion(visible, keptOut, path, errors) {
1184
+ if (!Array.isArray(visible) || !Array.isArray(keptOut))
1185
+ return;
1186
+ const visibleSet = new Set(visible);
1187
+ for (const field of keptOut) {
1188
+ if (visibleSet.has(field))
1189
+ errors.push({ path: `${path}.kept_out_fields`, code: "KEPT_OUT_FIELD_VISIBLE", message: `kept-out field must not also be visible: ${String(field)}` });
1190
+ }
1191
+ }
1192
+ function validateNestedObject(value, keys, path, errors) {
1193
+ if (!isRecord(value)) {
1194
+ errors.push({ path, code: "NESTED_VALUE_NOT_OBJECT", message: "nested value must be an object." });
1195
+ return;
1196
+ }
1197
+ checkUnknownKeys(value, keys, path, errors);
1198
+ }
1199
+ function scanForInlineSecrets(value, path, errors) {
1200
+ if (Array.isArray(value)) {
1201
+ value.forEach((item, index) => scanForInlineSecrets(item, `${path}[${index}]`, errors));
1202
+ return;
1203
+ }
1204
+ if (!isRecord(value))
1205
+ return;
1206
+ for (const [key, child] of Object.entries(value)) {
1207
+ const childPath = `${path}.${key}`;
1208
+ if (/database_url|read_url|write_url|connection_string|password|secret|token/i.test(key) && typeof child === "string" && /(postgres(?:ql)?:\/\/|mysql:\/\/|Bearer\s+|[A-Za-z0-9+/=]{24,})/i.test(child)) {
1209
+ errors.push({ path: childPath, code: "INLINE_SECRET_OR_URL", message: "Contracts must not contain database URLs, passwords, bearer tokens, or secrets. Put runtime wiring in runner config/env vars." });
1210
+ }
1211
+ scanForInlineSecrets(child, childPath, errors);
1212
+ }
1213
+ }
1214
+ function checkUnknownKeys(value, allowed, path, errors) {
1215
+ for (const key of Object.keys(value)) {
1216
+ if (allowed.has(key) || isExtensionKey(key))
1217
+ continue;
1218
+ errors.push({ path: `${path}.${key}`, code: "UNKNOWN_CORE_FIELD", message: `Unknown core field ${key}. Use x-cloud-*, x-runner-*, or x-experimental-* for extensions.` });
1219
+ }
1220
+ }
1221
+ function addUnique(seen, name, path, code, errors) {
1222
+ const value = String(name);
1223
+ if (seen.has(value))
1224
+ errors.push({ path, code, message: `Duplicate name: ${value}` });
1225
+ seen.add(value);
1226
+ }
1227
+ function isExtensionKey(key) {
1228
+ return /^x-(cloud|runner|experimental)-[A-Za-z0-9_.-]+$/.test(key);
1229
+ }
1230
+ function isRecord(value) {
1231
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1232
+ }
1233
+ function isNonEmptyString(value) {
1234
+ return typeof value === "string" && value.trim().length > 0;
1235
+ }
1236
+ function isSafeIdentifier(value) {
1237
+ return typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
1238
+ }
1239
+ function isQualifiedName(value) {
1240
+ return typeof value === "string" && /^[A-Za-z_][A-Za-z0-9_]*\.[A-Za-z_][A-Za-z0-9_]*$/.test(value);
1241
+ }
1242
+ function isQualifiedOrSafeName(value) {
1243
+ return isSafeIdentifier(value) || isQualifiedName(value);
1244
+ }
1245
+ function isPositiveInteger(value) {
1246
+ return Number.isInteger(value) && Number(value) > 0;
1247
+ }
1248
+ function isFiniteNumber(value) {
1249
+ return typeof value === "number" && Number.isFinite(value);
1250
+ }
1251
+ function isJsonScalar(value) {
1252
+ return value === null || typeof value === "string" || typeof value === "boolean" || isFiniteNumber(value);
1253
+ }
1254
+
1255
+ // node_modules/.pnpm/@synapsor+spec@1.4.2/node_modules/@synapsor/spec/dist/normalize.js
1256
+ function normalizeContract(input) {
1257
+ assertValidContract(input);
1258
+ return sortJson(input);
1259
+ }
1260
+ function sortJson(value) {
1261
+ if (Array.isArray(value))
1262
+ return value.map(sortJson);
1263
+ if (!value || typeof value !== "object")
1264
+ return value;
1265
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
1266
+ const output = {};
1267
+ for (const [key, child] of entries)
1268
+ output[key] = sortJson(child);
1269
+ return output;
1270
+ }
1271
+
1272
+ // apps/runner/src/authoring.ts
1273
+ function defineResource(resource) {
1274
+ return resource;
1275
+ }
1276
+ function defineAgentContext(context) {
1277
+ return context;
1278
+ }
1279
+ function defineCapability(capability) {
1280
+ return capability;
1281
+ }
1282
+ function defineWorkflow(workflow) {
1283
+ return workflow;
1284
+ }
1285
+ function definePolicy(policy) {
1286
+ return policy;
1287
+ }
1288
+ function defineContract(definition) {
1289
+ const contract = {
1290
+ ...definition,
1291
+ spec_version: "0.1",
1292
+ kind: "SynapsorContract"
1293
+ };
1294
+ assertValidContract(contract);
1295
+ return contract;
1296
+ }
1297
+ function compileContract(definition) {
1298
+ const contract = "spec_version" in definition ? definition : defineContract(definition);
1299
+ assertValidContract(contract);
1300
+ return normalizeContract(contract);
1301
+ }
1302
+ function contractJson(definition, indentation = 2) {
1303
+ if (!Number.isInteger(indentation) || indentation < 0 || indentation > 8) {
1304
+ throw new Error("contract JSON indentation must be an integer from 0 to 8");
1305
+ }
1306
+ return `${JSON.stringify(compileContract(definition), null, indentation)}
1307
+ `;
1308
+ }
1309
+ export {
1310
+ compileContract,
1311
+ contractJson,
1312
+ defineAgentContext,
1313
+ defineCapability,
1314
+ defineContract,
1315
+ definePolicy,
1316
+ defineResource,
1317
+ defineWorkflow
1318
+ };