@synapsor/runner 1.5.4 → 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 (81) hide show
  1. package/CHANGELOG.md +68 -2
  2. package/README.md +131 -124
  3. package/dist/authoring.mjs +405 -9
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/local-ui.d.ts +6 -1
  6. package/dist/local-ui.d.ts.map +1 -1
  7. package/dist/runner.mjs +17206 -12169
  8. package/dist/runtime.mjs +11249 -8542
  9. package/dist/shadow.mjs +408 -15
  10. package/docs/README.md +14 -6
  11. package/docs/aggregate-reads.md +22 -0
  12. package/docs/app-owned-executors.md +8 -0
  13. package/docs/auto-boundary-and-scoped-explore.md +338 -0
  14. package/docs/capability-authoring.md +75 -0
  15. package/docs/conformance.md +16 -0
  16. package/docs/current-scope.md +60 -28
  17. package/docs/cursor-plugin.md +20 -3
  18. package/docs/dsl-reference.md +78 -0
  19. package/docs/getting-started-own-database.md +39 -35
  20. package/docs/limitations.md +37 -7
  21. package/docs/local-mode.md +22 -3
  22. package/docs/production.md +19 -0
  23. package/docs/proposal-evidence-freshness.md +334 -0
  24. package/docs/release-notes.md +63 -4
  25. package/docs/runner-config-reference.md +46 -0
  26. package/docs/schema-api-candidates.md +28 -1
  27. package/docs/security-boundary.md +17 -0
  28. package/docs/store-lifecycle.md +8 -0
  29. package/docs/troubleshooting-first-run.md +174 -0
  30. package/docs/writeback-executors.md +8 -0
  31. package/examples/app-owned-writeback/command-handler.mjs +0 -0
  32. package/examples/auto-boundary-churn/README.md +23 -0
  33. package/examples/auto-boundary-churn/app/page.tsx +8 -0
  34. package/examples/auto-boundary-churn/docker-compose.yml +16 -0
  35. package/examples/auto-boundary-churn/package.json +18 -0
  36. package/examples/auto-boundary-churn/prisma/schema.prisma +36 -0
  37. package/examples/auto-boundary-churn/seed/postgres.sql +126 -0
  38. package/examples/mcp-postgres-billing-app-handler/scripts/run-demo.sh +0 -0
  39. package/examples/reference-support-billing-app/scripts/run-demo.sh +0 -0
  40. package/examples/support-billing-agent/scripts/run-demo.sh +0 -0
  41. package/examples/support-billing-agent/scripts/run-evaluation.sh +0 -0
  42. package/fixtures/compatibility/published-1.5.4/manifest.json +76 -0
  43. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
  44. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
  45. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
  46. package/fixtures/compatibility/published-1.5.4/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
  47. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
  48. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
  49. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
  50. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
  51. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
  52. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
  53. package/fixtures/compatibility/published-1.5.4/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
  54. package/fixtures/compatibility/published-1.6.0/manifest.json +76 -0
  55. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
  56. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
  57. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
  58. package/fixtures/compatibility/published-1.6.0/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
  59. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
  60. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
  61. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
  62. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
  63. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
  64. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
  65. package/fixtures/compatibility/published-1.6.0/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
  66. package/fixtures/protocol/MANIFEST.json +37 -7
  67. package/fixtures/protocol/change-set.freshness-update.v2.json +58 -0
  68. package/fixtures/protocol/freshness-authority.invoice.v1.json +35 -0
  69. package/fixtures/protocol/freshness-proof.fresh.v1.json +38 -0
  70. package/fixtures/protocol/writeback-job.freshness-update.v2.json +50 -0
  71. package/llms.txt +38 -0
  72. package/package.json +12 -8
  73. package/schemas/change-set.v1.schema.json +1 -0
  74. package/schemas/change-set.v2.schema.json +1 -0
  75. package/schemas/change-set.v3.schema.json +1 -0
  76. package/schemas/freshness-authority.v1.schema.json +89 -0
  77. package/schemas/freshness-proof.v1.schema.json +73 -0
  78. package/schemas/synapsor.runner.schema.json +50 -1
  79. package/schemas/writeback-job.v1.schema.json +1 -0
  80. package/schemas/writeback-job.v2.schema.json +1 -0
  81. package/schemas/writeback-job.v3.schema.json +1 -0
package/dist/shadow.mjs CHANGED
@@ -64,6 +64,8 @@ var protocolVersions = {
64
64
  runnerProposal: "synapsor.runner-proposal.v1",
65
65
  runnerActivity: "synapsor.runner-activity.v1",
66
66
  principalScope: "synapsor.principal-scope.v1",
67
+ freshnessAuthority: "synapsor.freshness-authority.v1",
68
+ freshnessProof: "synapsor.freshness-proof.v1",
67
69
  legacyWritebackJob: "1.0",
68
70
  normalizedWritebackJobV2: "2.0",
69
71
  normalizedWritebackJobV3: "3.0",
@@ -111,6 +113,101 @@ var principalScopeGuardSchema = z.object({
111
113
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: "principal scope fingerprint does not match its trusted value", path: ["value_fingerprint"] });
112
114
  }
113
115
  });
