@synapsor/runner 1.6.0 → 1.6.1

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 (55) hide show
  1. package/CHANGELOG.md +32 -4
  2. package/README.md +16 -2
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/local-ui.d.ts +4 -1
  5. package/dist/local-ui.d.ts.map +1 -1
  6. package/dist/runner.mjs +1624 -42
  7. package/dist/runtime.mjs +1011 -25
  8. package/dist/shadow.mjs +408 -15
  9. package/docs/app-owned-executors.md +8 -0
  10. package/docs/capability-authoring.md +45 -0
  11. package/docs/current-scope.md +5 -0
  12. package/docs/cursor-plugin.md +2 -2
  13. package/docs/limitations.md +15 -2
  14. package/docs/local-mode.md +22 -3
  15. package/docs/production.md +19 -0
  16. package/docs/proposal-evidence-freshness.md +334 -0
  17. package/docs/release-notes.md +30 -4
  18. package/docs/runner-config-reference.md +46 -0
  19. package/docs/security-boundary.md +17 -0
  20. package/docs/store-lifecycle.md +8 -0
  21. package/docs/troubleshooting-first-run.md +76 -0
  22. package/docs/writeback-executors.md +8 -0
  23. package/examples/app-owned-writeback/command-handler.mjs +0 -0
  24. package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +0 -0
  25. package/examples/reference-support-billing-app/scripts/run-demo.sh +0 -0
  26. package/examples/support-billing-agent/scripts/run-demo.sh +0 -0
  27. package/examples/support-billing-agent/scripts/run-evaluation.sh +0 -0
  28. package/fixtures/compatibility/published-1.6.0/manifest.json +76 -0
  29. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
  30. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
  31. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
  32. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
  33. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
  34. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
  35. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
  36. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
  37. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
  38. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
  39. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
  40. package/fixtures/protocol/MANIFEST.json +37 -7
  41. package/fixtures/protocol/change-set.freshness-update.v2.json +58 -0
  42. package/fixtures/protocol/freshness-authority.invoice.v1.json +35 -0
  43. package/fixtures/protocol/freshness-proof.fresh.v1.json +38 -0
  44. package/fixtures/protocol/writeback-job.freshness-update.v2.json +50 -0
  45. package/llms.txt +1 -0
  46. package/package.json +9 -8
  47. package/schemas/change-set.v1.schema.json +1 -0
  48. package/schemas/change-set.v2.schema.json +1 -0
  49. package/schemas/change-set.v3.schema.json +1 -0
  50. package/schemas/freshness-authority.v1.schema.json +89 -0
  51. package/schemas/freshness-proof.v1.schema.json +73 -0
  52. package/schemas/synapsor.runner.schema.json +32 -0
  53. package/schemas/writeback-job.v1.schema.json +1 -0
  54. package/schemas/writeback-job.v2.schema.json +1 -0
  55. package/schemas/writeback-job.v3.schema.json +1 -0
@@ -120,6 +120,16 @@ explicit host-level administrative boundary.
120
120
  Proposal tools read the current row through the read credential, store evidence
121
121
  and an exact before/after diff, and leave the source database unchanged.
122
122
 
123
+ For an explicitly freshness-enabled proposal, Runner also captures exact
124
+ version authority for the target and reviewed same-source supporting rows.
125
+ Before each local approval it performs a read-only live check and binds the
126
+ successful proof to that approval. This improves review integrity but does not
127
+ hold a lock through apply. The trusted direct SQL apply path therefore locks
128
+ and rechecks the dependencies again inside the mutation transaction. A stale
129
+ or unverifiable dependency produces no mutation and is never silently
130
+ refreshed. See
131
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
132
+
123
133
  Code-first Safe Actions do not create authority when a file is edited. Runner
124
134
  parses the restricted TypeScript object without importing or executing it,
125
135
  compiles it into a digest-addressed disabled canonical draft, and keeps the
@@ -153,11 +163,18 @@ outside the model-facing MCP server and verifies:
153
163
  - conflict/version guard;
154
164
  - idempotency key;
155
165
  - operation-specific version or source-unique deduplication guard;
166
+ - declared same-source supporting-version guards, when freshness is required;
156
167
  - job expiry;
157
168
  - exactly one reviewed row or every member of one bounded frozen set.
