@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
@@ -24,6 +24,14 @@ the receipt, and includes the result in replay.
24
24
  > error receipts. If you skip those checks, you can reintroduce cross-tenant
25
25
  > writes, lost updates, or duplicate writes. Keep handler credentials out of MCP.
26
26
 
27
+ Runner 1.6.1 strict `proposal_freshness` is limited to same-database direct SQL
28
+ writeback. Runner rejects it for `http_handler`, `command_handler`, and
29
+ cross-source dependencies because a local preflight cannot be atomic with an
30
+ effect executed elsewhere. If supporting evidence matters, the app handler
31
+ must lock and re-read those rows inside its own business transaction and fail
32
+ closed on drift. See
33
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
34
+
27
35
  A handler is your application endpoint or script. It is not a second Synapsor
28
36
  package that users need to install. Install `@synapsor/runner`, then generate
29
37
  or copy a handler template only when your approved write needs app-owned
@@ -384,6 +384,38 @@ surface.
384
384
  }
385
385
  ```
386
386
 
387
+ ### Optional Live Freshness
388
+
389
+ The proposal's exact `conflict_guard` already protects its target at apply.
390
+ Runner 1.6.1 can additionally recheck that target before local approval and can
391
+ bind explicitly reviewed supporting rows to both approval and apply.
392
+
393
+ This is deployment wiring, not DSL or canonical contract syntax:
394
+
395
+ ```json
396
+ {
397
+ "proposal_freshness": {
398
+ "billing.propose_late_fee_waiver": {
399
+ "approval": "required",
400
+ "dependencies": [
401
+ {
402
+ "id": "payment_status",
403
+ "capability": "billing.inspect_payment",
404
+ "identity_from_arg": "payment_id",
405
+ "version_column": "version"
406
+ }
407
+ ]
408
+ }
409
+ }
410
+ }
411
+ ```
412
+
413
+ `billing.inspect_payment` must be an existing reviewed single-row read on the
414
+ same source and trusted context. `payment_id` is an existing scalar proposal
415
+ argument; the model cannot choose the dependency table, version column, tenant,
416
+ principal, or whether the check runs. See
417
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
418
+
387
419
  ## Trusted Context
388
420
 
389
421
  Tenant, principal, approval authority, source ids, and row-version authority
@@ -468,6 +500,12 @@ Runner validates:
468
500
  - one affected row;
469
501
  - idempotency receipt.
470
502
 
503
+ When the proposal opts into `proposal_freshness`, the direct SQL adapter also
504
+ locks and compares every declared supporting row inside the same transaction
505
+ before touching the target. A mismatch returns a freshness conflict and zero
506
+ affected rows. Run `doctor --check-writeback` to verify that the writer can
507
+ perform those dependency locking reads.
508
+
471
509
  INSERT additionally requires a source-unique dedup key. DELETE requires an exact
472
510
  version guard and fails closed on widening cascades or write triggers.
473
511
 
@@ -489,6 +527,13 @@ inserting an outbox event, updating multiple app tables, calling your own
489
527
  service, or creating a row whose source constraints cannot satisfy native
490
528
  single-row INSERT guards.
491
529
 
530
+ Runner's strict `proposal_freshness` overlay does not support app-owned or
531
+ cross-source effects because Runner cannot make their source checks and effect
532
+ atomic. The handler must independently lock/re-read all relevant state and
533
+ enforce the exact proposal, scope, version, transaction, and idempotency
534
+ preconditions described in
535
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
536
+
492
537
  In DSL, keep the reviewed business contract portable and name the executor:
493
538
 
494
539
  ```sql
@@ -25,6 +25,9 @@ Current `1.6` scope:
25
25
  explicit development-only static values;
26
26
  - evidence handles, normalized query audit, proposals, receipts, local replay,
27
27
  and read-only lifecycle inspection without copying proposal ids;
28
+ - optional proposal/evidence freshness: live target and declared same-source
29
+ supporting-row checks before every approval, proof-bound human/quorum/policy
30
+ decisions, and atomic direct-SQL dependency revalidation at apply;
28
31
  - guarded single-row `INSERT`, `UPDATE`, and `DELETE`;