116
+ var freshnessDependencyUnsignedSchema = z.object({
117
+ id: safeIdentifier,
118
+ capability: z.string().regex(/^[A-Za-z_][A-Za-z0-9_.-]*\.[A-Za-z_][A-Za-z0-9_.-]*$/, "expected reviewed capability name"),
119
+ source_id: z.string().min(1).max(160),
120
+ engine: writebackEngineSchema,
121
+ target: z.object({
122
+ schema: safeIdentifier,
123
+ table: safeIdentifier,
124
+ primary_key: columnValueSchema,
125
+ tenant_column: safeIdentifier,
126
+ principal_column: safeIdentifier.optional()
127
+ }),
128
+ expected_version: columnValueSchema,
129
+ evidence: z.object({
130
+ bundle_id: z.string().min(1).max(200),
131
+ query_fingerprint: sha256
132
+ })
133
+ });
134
+ var freshnessDependencyV1Schema = freshnessDependencyUnsignedSchema.extend({
135
+ descriptor_digest: sha256
136
+ }).superRefine((dependency, ctx) => {
137
+ const { descriptor_digest: _digest, ...unsigned } = dependency;
138
+ if (canonicalJsonDigest(unsigned) !== dependency.descriptor_digest) {
139
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness dependency digest mismatch", path: ["descriptor_digest"] });
140
+ }
141
+ });
142
+ var freshnessAuthorityUnsignedSchema = z.object({
143
+ schema_version: z.literal(protocolVersions.freshnessAuthority),
144
+ required: z.literal(true),
145
+ target: z.object({
146
+ mode: z.enum(["exact_guard", "frozen_set", "not_applicable"]),
147
+ member_count: z.number().int().min(0).max(100)
148
+ }),
149
+ dependencies: z.array(freshnessDependencyV1Schema).max(16)
150
+ });
151
+ var freshnessAuthorityV1Schema = freshnessAuthorityUnsignedSchema.extend({
152
+ dependency_set_digest: sha256
153
+ }).superRefine((authority, ctx) => {
154
+ const identities = authority.dependencies.map((dependency) => dependency.id);
155
+ if (new Set(identities).size !== identities.length) {
156
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness dependency ids must be unique", path: ["dependencies"] });
157
+ }
158
+ const sorted = [...authority.dependencies].sort((left, right) => left.source_id.localeCompare(right.source_id) || left.target.schema.localeCompare(right.target.schema) || left.target.table.localeCompare(right.target.table) || JSON.stringify(left.target.primary_key.value).localeCompare(JSON.stringify(right.target.primary_key.value)) || left.id.localeCompare(right.id));
159
+ if (JSON.stringify(sorted) !== JSON.stringify(authority.dependencies)) {
160
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness dependencies must use deterministic lock order", path: ["dependencies"] });
161
+ }
162
+ const { dependency_set_digest: _digest, ...unsigned } = authority;
163
+ if (canonicalJsonDigest(unsigned) !== authority.dependency_set_digest) {
164
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness dependency-set digest mismatch", path: ["dependency_set_digest"] });
165
+ }
166
+ });
167
+ var freshnessCheckStatusSchema = z.enum(["fresh", "stale", "unavailable", "invalid", "unsupported"]);
168
+ var freshnessItemResultSchema = z.object({
169
+ id: z.string().min(1).max(160),
170
+ kind: z.enum(["target", "supporting"]),
171
+ status: z.enum(["fresh", "stale", "not_applicable", "unavailable", "invalid", "unsupported"]),
172
+ safe_code: z.string().regex(/^[A-Z][A-Z0-9_]*$/),
173
+ expected_version_digest: sha256.optional(),
174
+ observed_version_digest: sha256.optional()
175
+ });
176
+ var freshnessProofUnsignedSchema = z.object({
177
+ schema_version: z.literal(protocolVersions.freshnessProof),
178
+ proposal_id: z.string().min(1),
179
+ proposal_hash: sha256,
180
+ proposal_version: z.number().int().positive(),
181
+ dependency_set_digest: sha256,
182
+ checked_at: z.string().datetime(),
183
+ valid_until: z.string().datetime(),
184
+ source_adapters: z.array(z.object({
185
+ source_id: z.string().min(1).max(160),
186
+ engine: writebackEngineSchema
187
+ })).min(1).max(8),
188
+ result: freshnessCheckStatusSchema,
189
+ safe_code: z.string().regex(/^[A-Z][A-Z0-9_]*$/),
190
+ target_count: z.number().int().min(0).max(100),
191
+ supporting_count: z.number().int().min(0).max(16),
192
+ checks: z.array(freshnessItemResultSchema).max(116)
193
+ });
194
+ var freshnessProofV1Schema = freshnessProofUnsignedSchema.extend({
195
+ proof_digest: sha256
196
+ }).superRefine((proof, ctx) => {
197
+ if (Date.parse(proof.valid_until) < Date.parse(proof.checked_at)) {
198
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness proof validity ends before it was checked", path: ["valid_until"] });
199
+ }
200
+ if (proof.checks.filter((check) => check.kind === "target").length !== proof.target_count) {
201
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness target count mismatch", path: ["target_count"] });
202
+ }
203
+ if (proof.checks.filter((check) => check.kind === "supporting").length !== proof.supporting_count) {
204
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness supporting count mismatch", path: ["supporting_count"] });
205
+ }
206
+ const { proof_digest: _digest, ...unsigned } = proof;
207
+ if (canonicalJsonDigest(unsigned) !== proof.proof_digest) {
208
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "freshness proof digest mismatch", path: ["proof_digest"] });
209
+ }
210
+ });
114
211
  var resolvedDeduplicationComponentSchema = z.object({
115
212
  column: safeIdentifier,
116
213
  value: scalar,
@@ -240,6 +337,7 @@ var changeSetV1Schema = z.object({
240
337
  allowed_columns: z.array(z.string().min(1)).min(1),
241
338
  expected_version: columnValueSchema
242
339
  }),
340
+ freshness: freshnessAuthorityV1Schema.optional(),
243
341
  evidence: z.object({
244
342
  bundle_id: z.string().min(1),
245
343
  query_fingerprint: sha256,
@@ -260,6 +358,7 @@ var changeSetV1Schema = z.object({
260
358
  }),
261
359
  created_at: z.string().min(1)
262
360
  }).superRefine((changeSet, ctx) => {
361
+ validateFreshnessBinding(changeSet.freshness, changeSet.source.source_id, sourceEngine(changeSet.source.kind), "exact_guard", 1, ctx);
263
362
  const allowed = new Set(changeSet.guards.allowed_columns);
264
363
  for (const column of Object.keys(changeSet.patch)) {
265
364
  if (!allowed.has(column)) {
@@ -327,6 +426,7 @@ var changeSetV2Schema = z.object({
327
426
  version_advance: versionAdvanceSchema.optional(),
328
427
  deduplication: z.object({ components: z.array(resolvedDeduplicationComponentSchema).min(1).max(8) }).optional()
329
428
  }),
429
+ freshness: freshnessAuthorityV1Schema.optional(),
330
430
  reversibility: reversibilityRequestSchema.optional(),
331
431
  evidence: z.object({
332
432
  bundle_id: z.string().min(1),
@@ -346,6 +446,14 @@ var changeSetV2Schema = z.object({
346
446
  integrity: z.object({ proposal_hash: sha256 }),
347
447
  created_at: z.string().min(1)
348
448
  }).superRefine((changeSet, ctx) => {
449
+ validateFreshnessBinding(
450
+ changeSet.freshness,
451
+ changeSet.source.source_id,
452
+ sourceEngine(changeSet.source.kind),
453
+ changeSet.operation === "single_row_insert" ? "not_applicable" : "exact_guard",
454
+ changeSet.operation === "single_row_insert" ? 0 : 1,
455
+ ctx
456
+ );
349
457
  const allowed = new Set(changeSet.guards.allowed_columns);
350
458
  for (const column of Object.keys(changeSet.patch)) {
351
459
  if (!allowed.has(column)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: `patch column not allowed: ${column}`, path: ["patch", column] });
@@ -412,6 +520,7 @@ var changeSetV3Schema = z.object({
412
520
  expected_version: columnValueSchema.optional(),
413
521
  version_advance: versionAdvanceSchema.optional()
414
522
  }),
523
+ freshness: freshnessAuthorityV1Schema.optional(),
415
524
  frozen_set: frozenSetSchema,
416
525
  reversibility: reversibilityRequestSchema.optional(),
417
526
  evidence: z.object({ bundle_id: z.string().min(1), query_fingerprint: sha256, items: z.array(z.unknown()).max(100) }).passthrough(),
@@ -426,6 +535,14 @@ var changeSetV3Schema = z.object({
426
535
  integrity: z.object({ proposal_hash: sha256 }),
427
536
  created_at: z.string().min(1)
428
537
  }).superRefine((changeSet, ctx) => {
538
+ validateFreshnessBinding(
539
+ changeSet.freshness,
540
+ changeSet.source.source_id,
541
+ sourceEngine(changeSet.source.kind),
542
+ changeSet.operation === "batch_insert" ? "not_applicable" : "frozen_set",
543
+ changeSet.operation === "batch_insert" ? 0 : changeSet.frozen_set.row_count,
544
+ ctx
545
+ );
429
546
  if (changeSet.source.primary_key.value !== void 0 || changeSet.guards.expected_version) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "set guards live on frozen members, not the top-level envelope", path: ["frozen_set"] });
430
547
  const allowed = new Set(changeSet.guards.allowed_columns);
431
548
  for (const column of Object.keys(changeSet.patch)) if (!allowed.has(column)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: `patch column not allowed: ${column}`, path: ["patch", column] });
@@ -521,6 +638,7 @@ var writebackJobV1Schema = z.object({
521
638
  allowed_columns: z.array(safeIdentifier).min(1),
522
639
  patch: scalarMap,
523
640
  conflict_guard: publicConflictGuardSchema,
641
+ freshness: freshnessAuthorityV1Schema.optional(),
524
642
  idempotency_key: z.string().min(1),
525
643
  lease: z.object({
526
644
  lease_id: z.string().min(1),
@@ -528,6 +646,7 @@ var writebackJobV1Schema = z.object({
528
646
  expires_at: z.string().min(1)
529
647
  })
530
648
  }).superRefine((job, ctx) => {
649
+ validateFreshnessBinding(job.freshness, job.runner_scope.source_id, job.engine, "exact_guard", 1, ctx);
531
650
  validateAllowedPatchColumns(job.allowed_columns, Object.keys(job.patch), job.target.primary_key.column, job.tenant_guard.column, ctx);
532
651
  if (job.principal_scope && job.allowed_columns.includes(job.principal_scope.column)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "principal scope column must not be patch-allowlisted", path: ["allowed_columns"] });
533
652
  });
@@ -549,6 +668,7 @@ var normalizedWritebackJobV1Schema = writebackJobV1Schema.transform((job) => ({
549
668
  allowed_columns: job.allowed_columns,
550
669
  patch: job.patch,
551
670
  conflict_guard: normalizeConflictGuard(job.conflict_guard),
671
+ ...job.freshness ? { freshness: job.freshness } : {},
552
672
  idempotency_key: job.idempotency_key,
553
673
  lease_expires_at: job.lease.expires_at,
554
674
  attempt_count: job.lease.attempt
@@ -582,10 +702,12 @@ var legacyWritebackJobSchema = z.object({
582
702
  z.object({ kind: z.literal("row_hash"), expected_hash: z.string().min(1) }),
583
703
  z.object({ kind: z.literal("none") })
584
704
  ]),
705
+ freshness: freshnessAuthorityV1Schema.optional(),
585
706
  idempotency_key: z.string().min(1),
586
707
  lease_expires_at: z.union([z.string(), z.number()]),
587
708
  attempt_count: z.number().int().nonnegative().optional()
588
709
  }).superRefine((job, ctx) => {
710
+ validateFreshnessBinding(job.freshness, job.source_id, job.engine, "exact_guard", 1, ctx);
589
711
  validateAllowedPatchColumns(job.allowed_columns, Object.keys(job.patch), job.target.primary_key.column, job.target.tenant_guard.column, ctx);
590
712
  if (job.target.principal_scope && job.allowed_columns.includes(job.target.principal_scope.column)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "principal scope column must not be patch-allowlisted", path: ["allowed_columns"] });
591
713
  });
@@ -612,6 +734,7 @@ var normalizedWritebackJobV2InputSchema = z.object({
612
734
  z.object({ kind: z.literal("row_hash"), expected_hash: z.string().min(1) }),
613
735
  z.object({ kind: z.literal("none") })
614
736
  ]),
737
+ freshness: freshnessAuthorityV1Schema.optional(),
615
738
  version_advance: versionAdvanceSchema.optional(),
616
739
  deduplication: z.object({ components: z.array(resolvedDeduplicationComponentSchema).min(1).max(8) }).optional(),
617
740
  idempotency_key: z.string().min(1),
@@ -619,6 +742,14 @@ var normalizedWritebackJobV2InputSchema = z.object({
619
742
  attempt_count: z.number().int().positive(),
620
743
  inverse_capture: inverseDescriptorV1Schema.optional()
621
744
  }).superRefine((job, ctx) => {
745
+ validateFreshnessBinding(
746
+ job.freshness,
747
+ job.source_id,
748
+ job.engine,
749
+ job.operation === "single_row_insert" ? "not_applicable" : "exact_guard",
750
+ job.operation === "single_row_insert" ? 0 : 1,
751
+ ctx
752
+ );
622
753
  if (job.operation !== "single_row_insert" && job.target.primary_key.value === void 0) {
623
754
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: "UPDATE and DELETE require a primary-key value", path: ["target", "primary_key", "value"] });
624
755
  }
@@ -684,6 +815,7 @@ var writebackJobV2Schema = z.object({
684
815
  principal_scope: principalScopeGuardSchema.optional(),
685
816
  allowed_columns: z.array(safeIdentifier),
686
817
  mutation: writebackMutationV2Schema,
818
+ freshness: freshnessAuthorityV1Schema.optional(),
687
819
  idempotency_key: z.string().min(1),
688
820
  inverse_capture: inverseDescriptorV1Schema.optional(),
689
821
  lease: z.object({
@@ -692,6 +824,14 @@ var writebackJobV2Schema = z.object({
692
824
  expires_at: z.string().min(1)
693
825
  })
694
826
  }).superRefine((job, ctx) => {
827
+ validateFreshnessBinding(
828
+ job.freshness,
829
+ job.runner_scope.source_id,
830
+ job.engine,
831
+ job.mutation.kind === "single_row_insert" ? "not_applicable" : "exact_guard",
832
+ job.mutation.kind === "single_row_insert" ? 0 : 1,
833
+ ctx
834
+ );
695
835
  const mutation = job.mutation;
696
836
  if (mutation.kind !== "single_row_insert" && job.target.primary_key.value === void 0) {
697
837
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: "UPDATE and DELETE require a primary-key value", path: ["target", "primary_key", "value"] });
@@ -730,6 +870,7 @@ var writebackJobV2Schema = z.object({
730
870
  conflict_guard: job.mutation.kind === "single_row_insert" ? { kind: "none" } : normalizeConflictGuard(job.mutation.conflict_guard),
731
871
  ...job.mutation.kind === "single_row_update" && job.mutation.version_advance ? { version_advance: job.mutation.version_advance } : {},
732
872
  ...job.mutation.kind === "single_row_insert" ? { deduplication: job.mutation.deduplication } : {},
873
+ ...job.freshness ? { freshness: job.freshness } : {},
733
874
  idempotency_key: job.idempotency_key,
734
875
  ...job.inverse_capture ? { inverse_capture: job.inverse_capture } : {},
735
876
  lease_expires_at: job.lease.expires_at,
@@ -748,6 +889,7 @@ var normalizedWritebackJobV3InputSchema = z.object({
748
889
  allowed_columns: z.array(safeIdentifier).max(256),
749
890
  patch: boundedScalarRecord,
750
891
  conflict_guard: z.object({ kind: z.literal("none") }).default({ kind: "none" }),
892
+ freshness: freshnessAuthorityV1Schema.optional(),
751
893
  version_advance: versionAdvanceSchema.optional(),
752
894
  frozen_set: frozenSetSchema,
753
895
  idempotency_key: z.string().min(1),
@@ -755,6 +897,14 @@ var normalizedWritebackJobV3InputSchema = z.object({
755
897
  attempt_count: z.number().int().positive(),
756
898
  inverse_capture: inverseDescriptorV1Schema.optional()
757
899
  }).superRefine((job, ctx) => {
900
+ validateFreshnessBinding(
901
+ job.freshness,
902
+ job.source_id,
903
+ job.engine,
904
+ job.operation === "batch_insert" ? "not_applicable" : "frozen_set",
905
+ job.operation === "batch_insert" ? 0 : job.frozen_set.row_count,
906
+ ctx
907
+ );
758
908
  if (job.target.principal_scope && job.allowed_columns.includes(job.target.principal_scope.column)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "principal scope column must not be patch-allowlisted", path: ["allowed_columns"] });
759
909
  if (job.operation === "set_delete" && (job.allowed_columns.length || Object.keys(job.patch).length || job.version_advance)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "set DELETE cannot carry patch authority", path: ["patch"] });
760
910
  if (job.operation === "set_update" && (!Object.keys(job.patch).length || !job.version_advance)) ctx.addIssue({ code: z.ZodIssueCode.custom, message: "set UPDATE requires patch and version advance", path: ["patch"] });
@@ -776,9 +926,19 @@ var writebackJobV3Schema = z.object({
776
926
  patch: boundedScalarRecord,
777
927
  version_advance: versionAdvanceSchema.optional(),
778
928
  frozen_set: frozenSetSchema,
929
+ freshness: freshnessAuthorityV1Schema.optional(),
779
930
  idempotency_key: z.string().min(1),
780
931
  inverse_capture: inverseDescriptorV1Schema.optional(),
781
932
  lease: z.object({ lease_id: z.string().min(1), attempt: z.number().int().positive(), expires_at: z.string().min(1) })
933
+ }).superRefine((job, ctx) => {
934
+ validateFreshnessBinding(
935
+ job.freshness,
936
+ job.runner_scope.source_id,
937
+ job.engine,
938
+ job.operation === "batch_insert" ? "not_applicable" : "frozen_set",
939
+ job.operation === "batch_insert" ? 0 : job.frozen_set.row_count,
940
+ ctx
941
+ );
782
942
  }).transform((job) => ({
783
943
  protocol_version: protocolVersions.normalizedWritebackJobV3,
784
944
  job_id: job.writeback_job_id,
@@ -793,6 +953,7 @@ var writebackJobV3Schema = z.object({
793
953
  conflict_guard: { kind: "none" },
794
954
  ...job.version_advance ? { version_advance: job.version_advance } : {},
795
955
  frozen_set: job.frozen_set,
956
+ ...job.freshness ? { freshness: job.freshness } : {},
796
957
  idempotency_key: job.idempotency_key,
797
958
  ...job.inverse_capture ? { inverse_capture: job.inverse_capture } : {},
798
959
  lease_expires_at: job.lease.expires_at,
@@ -1146,6 +1307,38 @@ var runnerActivityV1Schema = z.object({
1146
1307
  detail: z.record(z.unknown()).optional(),
1147
1308
  occurred_at: z.string().min(1).max(80).optional()
1148
1309
  }).strict();
1310
+ function sourceEngine(kind) {
1311
+ if (kind === "external_postgres") return "postgres";
1312
+ if (kind === "external_mysql") return "mysql";
1313
+ return void 0;
1314
+ }
1315
+ function validateFreshnessBinding(freshness, sourceId, engine, expectedTargetMode, expectedMemberCount, ctx) {
1316
+ if (!freshness) return;
1317
+ if (!engine) {
1318
+ ctx.addIssue({
1319
+ code: z.ZodIssueCode.custom,
1320
+ message: "freshness authority requires an external PostgreSQL or MySQL source",
1321
+ path: ["freshness"]
1322
+ });
1323
+ return;
1324
+ }
1325
+ if (freshness.target.mode !== expectedTargetMode || freshness.target.member_count !== expectedMemberCount) {
1326
+ ctx.addIssue({
1327
+ code: z.ZodIssueCode.custom,
1328
+ message: `freshness target authority must be ${expectedTargetMode} with ${expectedMemberCount} reviewed member(s)`,
1329
+ path: ["freshness", "target"]
1330
+ });
1331
+ }
1332
+ for (const [index, dependency] of freshness.dependencies.entries()) {
1333
+ if (dependency.source_id !== sourceId || dependency.engine !== engine) {
1334
+ ctx.addIssue({
1335
+ code: z.ZodIssueCode.custom,
1336
+ message: "freshness dependencies must use the proposal/writeback source and engine",
1337
+ path: ["freshness", "dependencies", index, "source_id"]
1338
+ });
1339
+ }
1340
+ }
1341
+ }
1149
1342
  function validateAllowedPatchColumns(allowedColumns, patchColumns, primaryKeyColumn, tenantGuardColumn, ctx) {
1150
1343
  const allow = new Set(allowedColumns);
1151
1344
  for (const column of patchColumns) {
@@ -1206,6 +1399,12 @@ function normalizeConflictGuard(guard) {
1206
1399
  function parseChangeSet(input) {
1207
1400
  return z.union([changeSetV1Schema, changeSetV2Schema, changeSetV3Schema, compensationChangeSetV1Schema]).parse(input);
1208
1401
  }
1402
+ function parseFreshnessAuthority(input) {
1403
+ return freshnessAuthorityV1Schema.parse(input);
1404
+ }
1405
+ function parseFreshnessProof(input) {
1406
+ return freshnessProofV1Schema.parse(input);
1407
+ }
1209
1408
  function parseWritebackJob(input) {
1210
1409
  return writebackJobSchema.parse(input);
1211
1410
  }
@@ -1395,6 +1594,7 @@ var ProposalStore = class {
1395
1594
  decision_hash TEXT,
1396
1595
  signature TEXT,
1397
1596
  integrity_hash TEXT,
1597
+ freshness_proof_digest TEXT,
1398
1598
  created_at TEXT NOT NULL,
1399
1599
  FOREIGN KEY (proposal_id) REFERENCES proposals(proposal_id)
1400
1600
  );
@@ -1668,6 +1868,7 @@ var ProposalStore = class {
1668
1868
  this.ensureColumn("approvals", "decision_hash", "TEXT");
1669
1869
  this.ensureColumn("approvals", "signature", "TEXT");
1670
1870
  this.ensureColumn("approvals", "integrity_hash", "TEXT");
1871
+ this.ensureColumn("approvals", "freshness_proof_digest", "TEXT");
1671
1872
  }
1672
1873
  ensureSearchIndexes() {
1673
1874
  this.db.exec(`
@@ -1958,6 +2159,75 @@ var ProposalStore = class {
1958
2159
  const row = this.db.prepare("SELECT proposal_id FROM query_audit WHERE evidence_bundle_id = ? AND proposal_id IS NOT NULL ORDER BY created_at DESC LIMIT 1").get(evidenceBundleId);
1959
2160
  return isRecord(row) && row.proposal_id != null ? String(row.proposal_id) : void 0;
1960
2161
  }
2162
+ recordFreshnessProof(input) {
2163
+ const proof = parseFreshnessProof(input);
2164
+ const proposal = this.requireProposal(proof.proposal_id);
2165
+ assertProposalIdentity(proposal, proof.proposal_hash, proof.proposal_version);
2166
+ const authority = proposalFreshnessAuthority(proposal);
2167
+ if (!authority) {
2168
+ throw new ProposalStoreError(
2169
+ "FRESHNESS_NOT_REQUIRED",
2170
+ `proposal ${proof.proposal_id} has no reviewed freshness authority`
2171
+ );
2172
+ }
2173
+ if (proof.dependency_set_digest !== authority.dependency_set_digest) {
2174
+ throw new ProposalStoreError(
2175
+ "FRESHNESS_PROOF_AUTHORITY_MISMATCH",
2176
+ `freshness proof does not match proposal ${proof.proposal_id}`
2177
+ );
2178
+ }
2179
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2180
+ this.transaction(() => {
2181
+ this.appendEvent(proof.proposal_id, "proposal_freshness_checked", "runner", { proof });
2182
+ if (proof.result === "stale") {
2183
+ const current = this.requireProposal(proof.proposal_id);
2184
+ if (current.state === "pending_review" || current.state === "approved" || current.state === "pending_worker") {
2185
+ this.db.prepare("UPDATE proposals SET state = ?, updated_at = ? WHERE proposal_id = ?").run("conflict", now, proof.proposal_id);
2186
+ this.appendEvent(proof.proposal_id, "proposal_conflict", "runner", {
2187
+ reason: "freshness_stale",
2188
+ safe_code: proof.safe_code,
2189
+ proof_digest: proof.proof_digest
2190
+ });
2191
+ }
2192
+ }
2193
+ });
2194
+ return proof;
2195
+ }
2196
+ latestFreshnessProof(proposalId) {
2197
+ this.requireProposal(proposalId);
2198
+ const row = this.db.prepare(`
2199
+ SELECT payload_json
2200
+ FROM proposal_events
2201
+ WHERE proposal_id = ? AND kind = 'proposal_freshness_checked'
2202
+ ORDER BY event_id DESC
2203
+ LIMIT 1
2204
+ `).get(proposalId);
2205
+ if (!isRecord(row)) return void 0;
2206
+ try {
2207
+ const payload = JSON.parse(String(row.payload_json));
2208
+ return parseFreshnessProof(payload.proof);
2209
+ } catch {
2210
+ throw new ProposalStoreError(
2211
+ "FRESHNESS_PROOF_TAMPERED",
2212
+ `stored freshness proof for proposal ${proposalId} failed integrity validation`
2213
+ );
2214
+ }
2215
+ }
2216
+ recordFreshnessApprovalBlocked(proposalId, input) {
2217
+ this.requireProposal(proposalId);
2218
+ const proof = this.latestFreshnessProof(proposalId);
2219
+ if (!proof || proof.proof_digest !== input.proof_digest || proof.result === "fresh") {
2220
+ throw new ProposalStoreError(
2221
+ "FRESHNESS_BLOCK_RECORD_INVALID",
2222
+ `freshness block for proposal ${proposalId} does not match its latest non-fresh proof`
2223
+ );
2224
+ }
2225
+ this.appendEvent(proposalId, "proposal_approval_blocked_freshness", input.actor, {
2226
+ proof_digest: proof.proof_digest,
2227
+ safe_code: input.safe_code,
2228
+ result: proof.result
2229
+ });
2230
+ }
1961
2231
  approveProposal(proposalId, options) {
1962
2232
  const proposal = this.requireProposal(proposalId);
1963
2233
  assertWritebackAllowed(proposal, "approved");
@@ -1978,11 +2248,12 @@ var ProposalStore = class {
1978
2248
  if (isRecord(existing)) {
1979
2249
  throw new ProposalStoreError("APPROVER_ALREADY_COUNTED", `operator ${options.approver} already recorded a decision for proposal ${proposalId}`);
1980
2250
  }
2251
+ this.assertApprovalFreshness(current, options.freshness_proof_digest, now);
1981
2252
  this.db.prepare(`
1982
2253
  INSERT INTO approvals (
1983
2254
  proposal_id, proposal_version, proposal_hash, approver, status, reason,
1984
- identity_json, decision_hash, signature, integrity_hash, created_at
1985
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2255
+ identity_json, decision_hash, signature, integrity_hash, freshness_proof_digest, created_at
2256
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1986
2257
  `).run(
1987
2258
  proposalId,
1988
2259
  options.proposal_version,
@@ -1994,6 +2265,7 @@ var ProposalStore = class {
1994
2265
  options.identity?.decision_hash ?? null,
1995
2266
  options.identity?.signature ?? null,
1996
2267
  options.identity?.integrity_hash ?? null,
2268
+ options.freshness_proof_digest ?? null,
1997
2269
  now
1998
2270
  );
1999
2271
  const progress = this.approvalProgress(proposalId);
@@ -2006,6 +2278,7 @@ var ProposalStore = class {
2006
2278
  proposal_version: options.proposal_version,
2007
2279
  reason: options.reason ?? null,
2008
2280
  identity: publicIdentitySummary(options.identity),
2281
+ freshness_proof_digest: options.freshness_proof_digest ?? null,
2009
2282
  approvals: progress.approved,
2010
2283
  required_approvals: progress.required,
2011
2284
  remaining_approvals: progress.remaining
@@ -2114,17 +2387,31 @@ var ProposalStore = class {
2114
2387
  });
2115
2388
  return;
2116
2389
  }
2390
+ this.assertApprovalFreshness(proposal, options.freshness_proof_digest, now);
2117
2391
  this.db.prepare("UPDATE proposals SET state = ?, updated_at = ? WHERE proposal_id = ?").run("approved", now, proposalId);
2118
2392
  this.db.prepare(`
2119
- INSERT INTO approvals (proposal_id, proposal_version, proposal_hash, approver, status, reason, created_at)
2120
- VALUES (?, ?, ?, ?, ?, ?, ?)
2121
- `).run(proposalId, options.proposal_version, options.proposal_hash, actor, "approved", options.reason, now);
2393
+ INSERT INTO approvals (
2394
+ proposal_id, proposal_version, proposal_hash, approver, status, reason,
2395
+ freshness_proof_digest, created_at
2396
+ )
2397
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2398
+ `).run(
2399
+ proposalId,
2400
+ options.proposal_version,
2401
+ options.proposal_hash,
2402
+ actor,
2403
+ "approved",
2404
+ options.reason,
2405
+ options.freshness_proof_digest ?? null,
2406
+ now
2407
+ );
2122
2408
  this.appendEvent(proposalId, "proposal_approved", actor, {
2123
2409
  proposal_hash: options.proposal_hash,
2124
2410
  proposal_version: options.proposal_version,
2125
2411
  reason: options.reason,
2126
2412
  policy: options.policy,
2127
- aggregate_limits: options.limits ?? []
2413
+ aggregate_limits: options.limits ?? [],
2414
+ freshness_proof_digest: options.freshness_proof_digest ?? null
2128
2415
  });
2129
2416
  });
2130
2417
  return {
@@ -2472,7 +2759,8 @@ var ProposalStore = class {
2472
2759
  writeback_job_id: receipt.writeback_job_id,
2473
2760
  rows_affected: receipt.rows_affected,
2474
2761
  source_database_mutated: receipt.source_database_mutated,
2475
- receipt_hash: receipt.receipt_hash
2762
+ receipt_hash: receipt.receipt_hash,
2763
+ safe_error_code: "safe_error_code" in receipt ? receipt.safe_error_code ?? null : null
2476
2764
  });
2477
2765
  }
2478
2766
  recordHandlerWritebackJob(input) {
@@ -2556,6 +2844,7 @@ var ProposalStore = class {
2556
2844
  tenant_guard: changeSet.guards.tenant,
2557
2845
  ...changeSet.guards.principal_scope ? { principal_scope: changeSet.guards.principal_scope } : {},
2558
2846
  allowed_columns: changeSet.guards.allowed_columns,
2847
+ ..."freshness" in changeSet && changeSet.freshness ? { freshness: parseFreshnessAuthority(changeSet.freshness) } : {},
2559
2848
  idempotency_key: `${proposal.proposal_id}:${proposal.object_id}`,
2560
2849
  lease
2561
2850
  };
@@ -3009,7 +3298,21 @@ var ProposalStore = class {
3009
3298
  const key = `${tenantId}\0${capability}`;
3010
3299
  let row = rows.get(key);
3011
3300
  if (!row) {
3012
- row = { tenant_id: tenantId, capability, worker_retries: 0, dead_letters: 0, auto_approval_limit_trips: 0 };
3301
+ row = {
3302
+ tenant_id: tenantId,
3303
+ capability,
3304
+ worker_retries: 0,
3305
+ dead_letters: 0,
3306
+ auto_approval_limit_trips: 0,
3307
+ freshness_checks: 0,
3308
+ freshness_fresh: 0,
3309
+ freshness_stale_target: 0,
3310
+ freshness_stale_supporting: 0,
3311
+ freshness_unavailable: 0,
3312
+ freshness_unsupported: 0,
3313
+ freshness_approval_blocked: 0,
3314
+ freshness_apply_blocked: 0
3315
+ };
3013
3316
  rows.set(key, row);
3014
3317
  }
3015
3318
  return row;
@@ -3017,7 +3320,14 @@ var ProposalStore = class {
3017
3320
  const events = this.db.prepare(`
3018
3321
  SELECT p.tenant_id, p.action, e.kind, e.payload_json
3019
3322
  FROM proposal_events e JOIN proposals p ON p.proposal_id = e.proposal_id
3020
- WHERE e.kind IN ('writeback_retry_scheduled', 'writeback_dead_lettered', 'policy_auto_approval_deferred')
3323
+ WHERE e.kind IN (
3324
+ 'writeback_retry_scheduled',
3325
+ 'writeback_dead_lettered',
3326
+ 'policy_auto_approval_deferred',
3327
+ 'proposal_freshness_checked',
3328
+ 'proposal_approval_blocked_freshness',
3329
+ 'writeback_conflict'
3330
+ )
3021
3331
  AND (? IS NULL OR p.tenant_id = ?)
3022
3332
  AND (? IS NULL OR p.action = ?)
3023
3333
  `).all(filters.tenant ?? null, filters.tenant ?? null, filters.capability ?? null, filters.capability ?? null);
@@ -3026,13 +3336,29 @@ var ProposalStore = class {
3026
3336
  const row = ensure(String(raw.tenant_id), String(raw.action));
3027
3337
  if (raw.kind === "writeback_retry_scheduled") row.worker_retries += 1;
3028
3338
  if (raw.kind === "writeback_dead_lettered") row.dead_letters += 1;
3029
- if (raw.kind === "policy_auto_approval_deferred") {
3030
- try {
3031
- const payload = JSON.parse(String(raw.payload_json));
3032
- if (Array.isArray(payload.tripped_limits) && payload.tripped_limits.length > 0) row.auto_approval_limit_trips += 1;
3033
- } catch {
3339
+ let payload = {};
3340
+ try {
3341
+ const parsed = JSON.parse(String(raw.payload_json));
3342
+ if (isRecord(parsed)) payload = parsed;
3343
+ } catch {
3344
+ }
3345
+ if (raw.kind === "proposal_freshness_checked") {
3346
+ row.freshness_checks += 1;
3347
+ const proof = isRecord(payload.proof) ? payload.proof : {};
3348
+ if (proof.result === "fresh") row.freshness_fresh += 1;
3349
+ if (proof.result === "unavailable") row.freshness_unavailable += 1;
3350
+ if (proof.result === "unsupported") row.freshness_unsupported += 1;
3351
+ if (proof.result === "stale") {
3352
+ const checks = Array.isArray(proof.checks) ? proof.checks.filter(isRecord) : [];
3353
+ if (checks.some((check) => check.kind === "target" && check.status === "stale")) row.freshness_stale_target += 1;
3354
+ if (checks.some((check) => check.kind === "supporting" && check.status === "stale")) row.freshness_stale_supporting += 1;
3034
3355
  }
3035
3356
  }
3357
+ if (raw.kind === "proposal_approval_blocked_freshness") row.freshness_approval_blocked += 1;
3358
+ if (raw.kind === "writeback_conflict" && /^FRESHNESS_/.test(String(payload.safe_error_code ?? ""))) row.freshness_apply_blocked += 1;
3359
+ if (raw.kind === "policy_auto_approval_deferred") {
3360
+ if (Array.isArray(payload.tripped_limits) && payload.tripped_limits.length > 0) row.auto_approval_limit_trips += 1;
3361
+ }
3036
3362
  }
3037
3363
  return [...rows.values()].sort((left, right) => left.tenant_id.localeCompare(right.tenant_id) || left.capability.localeCompare(right.capability));
3038
3364
  }
@@ -3880,6 +4206,61 @@ var ProposalStore = class {
3880
4206
  }
3881
4207
  return proposal;
3882
4208
  }
4209
+ assertApprovalFreshness(proposal, proofDigest, now) {
4210
+ const authority = proposalFreshnessAuthority(proposal);
4211
+ if (!authority) {
4212
+ if (proofDigest !== void 0) {
4213
+ throw new ProposalStoreError(
4214
+ "FRESHNESS_PROOF_UNEXPECTED",
4215
+ `proposal ${proposal.proposal_id} does not require a freshness proof`
4216
+ );
4217
+ }
4218
+ return;
4219
+ }
4220
+ if (!proofDigest) {
4221
+ throw new ProposalStoreError(
4222
+ "FRESHNESS_PROOF_REQUIRED",
4223
+ `proposal ${proposal.proposal_id} requires a fresh live proof before approval`
4224
+ );
4225
+ }
4226
+ const proof = this.latestFreshnessProof(proposal.proposal_id);
4227
+ if (!proof || proof.proof_digest !== proofDigest) {
4228
+ throw new ProposalStoreError(
4229
+ "FRESHNESS_PROOF_MISSING",
4230
+ `the latest freshness proof for proposal ${proposal.proposal_id} was not supplied`
4231
+ );
4232
+ }
4233
+ if (proof.proposal_hash !== proposal.proposal_hash || proof.proposal_version !== proposal.proposal_version || proof.dependency_set_digest !== authority.dependency_set_digest) {
4234
+ throw new ProposalStoreError(
4235
+ "FRESHNESS_PROOF_AUTHORITY_MISMATCH",
4236
+ `freshness proof does not match proposal ${proposal.proposal_id}`
4237
+ );
4238
+ }
4239
+ if (proof.result !== "fresh") {
4240
+ throw new ProposalStoreError(
4241
+ proof.result === "stale" ? "FRESHNESS_STALE" : "FRESHNESS_NOT_VERIFIED",
4242
+ `proposal ${proposal.proposal_id} freshness result is ${proof.result}`
4243
+ );
4244
+ }
4245
+ if (Date.parse(proof.valid_until) < Date.parse(now)) {
4246
+ throw new ProposalStoreError(
4247
+ "FRESHNESS_PROOF_EXPIRED",
4248
+ `freshness proof for proposal ${proposal.proposal_id} has expired`
4249
+ );
4250
+ }
4251
+ const used = this.db.prepare(`
4252
+ SELECT approval_id
4253
+ FROM approvals
4254
+ WHERE proposal_id = ? AND freshness_proof_digest = ?
4255
+ LIMIT 1
4256
+ `).get(proposal.proposal_id, proofDigest);
4257
+ if (isRecord(used)) {
4258
+ throw new ProposalStoreError(
4259
+ "FRESHNESS_PROOF_ALREADY_USED",
4260
+ `freshness proof for proposal ${proposal.proposal_id} already authorized a reviewer decision`
4261
+ );
4262
+ }
4263
+ }
3883
4264
  setState(proposalId, state, actor, payload) {
3884
4265
  const now = (/* @__PURE__ */ new Date()).toISOString();
3885
4266
  this.transaction(() => {
@@ -4040,6 +4421,17 @@ function stateFromChangeSet(changeSet) {
4040
4421
  if (changeSet.approval.status === "canceled") return "canceled";
4041
4422
  return "pending_review";
4042
4423
  }
4424
+ function proposalFreshnessAuthority(proposal) {
4425
+ if (!("freshness" in proposal.change_set) || proposal.change_set.freshness === void 0) return void 0;
4426
+ try {
4427
+ return parseFreshnessAuthority(proposal.change_set.freshness);
4428
+ } catch {
4429
+ throw new ProposalStoreError(
4430
+ "FRESHNESS_AUTHORITY_TAMPERED",
4431
+ `stored freshness authority for proposal ${proposal.proposal_id} failed integrity validation`
4432
+ );
4433
+ }
4434
+ }
4043
4435
  function requiredApprovalCount(proposal) {
4044
4436
  const configured = proposal.change_set.approval.required_approvals;
4045
4437
  return typeof configured === "number" && Number.isSafeInteger(configured) && configured >= 1 ? configured : 1;
@@ -4566,6 +4958,7 @@ function rowToApproval(row) {
4566
4958
  decision_hash: row.decision_hash == null ? void 0 : String(row.decision_hash),
4567
4959
  signature: row.signature == null ? void 0 : String(row.signature),
4568
4960
  integrity_hash: row.integrity_hash == null ? void 0 : String(row.integrity_hash),
4961
+ freshness_proof_digest: row.freshness_proof_digest == null ? void 0 : String(row.freshness_proof_digest),
4569
4962
  created_at: String(row.created_at)
4570
4963
  };
4571
4964
  }
@@ -4942,7 +5335,7 @@ var sharedLedgerRestoreSpecs = {
4942
5335
  "updated_at"
4943
5336
  ], ["proposal_id", "proposal_version", "proposal_hash", "action", "state", "tenant_id", "business_object", "object_id", "source_kind", "source_id", "source_schema", "source_table", "source_database_mutated", "change_set_json", "created_at", "updated_at"]),
4944
5337
  proposal_events: restoreSpec("event_id", ["event_id", "proposal_id", "kind", "actor", "payload_json", "created_at"], ["event_id", "proposal_id", "kind", "actor", "payload_json", "created_at"]),
4945
- approvals: restoreSpec("approval_id", ["approval_id", "proposal_id", "proposal_version", "proposal_hash", "approver", "status", "reason", "identity_json", "decision_hash", "signature", "integrity_hash", "created_at"], ["approval_id", "proposal_id", "proposal_version", "proposal_hash", "approver", "status", "created_at"]),
5338
+ approvals: restoreSpec("approval_id", ["approval_id", "proposal_id", "proposal_version", "proposal_hash", "approver", "status", "reason", "identity_json", "decision_hash", "signature", "integrity_hash", "freshness_proof_digest", "created_at"], ["approval_id", "proposal_id", "proposal_version", "proposal_hash", "approver", "status", "created_at"]),
4946
5339
  writeback_jobs: restoreSpec("writeback_job_id", ["writeback_job_id", "proposal_id", "proposal_hash", "status", "job_json", "created_at", "updated_at"], ["writeback_job_id", "proposal_id", "proposal_hash", "status", "job_json", "created_at", "updated_at"]),
4947
5340
  writeback_intents: restoreSpec("intent_id", ["intent_id", "idempotency_key", "writeback_job_id", "proposal_id", "proposal_hash", "runner_id", "operation", "status", "intent_json", "result_json", "reconciliation_reason", "created_at", "updated_at"], ["intent_id", "idempotency_key", "writeback_job_id", "proposal_id", "proposal_hash", "runner_id", "operation", "status", "intent_json", "created_at", "updated_at"]),
4948
5341
  idempotency_receipts: restoreSpec("idempotency_key", ["idempotency_key", "writeback_job_id", "proposal_id", "receipt_status", "receipt_json", "created_at"], ["idempotency_key", "writeback_job_id", "proposal_id", "receipt_status", "receipt_json", "created_at"]),