158
169
 
159
170
  If any authority check cannot be verified, the write fails closed.
160
171
 
172
+ Strict freshness is intentionally limited to same-database direct SQL
173
+ writeback. Runner rejects app-owned or cross-source strict freshness because it
174
+ cannot claim one atomic transaction around those effects. Cloud can govern an
175
+ approval, but it does not read the source; the local Runner revalidates source
176
+ state before a Cloud-approved job can mutate data.
177
+
161
178
  For direct SQL writeback, the writer connection is the env var named by the
162
179
  source `write_url_env` in `synapsor.runner.json`. With `source_db` receipt
163
180
  authority, the source mutation and receipt commit atomically; the receipt table
@@ -48,6 +48,7 @@ then proposal id as the tie-breaker. The default view answers:
48
48
  - what capability and business object the model requested;
49
49
  - which trusted tenant and principal scoped it;
50
50
  - approval state and progress;
51
+ - required/live freshness state and the proof digest bound to each approval;
51
52
  - whether a writeback job or intent exists;
52
53
  - the latest guarded outcome and whether the source changed;
53
54
  - replay and Cloud-link status; and
@@ -123,6 +124,7 @@ The existing commands remain useful when you need one record type:
123
124
  | Question | Focused command |
124
125
  | --- | --- |
125
126
  | What did the model propose? | `synapsor-runner proposals show latest --details` |
127
+ | Is the latest proposal still fresh enough to review? | `synapsor-runner proposals check-freshness latest --config ./synapsor.runner.json` |
126
128
  | What data supported it? | `synapsor-runner evidence list --proposal <proposal-id>` then `evidence show <evidence-id> --details` |
127
129
  | What query was run? | `synapsor-runner query-audit list --proposal <proposal-id>` |
128
130
  | Did guarded writeback apply? | `synapsor-runner receipts list --proposal <proposal-id>` |
@@ -135,6 +137,12 @@ The existing commands remain useful when you need one record type:
135
137
  it materializes a job and is therefore an operator mutation, not a read-only
136
138
  view.
137
139
 
140
+ `proposals check-freshness` is source-read-only but is not a ledger-pure
141
+ inspection: it contacts the configured source and records an immutable proof
142
+ event. The `lifecycle` command never does that; it only reports the most recent
143
+ stored proof and approval linkage. See
144
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
145
+
138
146
  ## Server leases
139
147
 
140
148
  MCP server modes write a small lease file next to the store:
@@ -364,6 +364,82 @@ Fix:
364
364
  Use a read-only credential for MCP reads and a separate writer credential only
365
365
  for trusted apply.
366
366
 
367
+ ## Freshness Check Is Stale
368
+
369
+ What happened:
370
+
371
+ ```text
372
+ Freshness: stale
373
+ code: FRESHNESS_TARGET_STALE
374
+ ```
375
+
376
+ or:
377
+
378
+ ```text
379
+ code: FRESHNESS_DEPENDENCY_STALE
380
+ ```
381
+
382
+ Why it matters:
383
+
384
+ The proposal target or one explicitly declared supporting row no longer has
385
+ the exact version captured by the immutable proposal. Runner records no
386
+ approval and never updates the old proposal to match current data.
387
+
388
+ Fix:
389
+
390
+ Perform a new reviewed source read and create a new proposal. Do not retry
391
+ approval on the stale proposal or edit its stored JSON.
392
+
393
+ ## Freshness Check Is Unavailable
394
+
395
+ What happened:
396
+
397
+ ```text
398
+ Freshness: unavailable
399
+ code: FRESHNESS_TEMPORARILY_UNAVAILABLE
400
+ ```
401
+
402
+ Why it matters:
403
+
404
+ Runner could not prove current source state. Unavailability is not treated as
405
+ fresh, and no approval was recorded.
406
+
407
+ Fix:
408
+
409
+ Restore the read connection or resolve the transient database/pool issue, then
410
+ retry:
411
+
412
+ ```bash
413
+ synapsor-runner proposals check-freshness latest \
414
+ --config ./synapsor.runner.json \
415
+ --store ./.synapsor/local.db
416
+ ```
417
+
418
+ ## Freshness Writer Lock Probe Failed
419
+
420
+ What happened:
421
+
422
+ `doctor --check-writeback` reports that the writer cannot lock a declared
423
+ supporting dependency.
424
+
425
+ Why it matters:
426
+
427
+ Approval-time reads alone cannot provide the final atomic guarantee. The direct
428
+ SQL adapter must lock and compare every dependency inside the mutation
429
+ transaction.
430
+
431
+ Fix:
432
+
433
+ Grant only the narrow table/column privilege required for the writer's
434
+ `SELECT ... FOR UPDATE` locking read, then rerun:
435
+
436
+ ```bash
437
+ synapsor-runner doctor --check-writeback --config ./synapsor.runner.json
438
+ ```
439
+
440
+ Do not replace this with an overprivileged owner/superuser role. See
441
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
442
+
367
443
  ## MCP Client Config Contains A Secret