29
32
  - fixed-predicate set `UPDATE`/`DELETE` and exact-review batch `INSERT`, with
30
33
  mandatory row/value caps, frozen members, human approval, atomic execution,
@@ -62,6 +65,8 @@ Out of scope:
62
65
  - model-selected tenant/principal, activation, approval, or commit authority;
63
66
  - UPSERT, DDL, free-form predicates, unbounded writes, or cross-table direct
64
67
  transactions;
68
+ - strict atomic freshness for app-owned handlers, APIs, or cross-source
69
+ dependencies; those executors own their transaction preconditions;
65
70
  - physical branching of external Postgres/MySQL or automatic rollback/time
66
71
  travel;
67
72
  - self-hosted Synapsor Cloud, an unbounded/multi-region shared ledger, a managed
@@ -19,7 +19,7 @@ claim that Cursor Marketplace review has completed.
19
19
 
20
20
  ## What the plugin installs
21
21
 
22
- - one production stdio MCP server pinned to `@synapsor/runner@1.6.0`;
22
+ - one production stdio MCP server pinned to `@synapsor/runner@1.6.1`;
23
23
  - one `/synapsor-protect` agent command;
24
24
  - one discoverable Safe Action skill;
25
25
  - one file-scoped rule for `synapsor/actions/**/*.ts`;
@@ -80,7 +80,7 @@ Required listing media:
80
80
 
81
81
  ## Submission checklist
82
82
 
83
- - [ ] Runner 1.6.0 package and plugin version agree.
83
+ - [ ] Runner 1.6.1 package and plugin version agree.
84
84
  - [ ] `corepack pnpm verify:cursor-plugin` passes from a clean checkout.
85
85
  - [ ] Plugin is manually loaded in current stable Cursor at workspace scope.
86
86
  - [ ] `/synapsor-protect` drafts and validates without activation.
@@ -2,8 +2,10 @@
2
2
 
3
3
  Synapsor Runner is intentionally narrow. Version 1.6 adds deterministic
4
4
  whole-application boundary drafting and a local authoring-only Explore ->
5
- Protect path on top of guarded writes. It does not turn Runner into a generic
6
- database query tool, claim Synapsor Cloud scale, or claim an enterprise SLA.
5
+ Protect path on top of guarded writes. Version 1.6.1 also adds opt-in
6
+ proposal/evidence freshness for exact same-source direct-SQL proposals. It does
7
+ not turn Runner into a generic database query tool, claim Synapsor Cloud scale,
8
+ or claim an enterprise SLA.
7
9
 
8
10
  ## Supported
9
11
 
@@ -15,6 +17,9 @@ database query tool, claim Synapsor Cloud scale, or claim an enterprise SLA.
15
17
  - Native Postgres/MySQL source pools and operational/fleet-wide rate limits.
16
18
  - Verified operator approval through CLI, optional distinct-reviewer quorum,
17
19
  and shared local review UI reads.
20
+ - Optional live target and explicitly declared supporting-row freshness checks
21
+ before every approval, with immutable proof binding and final transactional
22
+ revalidation for PostgreSQL/MySQL direct SQL writeback.
18
23
  - Separately protected scrapeable metrics and dead-letter recovery commands.
19
24
  - Shared-ledger backup/digest verification, clean restore, and
20
25
  archive-before-retention.
@@ -90,6 +95,8 @@ truth for the model-facing tools.
90
95
  - Policy auto-approval for bounded sets.
91
96
  - Stored procedures.
92
97
  - Cross-database transactions.
98
+ - Strict atomic proposal freshness across databases, app-owned handlers, or
99
+ external APIs. Those paths must enforce their own final preconditions.
93
100
  - Physical branching of Postgres/MySQL.
94
101
  - Full Synapsor workflow/DAG execution.
95
102
  - `CREATE AGENT WORKFLOW` or hosted Synapsor SQL generation.
@@ -163,3 +170,9 @@ as equivalent to a durable version column.
163
170
  Runner-ledger UPDATE and DELETE require an exact guard; UPDATE must advance it