368
444
 
369
445
  What happened:
@@ -250,3 +250,11 @@ Executor secrets are never exposed over MCP. The model never receives:
250
250
 
251
251
  `MCP tool call = request/proposal authority. Trusted runner = execution
252
252
  authority.`
253
+
254
+ Optional Runner `proposal_freshness` adds pre-approval and final-transaction
255
+ checks only to same-source direct SQL writeback. It is deliberately rejected
256
+ for HTTP/command handlers and cross-source dependencies: Runner cannot claim an
257
+ atomic source check around an externally owned transaction. Handler authors
258
+ must implement equivalent scope, exact-version, locking, idempotency, and
259
+ receipt semantics themselves. See
260
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
File without changes
@@ -0,0 +1,76 @@
1
+ {
2
+ "schema_version": "synapsor.published-compatibility.v1",
3
+ "published_packages": {
4
+ "@synapsor/runner": {
5
+ "version": "1.6.0",
6
+ "npm_shasum": "3ac934e1742184514c7eca935d7d3c1ca752bb70"
7
+ },
8
+ "@synapsor/dsl": {
9
+ "version": "1.5.0",
10
+ "npm_shasum": "45832b2e4e754bd68d195e3daf14b1c14edbc27d"
11
+ },
12
+ "@synapsor/spec": {
13
+ "version": "1.5.0",
14
+ "npm_shasum": "e167ccff21e0a511bbe0758d0a918fdd59b03b4d"
15
+ }
16
+ },
17
+ "contracts": [
18
+ {
19
+ "path": "packages/spec/fixtures/valid/basic-read.contract.json",
20
+ "source_sha256": "5ec499965f639d784603b7942390d18a3e742d79e4d510dbea470e166a743972",
21
+ "canonical_sha256": "9d3addfde77eb6528aae7b9e81ae0f582b5c002aa77ebb2d7381fe663f5f5b1e"
22
+ },
23
+ {
24
+ "path": "packages/spec/fixtures/conformance/aggregate-read/contract.json",
25
+ "source_sha256": "830bc208b749bb9ed6a4b4468571d58d68bc8a2b6e1072fc45fb87d1c68697e7",
26
+ "canonical_sha256": "ccc6ea17f449bfc207d1cea70ef3320d575c450a3904c850dcb631132c6e5602"
27
+ },
28
+ {
29
+ "path": "packages/spec/fixtures/conformance/proposal-capability/contract.json",
30
+ "source_sha256": "d4c25fb8f79f04c13bdb3daf8c18a44d4f1c61596660a67bf35d46d0f94f4100",
31
+ "canonical_sha256": "d895081246d41a072892b5dde6929099302492a77a229033fbfa5052cce34b18"
32
+ },
33
+ {
34
+ "path": "packages/spec/fixtures/conformance/bounded-set-threats/contract.json",
35
+ "source_sha256": "c902d45457789a6d87504ca434c8b299f3e09be779f3bfdf7bbd8684d575a10e",
36
+ "canonical_sha256": "64c7078ff1687762d95c17873238759b3572dce499f903a8d6f1aaa8faae5f24"
37
+ },
38
+ {
39
+ "path": "packages/spec/fixtures/conformance/reversible-change-sets/contract.json",
40
+ "source_sha256": "7e4f8da90a4b235097f76e70a7f550153bbf62d219e7ef6f4f2194f6c36fa205",
41
+ "canonical_sha256": "181f2e93ad3c53b9a1345a4ed6c8b5e24ab0201c00159f38659f0c2a00c037de"
42
+ },
43
+ {
44
+ "path": "packages/spec/fixtures/conformance/principal-row-scope/contract.json",
45
+ "source_sha256": "00a05c1f4e82cc9c4e5d2d68c9c9201cd9512ac42d63292314bb7b4845026996",
46
+ "canonical_sha256": "fc96a5327ab0f4b311c90da9b5585348e81349ed66bd3a22e41e5f29804c32a8"
47
+ },
48
+ {
49
+ "path": "packages/spec/fixtures/conformance/approval-quorum/contract.json",
50
+ "source_sha256": "dde6b328f648ba60d837fa363494d764d7eb0d2d9a8a81ff70a9c077edde410c",
51
+ "canonical_sha256": "9407a7d0cc4439c773c3caf721aaed6c6e163c403fa67518632829d8c5de33e7"
52
+ }
53
+ ],
54
+ "dsl_sources": [
55
+ {
56
+ "path": "packages/dsl/examples/aggregate-read.synapsor.sql",
57
+ "source_sha256": "bccac4c2eb0ab7b2fc099c7fb3f6fdc25ee2e6e534282aa19b46f0aec8191b8d",
58
+ "canonical_sha256": "0cc2d892f00a31a3582d773812a26c6c45c0ce343e1d8d09da226a5bbb7c93eb"
59
+ },
60
+ {
61
+ "path": "packages/dsl/examples/billing-late-fee.synapsor.sql",
62
+ "source_sha256": "f5c462cff7aa0cd01a08543c6096ee1d225ebba449d65b7470815b1aa7ec42cd",
63
+ "canonical_sha256": "d495df575d64e798a730dad385e097dfc2c2f371a1505865bc45179ad3c479f2"
64
+ },
65
+ {
66
+ "path": "packages/dsl/examples/bounded-set-multi-term.synapsor.sql",
67
+ "source_sha256": "0ae199034ebba8b25146435c911576118f948b55b54636a5e68f46002cd1c6dd",
68
+ "canonical_sha256": "5b3e84cf8f5062a8afcd50763bde907e6a141977456e74a81d01f8b3a484e422"
69
+ },
70
+ {
71
+ "path": "packages/dsl/examples/principal-row-scope.synapsor.sql",
72
+ "source_sha256": "08ccd769d633417ea45d478383d9628991f43262223f12c78bbd776202b10253",
73
+ "canonical_sha256": "e29f2819c267210e54f783c40d0f550d11d0d1de844dbe1590ed8c3405af3cf7"
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,21 @@
1
+ CREATE AGENT CONTEXT finance_operator
2
+ BIND tenant_id FROM ENVIRONMENT SYNAPSOR_TENANT_ID REQUIRED
3
+ BIND principal FROM ENVIRONMENT SYNAPSOR_PRINCIPAL REQUIRED
4
+ TENANT BINDING tenant_id
5
+ PRINCIPAL BINDING principal
6
+ END
7
+
8
+ CREATE CAPABILITY billing.overdue_balance_total
9
+ DESCRIPTION 'Return one suppressed overdue-balance aggregate for the trusted tenant.'
10
+ RETURNS HINT 'Returns one scalar or a suppression result; never member rows.'
11
+ USING CONTEXT finance_operator
12
+ SOURCE local_postgres
13
+ ON public.invoices
14
+ PRIMARY KEY id
15
+ TENANT KEY tenant_id
16
+ AGGREGATE READ SUM balance_cents
17
+ SELECT WHERE status = 'overdue'
18
+ MIN GROUP SIZE 5
19
+ KEEP OUT customer_email, private_notes
20
+ REQUIRE EVIDENCE
21
+ END
@@ -0,0 +1,56 @@
1
+ CREATE AGENT CONTEXT local_operator
2
+ BIND tenant_id FROM ENVIRONMENT SYNAPSOR_TENANT_ID REQUIRED
3
+ BIND principal FROM ENVIRONMENT SYNAPSOR_PRINCIPAL REQUIRED
4
+ TENANT BINDING tenant_id
5
+ PRINCIPAL BINDING principal
6
+ END
7
+
8
+ CREATE CAPABILITY billing.inspect_invoice
9
+ DESCRIPTION 'Inspect one invoice in the trusted tenant before proposing a waiver.'
10
+ RETURNS HINT 'Returns reviewed invoice fields plus evidence/query-audit handles.'
11
+ USING CONTEXT local_operator
12
+ SOURCE local_postgres
13
+ ON public.invoices
14
+ PRIMARY KEY id
15
+ TENANT KEY tenant_id
16
+ CONFLICT GUARD updated_at
17
+ LOOKUP invoice_id BY id
18
+ ARG invoice_id STRING REQUIRED MAX LENGTH 128 DESCRIPTION 'Invoice id such as INV-3001.'
19
+ ALLOW READ id, tenant_id, customer_id, status, late_fee_cents, waiver_reason, updated_at
20
+ KEEP OUT card_token, internal_risk_score
21
+ REQUIRE EVIDENCE
22
+ MAX ROWS 1
23
+ END
24
+
25
+ CREATE CAPABILITY billing.propose_late_fee_waiver
26
+ DESCRIPTION 'Propose waiving one invoice late fee after inspecting invoice and policy evidence.'
27
+ RETURNS HINT 'Returns a review-required proposal id, exact diff, evidence handle, and source_database_changed:false.'
28
+ USING CONTEXT local_operator
29
+ SOURCE local_postgres
30
+ ON public.invoices
31
+ PRIMARY KEY id
32
+ TENANT KEY tenant_id
33
+ CONFLICT GUARD updated_at
34
+ LOOKUP invoice_id BY id
35
+ ARG invoice_id STRING REQUIRED MAX LENGTH 128 DESCRIPTION 'Invoice id such as INV-3001.'
36
+ ARG waiver_reason TEXT REQUIRED MAX LENGTH 500 DESCRIPTION 'Business reason for the proposed waiver.'
37
+ ALLOW READ id, tenant_id, customer_id, status, late_fee_cents, waiver_reason, updated_at
38
+ KEEP OUT card_token, internal_risk_score
39
+ REQUIRE EVIDENCE
40
+ MAX ROWS 1
41
+ PROPOSE ACTION billing.waive_late_fee
42
+ ALLOW WRITE late_fee_cents, waiver_reason
43
+ PATCH late_fee_cents = 0
44
+ PATCH waiver_reason = ARG waiver_reason
45
+ APPROVAL ROLE billing_lead
46
+ WRITEBACK DIRECT SQL
47
+ END
48
+
49
+ CREATE AGENT WORKFLOW billing.late_fee_review
50
+ USING CONTEXT local_operator
51
+ ALLOW CAPABILITY billing.inspect_invoice
52
+ ALLOW CAPABILITY billing.propose_late_fee_waiver
53
+ REQUIRE EVIDENCE
54
+ APPROVAL REQUIRED ROLE billing_lead
55
+ CHECKPOINT PROPOSAL ONLY
56
+ END
@@ -0,0 +1,30 @@
1
+ CREATE AGENT CONTEXT local_operator
2
+ BIND tenant_id FROM ENVIRONMENT SYNAPSOR_TENANT_ID REQUIRED
3
+ BIND principal FROM ENVIRONMENT SYNAPSOR_PRINCIPAL REQUIRED
4
+ TENANT BINDING tenant_id
5
+ PRINCIPAL BINDING principal
6
+ END
7
+
8
+ CREATE CAPABILITY cases.close_high_risk
9
+ DESCRIPTION 'Close active high-risk support cases within reviewed row and value caps.'
10
+ RETURNS HINT 'Returns a proposal for the exact reviewed set; source rows remain unchanged.'
11
+ USING CONTEXT local_operator
12
+ SOURCE support_db
13
+ ON public.support_cases
14
+ PRIMARY KEY id
15
+ TENANT KEY tenant_id
16
+ CONFLICT GUARD version
17
+ LOOKUP reason BY id
18
+ ARG reason STRING REQUIRED MAX LENGTH 100
19
+ ALLOW READ id, tenant_id, risk_level, case_status, exposure_cents, version
20
+ REQUIRE EVIDENCE
21
+ PROPOSE ACTION close_high_risk UPDATE SET
22
+ SELECT WHERE risk_level = 'high' AND case_status = 'active'
23
+ MAX ROWS 10
24
+ MAX TOTAL exposure_cents BEFORE 50000
25
+ ALLOW WRITE case_status
26
+ PATCH case_status = 'closed'
27
+ ADVANCE VERSION version USING INTEGER INCREMENT
28
+ APPROVAL ROLE support_manager
29
+ WRITEBACK DIRECT SQL
30
+ END
@@ -0,0 +1,23 @@
1
+ CREATE AGENT CONTEXT care_session
2
+ BIND hospital_id FROM HTTP_CLAIM hospital_id REQUIRED
3
+ BIND principal FROM HTTP_CLAIM sub REQUIRED
4
+ TENANT BINDING hospital_id
5
+ PRINCIPAL BINDING principal
6
+ END
7
+
8
+ CREATE CAPABILITY care.inspect_assigned_patient
9
+ DESCRIPTION 'Inspect one patient assigned to the authenticated case manager within the trusted hospital.'
10
+ RETURNS HINT 'Returns reviewed patient fields and evidence only when both tenant and assignee locks match.'
11
+ USING CONTEXT care_session
12
+ SOURCE local_postgres
13
+ ON public.patients
14
+ PRIMARY KEY id
15
+ TENANT KEY hospital_id
16
+ PRINCIPAL SCOPE KEY assigned_to
17
+ LOOKUP patient_id BY id
18
+ ARG patient_id STRING REQUIRED MAX LENGTH 128 DESCRIPTION 'Reviewed patient identifier.'
19
+ ALLOW READ id, hospital_id, display_name, care_status, updated_at
20
+ KEEP OUT assigned_to, diagnosis_notes, insurance_member_id
21
+ REQUIRE EVIDENCE
22
+ MAX ROWS 1
23
+ END
@@ -0,0 +1,119 @@
1
+ {
2
+ "spec_version": "0.1",
3
+ "kind": "SynapsorContract",
4
+ "metadata": {
5
+ "name": "aggregate read and enum conformance",
6
+ "version": "1.0.0"
7
+ },
8
+ "resources": [
9
+ {
10
+ "name": "billing_invoices",
11
+ "engine": "postgres",
12
+ "schema": "public",
13
+ "table": "invoices",
14
+ "primary_key": "id",
15
+ "tenant_key": "tenant_id"
16
+ }
17
+ ],
18
+ "contexts": [
19
+ {
20
+ "name": "billing_context",
21
+ "bindings": [
22
+ {
23
+ "name": "tenant_id",
24
+ "source": "environment",
25
+ "key": "SYNAPSOR_TENANT_ID"
26
+ },
27
+ {
28
+ "name": "principal",
29
+ "source": "environment",
30
+ "key": "SYNAPSOR_PRINCIPAL"
31
+ }
32
+ ],
33
+ "tenant_binding": "tenant_id",
34
+ "principal_binding": "principal"
35
+ }
36
+ ],
37
+ "capabilities": [
38
+ {
39
+ "name": "billing.sum_overdue_balance",
40
+ "description": "Return one privacy-suppressed aggregate for the trusted tenant.",
41
+ "returns_hint": "Returns one scalar or a suppression result; never member rows.",
42
+ "kind": "aggregate_read",
43
+ "context": "billing_context",
44
+ "source": "local_postgres",
45
+ "subject": {
46
+ "resource": "billing_invoices"
47
+ },
48
+ "args": {},
49
+ "visible_fields": [],
50
+ "kept_out_fields": [
51
+ "customer_email",
52
+ "private_notes"
53
+ ],
54
+ "evidence": {
55
+ "required": true,
56
+ "query_audit": true
57
+ },
58
+ "aggregate": {
59
+ "function": "sum",
60
+ "column": "balance_cents",
61
+ "selection": {
62
+ "all": [
63
+ {
64
+ "column": "status",
65
+ "operator": "eq",
66
+ "value": "overdue"
67
+ }
68
+ ]
69
+ },
70
+ "minimum_group_size": 5
71
+ }
72
+ },
73
+ {
74
+ "name": "billing.inspect_invoice",
75
+ "description": "Inspect one tenant-scoped invoice using a reviewed risk enum.",
76
+ "kind": "read",
77
+ "context": "billing_context",
78
+ "source": "local_postgres",
79
+ "subject": {
80
+ "resource": "billing_invoices"
81
+ },
82
+ "args": {
83
+ "invoice_id": {
84
+ "type": "string",
85
+ "required": true,
86
+ "max_length": 128
87
+ },
88
+ "risk_level": {
89
+ "type": "string",
90
+ "required": true,
91
+ "enum": [
92
+ "low",
93
+ "medium",
94
+ "high"
95
+ ]
96
+ }
97
+ },
98
+ "lookup": {
99
+ "id_from_arg": "invoice_id"
100
+ },
101
+ "visible_fields": [
102
+ "id",
103
+ "tenant_id",
104
+ "status",
105
+ "balance_cents"
106
+ ],
107
+ "kept_out_fields": [
108
+ "customer_email",
109
+ "private_notes"
110
+ ],
111
+ "evidence": {
112
+ "required": true,
113
+ "query_audit": true
114
+ },
115
+ "max_rows": 1
116
+ }
117
+ ]
118
+ }
119
+
@@ -0,0 +1,44 @@
1
+ {
2
+ "spec_version": "0.1",
3
+ "kind": "SynapsorContract",
4
+ "contexts": [
5
+ {
6
+ "name": "local_operator",
7
+ "bindings": [
8
+ { "name": "tenant_id", "source": "environment", "key": "SYNAPSOR_TENANT_ID" },
9
+ { "name": "principal", "source": "environment", "key": "SYNAPSOR_PRINCIPAL" }
10
+ ],
11
+ "tenant_binding": "tenant_id",
12
+ "principal_binding": "principal"
13
+ }
14
+ ],
15
+ "capabilities": [
16
+ {
17
+ "name": "billing.propose_late_fee_waiver",
18
+ "kind": "proposal",
19
+ "context": "local_operator",
20
+ "subject": {
21
+ "schema": "public",
22
+ "table": "invoices",
23
+ "primary_key": "id",
24
+ "tenant_key": "tenant_id",
25
+ "conflict_key": "updated_at"
26
+ },
27
+ "args": { "invoice_id": { "type": "string" } },
28
+ "lookup": { "id_from_arg": "invoice_id" },
29
+ "visible_fields": ["id", "tenant_id", "late_fee_cents", "updated_at"],
30
+ "proposal": {
31
+ "action": "billing.waive_late_fee",
32
+ "allowed_fields": ["late_fee_cents"],
33
+ "patch": { "late_fee_cents": { "fixed": 0 } },
34
+ "conflict_guard": { "column": "updated_at" },
35
+ "approval": {
36
+ "mode": "human",
37
+ "required_role": "billing_lead",
38
+ "required_approvals": 2
39
+ },
40
+ "writeback": { "mode": "direct_sql" }
41
+ }
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,115 @@
1
+ {
2
+ "spec_version": "0.1",
3
+ "kind": "SynapsorContract",
4
+ "metadata": { "name": "bounded-set-threats", "version": "1.0.0" },
5
+ "contexts": [
6
+ {
7
+ "name": "trusted_operator",
8
+ "bindings": [
9
+ { "name": "tenant_id", "source": "environment", "key": "SYNAPSOR_TENANT_ID", "required": true },
10
+ { "name": "principal", "source": "environment", "key": "SYNAPSOR_PRINCIPAL", "required": true }
11
+ ],
12
+ "tenant_binding": "tenant_id",
13
+ "principal_binding": "principal"
14
+ }
15
+ ],
16
+ "capabilities": [
17
+ {
18
+ "name": "billing.close_overdue_invoices",
19
+ "kind": "proposal",
20
+ "context": "trusted_operator",
21
+ "source": "local_postgres",
22
+ "subject": { "schema": "public", "table": "invoices", "primary_key": "id", "tenant_key": "tenant_id", "conflict_key": "version" },
23
+ "args": { "reason": { "type": "string", "required": true, "max_length": 500 } },
24
+ "visible_fields": ["id", "tenant_id", "status", "balance_cents", "version"],
25
+ "kept_out_fields": ["internal_risk_score"],
26
+ "evidence": { "required": true, "query_audit": true },
27
+ "proposal": {
28
+ "action": "close_overdue",
29
+ "operation": {
30
+ "kind": "update",
31
+ "cardinality": "set",
32
+ "selection": { "all": [{ "column": "status", "operator": "eq", "value": "overdue" }] },
33
+ "max_rows": 10,
34
+ "aggregate_bounds": [{ "column": "balance_cents", "measure": "before", "maximum": 50000 }],
35
+ "version_advance": { "column": "version", "strategy": "integer_increment" }
36
+ },
37
+ "allowed_fields": ["status", "close_reason"],
38
+ "patch": { "status": { "fixed": "closed" }, "close_reason": { "from_arg": "reason" } },
39
+ "conflict_guard": { "column": "version" },
40
+ "approval": { "mode": "human", "required_role": "billing_reviewer" },
41
+ "writeback": { "mode": "direct_sql" }
42
+ }
43
+ },
44
+ {
45
+ "name": "billing.delete_expired_drafts",
46
+ "kind": "proposal",
47
+ "context": "trusted_operator",
48
+ "source": "local_postgres",
49
+ "subject": { "schema": "public", "table": "invoice_drafts", "primary_key": "id", "tenant_key": "tenant_id", "conflict_key": "version" },
50
+ "args": { "reason": { "type": "string", "required": true, "max_length": 500 } },
51
+ "visible_fields": ["id", "tenant_id", "status", "estimated_cents", "version"],
52
+ "evidence": { "required": true, "query_audit": true },
53
+ "proposal": {
54
+ "action": "delete_expired_drafts",
55
+ "operation": {
56
+ "kind": "delete",
57
+ "cardinality": "set",
58
+ "selection": { "all": [{ "column": "status", "operator": "eq", "value": "expired" }] },
59
+ "max_rows": 5,
60
+ "aggregate_bounds": [{ "column": "estimated_cents", "measure": "before", "maximum": 10000 }]
61
+ },
62
+ "allowed_fields": [],
63
+ "patch": {},
64
+ "conflict_guard": { "column": "version" },
65
+ "approval": { "mode": "human", "required_role": "billing_reviewer", "required_approvals": 2 },
66
+ "writeback": { "mode": "direct_sql" }
67
+ }
68
+ },
69
+ {
70
+ "name": "billing.create_credits",
71
+ "kind": "proposal",
72
+ "context": "trusted_operator",
73
+ "source": "local_postgres",
74
+ "subject": { "schema": "public", "table": "account_credits", "primary_key": "id", "tenant_key": "tenant_id" },
75
+ "args": {
76
+ "items": {
77
+ "type": "object_array",
78
+ "required": true,
79
+ "max_items": 10,
80
+ "fields": {
81
+ "id": { "type": "string", "required": true, "max_length": 128 },
82
+ "external_id": { "type": "string", "required": true, "max_length": 128 },
83
+ "amount_cents": { "type": "number", "required": true, "minimum": 1, "maximum": 2500 },
84
+ "reason": { "type": "string", "required": true, "max_length": 500 }
85
+ }
86
+ }
87
+ },
88
+ "visible_fields": ["id", "tenant_id", "external_id", "amount_cents", "reason", "version"],
89
+ "kept_out_fields": ["internal_note"],
90
+ "evidence": { "required": true, "query_audit": true },
91
+ "proposal": {
92
+ "action": "create_credits",
93
+ "operation": {
94
+ "kind": "insert",
95
+ "cardinality": "set",
96
+ "batch": { "items_from_arg": "items" },
97
+ "max_rows": 10,
98
+ "aggregate_bounds": [{ "column": "amount_cents", "measure": "after", "maximum": 25000 }],
99
+ "deduplication": {
100
+ "components": [
101
+ { "column": "tenant_id", "source": "trusted_tenant" },
102
+ { "column": "id", "source": "item_field", "item_field": "id" },
103
+ { "column": "external_id", "source": "item_field", "item_field": "external_id" }
104
+ ]
105
+ }
106
+ },
107
+ "allowed_fields": ["amount_cents", "reason"],
108
+ "patch": { "amount_cents": { "from_item": "amount_cents" }, "reason": { "from_item": "reason" } },
109
+ "numeric_bounds": { "amount_cents": { "minimum": 1, "maximum": 2500 } },
110
+ "approval": { "mode": "human", "required_role": "billing_reviewer" },
111
+ "writeback": { "mode": "direct_sql" }
112
+ }
113
+ }
114
+ ]
115
+ }