164
171
  inside the source transaction. INSERT requires a reviewed source-unique dedup
165
172
  identity. See [Guarded Single-Row CRUD Writeback](guarded-crud-writeback.md).
173
+
174
+ Approval-time freshness also depends on a correct exact version column. It
175
+ does not freeze source state until apply. Direct SQL apply rechecks again;
176
+ app-owned/cross-source effects do not receive that Runner-owned atomic
177
+ guarantee. See
178
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
@@ -140,10 +140,19 @@ Show a proposal:
140
140
  npx -y -p @synapsor/runner synapsor-runner proposals show wrp_123 --store ./.synapsor/local.db
141
141
  ```
142
142
 
143
+ Check a freshness-enabled proposal without copying its id:
144
+
145
+ ```bash
146
+ npx -y -p @synapsor/runner synapsor-runner proposals check-freshness latest \
147
+ --config ./synapsor.runner.json \
148
+ --store ./.synapsor/local.db
149
+ ```
150
+
143
151
  Approve:
144
152
 
145
153
  ```bash
146
154
  npx -y -p @synapsor/runner synapsor-runner proposals approve wrp_123 \
155
+ --config ./synapsor.runner.json \
147
156
  --store ./.synapsor/local.db \
148
157
  --actor local_reviewer \
149
158
  --yes
@@ -151,6 +160,14 @@ npx -y -p @synapsor/runner synapsor-runner proposals approve wrp_123 \
151
160
 
152
161
  Before approval, the CLI prints the reviewer-critical proposal details: trusted principal, tenant, target row, primary key, required role, proposal hash/version, allowed columns, conflict guard, evidence bundle/query fingerprint, writeback boundary, source mutation state, and exact before/after diff. Interactive approval still requires typing `yes`; noninteractive scripts must pass `--yes`.
153
162
 
163
+ When `proposal_freshness` is configured for that capability, approval also
164
+ performs a live read-only check of the target and each declared same-source
165
+ supporting dependency. A fresh result is bound to the approval by proof digest.
166
+ A stale result records no approval and terminally conflicts the proposal; an
167
+ unavailable source records no approval and leaves it pending. Apply always
168
+ revalidates again because source data can change after approval. See
169
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
170
+
154
171
  Create a guarded writeback job from an approved proposal:
155
172
 
156
173
  ```bash
@@ -203,9 +220,11 @@ npx -y -p @synapsor/runner synapsor-runner ui --config synapsor.runner.json --st
203
220
  ```
204
221
 
205
222
  The UI shows setup summary, semantic tools, proposal states, exact diffs,
206
- evidence, approval state, receipts, and replay. It binds to `127.0.0.1` by
207
- default, uses a per-run local session token, and requires CSRF protection for
208
- approve/reject actions.
223
+ evidence, live freshness state, approval state, receipts, and replay. A
224
+ freshness-required approve control stays disabled until the live result is
225
+ fresh; the approve request performs another live check. The UI binds to
226
+ `127.0.0.1` by default, uses a per-run local session token, and requires CSRF
227
+ protection for approve/reject actions.
209
228
 
210
229
  The UI does not expose raw SQL, database URLs, password values, MCP approval
211
230
  tools, MCP commit tools, or controls that widen configured tables/columns.
@@ -191,6 +191,16 @@ business action, transaction boundaries, and safe errors. See
191
191
  [Writeback Executors](writeback-executors.md) and
192
192
  [Handler Helper](handler-helper.md).
193
193
 
194
+ For a direct SQL proposal whose approval depends on live source facts, use the
195
+ optional `proposal_freshness` overlay. It rechecks the target and declared
196
+ same-source supporting rows before approval, then locks and rechecks them again
197
+ inside the final PostgreSQL/MySQL transaction. Run
198
+ `doctor --check-writeback` to prove that the writer can perform every
199
+ dependency locking read. App-owned and cross-source effects must enforce their
200
+ own equivalent transactional preconditions; Runner rejects them from strict
201
+ freshness mode. See
202
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
203
+
194
204
  ## Local Ledger
195
205
 
196
206
  Default local ledger path:
@@ -589,6 +599,10 @@ Doctor reports should be redacted by default before sharing. They must not
589
599
  include database passwords, bearer tokens, handler secrets, or raw driver
590
600
  connection strings.
591
601
 
602
+ For freshness-enabled proposals, `--check-writeback` includes rollback-only
603
+ dependency lock probes. A passing metadata/read check alone does not prove that
604
+ the writer has the locking privilege needed at apply.
605
+
592
606
  ## Logging And Redaction
593
607
 
594
608
  Expected public outputs must avoid secrets in:
@@ -613,3 +627,8 @@ Before promoting a package or calling a build production-candidate:
613
627
  The release gate should cover typecheck, focused tests, packed-package install,
614
628
  quick demo, own-db fixture, MCP stdio/HTTP checks, direct writeback,
615
629
  app-owned executor paths, package dry-run, and docs/package consistency.
630
+ The 1.6.1 security gate additionally runs:
631
+
632
+ ```bash
633
+ corepack pnpm test:proposal-freshness
634
+ ```
@@ -0,0 +1,334 @@
1
+ # Proposal And Evidence Freshness
2
+
3
+ Runner 1.6.1 adds an optional fail-closed freshness gate for proposals whose
4
+ review depends on live source state.
5
+
6
+ This closes two different review gaps without changing the model-facing MCP
7
+ surface:
8
+
9
+ 1. Before a local approval, Runner can re-read the proposal target and refuse
10
+ approval if its exact version changed.
11
+ 2. A proposal can declare reviewed supporting rows whose exact versions must
12
+ still match before approval and again at apply.
13
+
14
+ Target-row drift was already blocked by the exact conflict guard at apply.
15
+ Version 1.6.1 does not replace that protection. It adds an earlier review-time
16
+ check and extends the final transaction to declared same-source dependencies.
17
+
18
+ ## What Freshness Does And Does Not Mean
19
+
20
+ Keep these controls separate:
21
+
22
+ | Control | What it proves |
23
+ | --- | --- |
24
+ | Proposal hash and version | The approval refers to one immutable proposal shape. |
25
+ | Evidence and query fingerprints | The stored review material has not silently changed. |
26
+ | Freshness proof | The target and declared dependencies matched their captured exact versions at one live check. |
27
+ | Approval | A trusted reviewer or policy authorized that exact proposal and proof. |
28
+ | Apply conflict checks | The target and dependencies still match inside the mutation transaction. |
29
+ | Idempotency receipt | Retrying the same completed writeback identity does not duplicate its effect. |
30
+
31
+ A successful approval-time check is not a lock held until apply. Data can
32
+ change one millisecond later, so apply always revalidates again.
33
+
34
+ Runner never refreshes a stale proposal in place. The operator must perform a
35
+ new source read and create a new proposal with a new hash and review.
36
+
37
+ ## Configuration
38
+
39
+ Freshness is a Runner deployment overlay in `synapsor.runner.json`. Version
40
+ 1.6.1 adds no DSL clause and no canonical Spec field. Existing hand-authored
41
+ DSL, canonical JSON, contract digests, and `tools/list` output remain unchanged
42
+ when this optional section is absent.
43
+
44
+ ```json
45
+ {
46
+ "proposal_freshness": {
47
+ "billing.propose_credit": {
48
+ "approval": "required",
49
+ "dependencies": [
50
+ {
51
+ "id": "invoice_eligibility",
52
+ "capability": "billing.inspect_invoice",
53
+ "identity_from_arg": "invoice_id",
54
+ "version_column": "updated_at"
55
+ }
56
+ ]
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ The names mean:
63
+
64
+ - `billing.propose_credit`: an existing reviewed proposal capability.
65
+ - `approval: "required"`: every approval decision requires a new short-lived
66
+ live proof.
67
+ - `invoice_eligibility`: an operator-defined stable dependency label.
68
+ - `billing.inspect_invoice`: an existing reviewed single-row read capability.
69
+ - `invoice_id`: an existing bounded scalar argument on the proposal. Its value
70
+ identifies the supporting row through the read capability's fixed lookup.
71
+ - `updated_at`: the exact version/conflict column on that supporting row.
72
+
73
+ The proposal and supporting read must use the same source and trusted context.
74
+ The supporting capability supplies the fixed engine, schema, table, primary
75
+ key, tenant key, optional principal key, and lookup shape. None of those
76
+ identifiers can come from an MCP argument.
77
+
78
+ Strict validation rejects:
79
+
80
+ - unknown or non-proposal policy keys;
81
+ - app-owned or cross-source writeback;
82
+ - unknown, aggregate, protected, or non-single-row dependencies;
83
+ - missing or untrusted tenant scope;
84
+ - incompatible principal scope;
85
+ - missing scalar identity arguments;
86
+ - unsafe or missing version identifiers;
87
+ - duplicate, self-referential, or more than 16 dependencies.
88
+
89
+ Run both static validation and live writeback probes:
90
+
91
+ ```bash
92
+ synapsor-runner config validate --config ./synapsor.runner.json
93
+ synapsor-runner doctor --check-writeback --config ./synapsor.runner.json
94
+ ```
95
+
96
+ `doctor --check-writeback` performs rollback-only lock probes. The read role
97
+ needs scoped `SELECT`. The writer must be able to lock every declared
98
+ supporting row in the final transaction. PostgreSQL and MySQL may require a
99
+ narrow update/locking privilege in addition to `SELECT`; use a dedicated
100
+ least-privilege writer and verify the exact grants on the deployed database.
101
+
102
+ ## Proposal Creation
103
+
104
+ When a freshness-enabled proposal is created, Runner:
105
+
106
+ 1. reads the target through the existing reviewed proposal capability;
107
+ 2. reads each declared dependency through its reviewed single-row capability
108
+ in a read-only transaction;
109
+ 3. captures only the fixed identity, exact version, and bounded
110
+ evidence/query-audit linkage;
111
+ 4. sorts dependencies into deterministic lock order;
112
+ 5. binds the resulting `synapsor.freshness-authority.v1` object into the
113
+ immutable change set and proposal hash.
114
+
115
+ The authority contains digests and fixed identifiers. It does not add source
116
+ rows, kept-out values, database URLs, credentials, or a second copy of trusted
117
+ tenant/principal values to the proposal.
118
+
119
+ The published protocol representation is inspectable without reading the
120
+ TypeScript implementation:
121
+
122
+ - `schemas/freshness-authority.v1.schema.json` defines the optional authority
123
+ carried by change sets and writeback jobs.
124
+ - `schemas/freshness-proof.v1.schema.json` defines the immutable live-check
125
+ proof stored in the ledger.
126
+ - `fixtures/protocol/change-set.freshness-update.v2.json`,
127
+ `fixtures/protocol/writeback-job.freshness-update.v2.json`,
128
+ `fixtures/protocol/freshness-authority.invoice.v1.json`, and
129
+ `fixtures/protocol/freshness-proof.fresh.v1.json` are hash-manifested
130
+ conformance examples.
131
+
132
+ The JSON Schemas enforce the public structure. The executable protocol
133
+ validator additionally verifies canonical descriptor, dependency-set, and
134
+ proof digests plus cross-field invariants that JSON Schema cannot express.
135
+
136
+ For UPDATE and DELETE, target mode is `exact_guard`. For a bounded set it is
137
+ `frozen_set`. INSERT has no prior target row, so target freshness is
138
+ `not_applicable`; its source uniqueness and idempotency guards still apply.
139
+
140
+ ## Approval-Time Check
141
+
142
+ Inspect the newest proposal without copying an ID:
143
+
144
+ ```bash
145
+ synapsor-runner proposals check-freshness latest \
146
+ --config ./synapsor.runner.json \
147
+ --store ./.synapsor/local.db
148
+ ```
149
+
150
+ Add `--details` for per-check digests or `--json` for the versioned
151
+ `synapsor.proposal-freshness-result.v1` document.
152
+
153
+ Stable exit codes are:
154
+
155
+ | Status | Exit code | Meaning |
156
+ | --- | ---: | --- |
157
+ | `fresh` | 0 | Every required target/dependency version matched. |
158
+ | `not_required` | 0 | Legacy proposal; its normal apply guard still applies. |
159
+ | `stale` | 3 | A target or dependency changed, disappeared, or left trusted scope. |
160
+ | `unavailable` | 4 | The source could not be checked; retry may be appropriate. |
161
+ | `invalid` | 5 | Stored or configured authority did not validate. |
162
+ | `unsupported` | 6 | The topology cannot provide the claimed guarantee. |
163
+
164
+ Approval runs the same live check automatically:
165
+
166
+ ```bash
167
+ synapsor-runner proposals approve latest \
168
+ --yes \
169
+ --actor billing_reviewer \
170
+ --config ./synapsor.runner.json \
171
+ --store ./.synapsor/local.db
172
+ ```
173
+
174
+ A successful check records an immutable `synapsor.freshness-proof.v1` event.
175
+ The proof binds:
176
+
177
+ - proposal ID, hash, and integer version;
178
+ - dependency-set digest;
179
+ - checked and short validity times;
180
+ - source adapter identity;
181
+ - bounded target/supporting counts and result codes;
182
+ - expected/observed version digests, not source values;
183
+ - the proof's own digest.
184
+
185
+ Every human quorum decision receives a distinct live proof. Policy
186
+ auto-approval uses the same evaluator. A stale check records no approval and
187
+ makes the proposal terminal `conflict`. An unavailable check records no
188
+ approval and leaves it `pending_review`.
189
+
190
+ ## Apply-Time Revalidation
191
+
192
+ Apply remains an operator/worker action outside MCP:
193
+
194
+ ```bash
195
+ synapsor-runner apply latest \
196
+ --yes \
197
+ --config ./synapsor.runner.json \
198
+ --store ./.synapsor/local.db
199
+ ```
200
+
201
+ For direct SQL PostgreSQL/MySQL writeback, Runner:
202
+
203
+ 1. begins the existing source transaction;
204
+ 2. locks supporting rows in deterministic source/schema/table/identity order;
205
+ 3. rechecks each exact version under trusted tenant and principal scope;
206
+ 4. locks and rechecks the target row or every frozen set member;
207
+ 5. performs the existing guarded mutation only if all checks pass;
208
+ 6. finalizes the configured source or Runner-ledger receipt behavior.
209
+
210
+ PostgreSQL uses parameterized `SELECT ... FOR UPDATE` and quoted reviewed
211
+ identifiers. MySQL uses the equivalent InnoDB locking read. A stale supporting
212
+ row returns `FRESHNESS_DEPENDENCY_STALE`, zero affected rows, and rolls back the
213
+ whole transaction. A stale target continues to use the exact target conflict
214
+ path. A stale conflict is never converted to `already_applied`.
215
+
216
+ For a bounded set, one stale member or one stale dependency aborts every member.
217
+
218
+ ## Timeline
219
+
220
+ Change before approval:
221
+
222
+ ```text
223
+ proposal captures target v4 + invoice v8
224
+ invoice moves to v9
225
+ reviewer approves
226
+ -> live preflight sees v9 != v8
227
+ -> no approval, no source mutation
228
+ -> create a new read and proposal
229
+ ```
230
+
231
+ Change after approval:
232
+
233
+ ```text
234
+ proposal captures target v4 + invoice v8
235
+ approval preflight succeeds and binds proof
236
+ invoice moves to v9
237
+ trusted apply starts
238
+ -> transaction locks invoice and sees v9 != v8
239
+ -> conflict, zero source mutation
240
+ -> create a new read and proposal
241
+ ```
242
+
243
+ The second timeline is why approval-time freshness can never replace
244
+ apply-time concurrency control.
245
+
246
+ ## Supporting Evidence Example
247
+
248
+ Assume `credits.propose_account_credit` changes an account row, but eligibility
249
+ depends on a separate invoice:
250
+
251
+ ```text
252
+ target account version 12 (unchanged)
253
+ supporting invoice status=open, version 7
254
+ proposal credit $25 because invoice is open
255
+ ```
256
+
257
+ If a payment process closes the invoice and advances it to version 8 while the
258
+ account stays at version 12, the target guard alone would still pass. Declaring
259
+ the invoice read as a freshness dependency makes approval or apply fail closed
260
+ on the invoice version mismatch.
261
+
262
+ Freshness checks the reviewed exact version, not whether the current value
263
+ "looks equivalent." Returning a row to its old value does not revive a proposal
264
+ that already became stale.
265
+
266
+ ## Ledger, Replay, Reports, And Metrics
267
+
268
+ Use the no-ID lifecycle view:
269
+
270
+ ```bash
271
+ synapsor-runner lifecycle --details --store ./.synapsor/local.db
272
+ synapsor-runner replay show latest --details --store ./.synapsor/local.db
273
+ ```
274
+
275
+ Lifecycle and replay retain the proof chain: authority, proof event, approval
276
+ proof digest, apply result, receipt, and replay linkage. Backup/restore and the
277
+ shared PostgreSQL runtime store preserve the same versioned records.
278
+
279
+ Compliance reports include only bounded freshness metadata and digests. Metrics
280
+ separate checks, stale target/supporting outcomes, blocked approvals, and
281
+ apply-time blocks. Logs never include source rows or credential values.
282
+
283
+ ## Cloud And App-Owned Executors
284
+
285
+ Cloud-linked approval remains proposal/digest authority. Cloud may retain
286
+ bounded freshness metadata, but it does not connect to or read the customer
287
+ database. A Runner next to the source revalidates dependencies when it applies
288
+ a Cloud-approved lease. Cloud outage never enables weaker local approval or
289
+ apply authority.
290
+
291
+ Strict freshness currently supports same-database direct SQL writeback only.
292
+ Runner rejects strict freshness configuration for app-owned handlers and
293
+ cross-source dependencies because it cannot place those checks and effects in
294
+ one transaction.
295
+
296
+ An app-owned executor can implement equivalent preconditions, but the
297
+ application must own and test:
298
+
299
+ - source reads and locking;
300
+ - tenant/principal authorization;
301
+ - exact expected versions;
302
+ - transaction boundaries;
303
+ - idempotency and ambiguous outcomes;
304
+ - safe receipts.
305
+
306
+ Do not describe those application guarantees as Runner's atomic direct-SQL
307
+ freshness guarantee.
308
+
309
+ ## Security Boundary
310
+
311
+ Freshness adds no model-facing approval, apply, activation, bypass, SQL, table,
312
+ column, scope, or policy argument. Existing `tools/list` output is unchanged.
313
+
314
+ It also does not replace:
315
+
316
+ - correct monotonic version columns;
317
+ - least-privilege read/write roles;
318
+ - PostgreSQL RLS, restricted views, or tenant-bound credentials;
319
+ - application authorization;
320
+ - source backups and recovery testing;
321
+ - operator identity and host/store protection.
322
+
323
+ The dedicated live gate is:
324
+
325
+ ```bash
326
+ corepack pnpm test:proposal-freshness
327
+ ```
328
+
329
+ It runs disposable PostgreSQL and MySQL scenarios for approval preflight,
330
+ proof-bound human/quorum/policy approval, target/supporting drift before and
331
+ after approval, source and Runner-ledger receipts, bounded-set rollback,
332
+ reversible UPDATE, DELETE, shared runtime state, Cloud-approved local
333
+ revalidation, writer lock probes, idempotent retry, and kept-out-value
334
+ non-disclosure.
@@ -10,7 +10,34 @@ npx -y -p @synapsor/runner synapsor-runner demo --quick
10
10
  The OSS runner command is `synapsor-runner`. The `synapsor` command is reserved
11
11
  for the Synapsor Cloud CLI.
12
12
 
13
- ## 1.6.0 (prepared, not published)
13
+ ## 1.6.1 (prepared, not published)
14
+
15
+ ### Fail-closed proposal and evidence freshness
16
+
17
+ - An optional `proposal_freshness` Runner overlay can require a live target and
18
+ explicitly declared same-source supporting-row check immediately before
19
+ every local approval.
20
+ - Each successful human, quorum, or policy approval references a distinct
21
+ short-lived proof bound to the exact proposal hash/version and dependency
22
+ set. Stale and unavailable checks record no approval.
23
+ - Direct PostgreSQL/MySQL apply locks and rechecks dependencies inside the
24
+ mutation transaction. Source drift after approval returns a conflict and
25
+ changes zero rows; bounded sets roll back completely.
26
+ - `proposals check-freshness latest`, Workbench gating, lifecycle, replay,
27
+ compliance reports, metrics, logs, and `doctor --check-writeback` expose the
28
+ bounded proof and operational state without source rows, kept-out values,
29
+ trusted scope values, or credentials.
30
+ - Cloud does not read the source. A local Runner revalidates a Cloud-approved
31
+ job before apply. Strict app-owned and cross-source freshness remains an
32
+ explicitly unsupported topology.
33
+ - Existing contracts, exact digests, DSL, model-facing tool lists, and
34
+ non-freshness deployments keep their prior behavior.
35
+
36
+ Prepared package version: `@synapsor/runner@1.6.1`.
37
+ `@synapsor/spec@1.5.0` and `@synapsor/dsl@1.5.0` are unchanged. Nothing has
38
+ been published by this repository change.
39
+
40
+ ## 1.6.0 (published 2026-07-23)
14
41
 
15
42
  ### Connect, Explore, Protect
16
43
 
@@ -39,9 +66,8 @@ for the Synapsor Cloud CLI.
39
66
  new fields. Published legacy contracts preserve exact normalization/digests,
40
67
  and established CLI/headless/CI routes keep their behavior.
41
68
 
42
- Prepared package versions: `@synapsor/runner@1.6.0`,
43
- `@synapsor/dsl@1.5.0`, and `@synapsor/spec@1.5.0`. Nothing has been published
44
- by this repository change.
69
+ Published package versions: `@synapsor/runner@1.6.0`,
70
+ `@synapsor/dsl@1.5.0`, and `@synapsor/spec@1.5.0`.
45
71
 
46
72
  ## 1.5.4 (published 2026-07-22)
47
73
 
@@ -453,6 +453,52 @@ Distinct verified subjects fill quorum slots; one subject cannot count twice.
453
453
  A rejection is terminal. Policy auto-approval is deferred when quorum is
454
454
  greater than one.
455
455
 
456
+ ## Proposal freshness
457
+
458
+ `proposal_freshness` is an optional Runner-only deployment overlay. It does not
459
+ change the public DSL or canonical contract and does not alter legacy proposal
460
+ or `tools/list` behavior when absent.
461
+
462
+ ```json
463
+ {
464
+ "proposal_freshness": {
465
+ "billing.propose_credit": {
466
+ "approval": "required",
467
+ "dependencies": [
468
+ {
469
+ "id": "invoice_eligibility",
470
+ "capability": "billing.inspect_invoice",
471
+ "identity_from_arg": "invoice_id",
472
+ "version_column": "updated_at"
473
+ }
474
+ ]
475
+ }
476
+ }
477
+ }
478
+ ```
479
+
480
+ The top-level key is an existing proposal capability. `approval` must be
481
+ `required`. Every dependency must reference an existing reviewed single-row
482
+ read on the same source/context and map one bounded scalar proposal argument to
483
+ that read's fixed lookup. The read capability supplies schema, table, primary
484
+ key, tenant/principal keys, and source; the model cannot supply or widen them.
485
+ At most 16 dependencies are allowed.
486
+
487
+ The proposal target and dependencies are read again before every approval.
488
+ Direct SQL apply locks and rechecks them again inside the source transaction.
489
+ Strict freshness is rejected for app-owned and cross-source effects because
490
+ Runner cannot make those checks atomic with the external effect.
491
+
492
+ Use:
493
+
494
+ ```bash
495
+ synapsor-runner proposals check-freshness latest --config ./synapsor.runner.json
496
+ synapsor-runner doctor --check-writeback --config ./synapsor.runner.json
497
+ ```
498
+
499
+ The complete validation, privilege, lifecycle, and error-code contract is in
500
+ [Proposal And Evidence Freshness](proposal-evidence-freshness.md).
501
+
456
502
  ## Rate limits
457
503
 
458
504
  ```json