@emilia-protocol/gate 0.10.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +272 -0
- package/action-control-manifest.js +3 -268
- package/action-escrow-custodian.js +4 -0
- package/action-escrow-evidence.js +4 -0
- package/action-escrow-package.js +4 -0
- package/action-escrow-postgres.js +4 -0
- package/action-escrow-state.js +4 -0
- package/action-escrow-verifiers.js +4 -0
- package/action-escrow.js +4 -0
- package/action-packs.js +3 -140
- package/adapters/_kit.js +3 -104
- package/adapters/aws.js +3 -108
- package/adapters/cloudflare.js +3 -54
- package/adapters/gcp.js +3 -70
- package/adapters/github.js +3 -109
- package/adapters/jira.js +3 -55
- package/adapters/k8s.js +3 -70
- package/adapters/linear.js +3 -55
- package/adapters/salesforce.js +3 -55
- package/adapters/stripe.js +3 -81
- package/adapters/supabase.js +3 -121
- package/adapters/terraform.js +3 -59
- package/adapters/vercel.js +3 -84
- package/aec-execution.js +3 -312
- package/breakglass.js +3 -582
- package/capability-receipt.js +4 -0
- package/cf1-conformance.js +3 -106
- package/challenge-store.js +3 -69
- package/control-plane.js +3 -340
- package/coverage.js +3 -721
- package/demo.mjs +1 -1
- package/deployment-attestation.js +3 -247
- package/dist/action-control-manifest.d.ts +82 -0
- package/dist/action-control-manifest.d.ts.map +1 -0
- package/dist/action-control-manifest.js +422 -0
- package/dist/action-control-manifest.js.map +1 -0
- package/dist/action-escrow-custodian.d.ts +74 -0
- package/dist/action-escrow-custodian.d.ts.map +1 -0
- package/dist/action-escrow-custodian.js +363 -0
- package/dist/action-escrow-custodian.js.map +1 -0
- package/dist/action-escrow-evidence.d.ts +130 -0
- package/dist/action-escrow-evidence.d.ts.map +1 -0
- package/dist/action-escrow-evidence.js +1012 -0
- package/dist/action-escrow-evidence.js.map +1 -0
- package/dist/action-escrow-package.d.ts +10 -0
- package/dist/action-escrow-package.d.ts.map +1 -0
- package/dist/action-escrow-package.js +1583 -0
- package/dist/action-escrow-package.js.map +1 -0
- package/dist/action-escrow-postgres.d.ts +62 -0
- package/dist/action-escrow-postgres.d.ts.map +1 -0
- package/dist/action-escrow-postgres.js +339 -0
- package/dist/action-escrow-postgres.js.map +1 -0
- package/dist/action-escrow-state.d.ts +141 -0
- package/dist/action-escrow-state.d.ts.map +1 -0
- package/dist/action-escrow-state.js +413 -0
- package/dist/action-escrow-state.js.map +1 -0
- package/dist/action-escrow-verifiers.d.ts +65 -0
- package/dist/action-escrow-verifiers.d.ts.map +1 -0
- package/dist/action-escrow-verifiers.js +421 -0
- package/dist/action-escrow-verifiers.js.map +1 -0
- package/dist/action-escrow.d.ts +112 -0
- package/dist/action-escrow.d.ts.map +1 -0
- package/dist/action-escrow.js +2661 -0
- package/dist/action-escrow.js.map +1 -0
- package/dist/action-packs.d.ts +54 -0
- package/dist/action-packs.d.ts.map +1 -0
- package/dist/action-packs.js +143 -0
- package/dist/action-packs.js.map +1 -0
- package/dist/adapters/_kit.d.ts +50 -0
- package/dist/adapters/_kit.d.ts.map +1 -0
- package/dist/adapters/_kit.js +98 -0
- package/dist/adapters/_kit.js.map +1 -0
- package/dist/adapters/aws.d.ts +158 -0
- package/dist/adapters/aws.d.ts.map +1 -0
- package/dist/adapters/aws.js +136 -0
- package/dist/adapters/aws.js.map +1 -0
- package/dist/adapters/cloudflare.d.ts +116 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +75 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/gcp.d.ts +139 -0
- package/dist/adapters/gcp.d.ts.map +1 -0
- package/dist/adapters/gcp.js +69 -0
- package/dist/adapters/gcp.js.map +1 -0
- package/dist/adapters/github.d.ts +123 -0
- package/dist/adapters/github.d.ts.map +1 -0
- package/dist/adapters/github.js +105 -0
- package/dist/adapters/github.js.map +1 -0
- package/dist/adapters/jira.d.ts +109 -0
- package/dist/adapters/jira.d.ts.map +1 -0
- package/dist/adapters/jira.js +54 -0
- package/dist/adapters/jira.js.map +1 -0
- package/dist/adapters/k8s.d.ts +139 -0
- package/dist/adapters/k8s.d.ts.map +1 -0
- package/dist/adapters/k8s.js +69 -0
- package/dist/adapters/k8s.js.map +1 -0
- package/dist/adapters/linear.d.ts +109 -0
- package/dist/adapters/linear.d.ts.map +1 -0
- package/dist/adapters/linear.js +54 -0
- package/dist/adapters/linear.js.map +1 -0
- package/dist/adapters/salesforce.d.ts +109 -0
- package/dist/adapters/salesforce.d.ts.map +1 -0
- package/dist/adapters/salesforce.js +54 -0
- package/dist/adapters/salesforce.js.map +1 -0
- package/dist/adapters/stripe.d.ts +116 -0
- package/dist/adapters/stripe.d.ts.map +1 -0
- package/dist/adapters/stripe.js +77 -0
- package/dist/adapters/stripe.js.map +1 -0
- package/dist/adapters/supabase.d.ts +127 -0
- package/dist/adapters/supabase.d.ts.map +1 -0
- package/dist/adapters/supabase.js +112 -0
- package/dist/adapters/supabase.js.map +1 -0
- package/dist/adapters/terraform.d.ts +109 -0
- package/dist/adapters/terraform.d.ts.map +1 -0
- package/dist/adapters/terraform.js +58 -0
- package/dist/adapters/terraform.js.map +1 -0
- package/dist/adapters/vercel.d.ts +114 -0
- package/dist/adapters/vercel.d.ts.map +1 -0
- package/dist/adapters/vercel.js +81 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/aec-execution.d.ts +107 -0
- package/dist/aec-execution.d.ts.map +1 -0
- package/dist/aec-execution.js +334 -0
- package/dist/aec-execution.js.map +1 -0
- package/dist/breakglass.d.ts +205 -0
- package/dist/breakglass.d.ts.map +1 -0
- package/dist/breakglass.js +627 -0
- package/dist/breakglass.js.map +1 -0
- package/dist/capability-receipt.d.ts +601 -0
- package/dist/capability-receipt.d.ts.map +1 -0
- package/dist/capability-receipt.js +1177 -0
- package/dist/capability-receipt.js.map +1 -0
- package/dist/cf1-conformance.d.ts +83 -0
- package/dist/cf1-conformance.d.ts.map +1 -0
- package/dist/cf1-conformance.js +94 -0
- package/dist/cf1-conformance.js.map +1 -0
- package/dist/challenge-store.d.ts +20 -0
- package/dist/challenge-store.d.ts.map +1 -0
- package/dist/challenge-store.js +62 -0
- package/dist/challenge-store.js.map +1 -0
- package/dist/control-plane.d.ts +136 -0
- package/dist/control-plane.d.ts.map +1 -0
- package/dist/control-plane.js +349 -0
- package/dist/control-plane.js.map +1 -0
- package/dist/coverage.d.ts +156 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +768 -0
- package/dist/coverage.js.map +1 -0
- package/dist/deployment-attestation.d.ts +93 -0
- package/dist/deployment-attestation.d.ts.map +1 -0
- package/dist/deployment-attestation.js +262 -0
- package/dist/deployment-attestation.js.map +1 -0
- package/dist/eg1-conformance.d.ts +286 -0
- package/dist/eg1-conformance.d.ts.map +1 -0
- package/dist/eg1-conformance.js +379 -0
- package/dist/eg1-conformance.js.map +1 -0
- package/dist/enterprise.d.ts +88 -0
- package/dist/enterprise.d.ts.map +1 -0
- package/dist/enterprise.js +194 -0
- package/dist/enterprise.js.map +1 -0
- package/dist/evidence-postgres.d.ts +123 -0
- package/dist/evidence-postgres.d.ts.map +1 -0
- package/dist/evidence-postgres.js +343 -0
- package/dist/evidence-postgres.js.map +1 -0
- package/dist/evidence.d.ts +113 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +369 -0
- package/dist/evidence.js.map +1 -0
- package/dist/execution-binding.d.ts +49 -0
- package/dist/execution-binding.d.ts.map +1 -0
- package/dist/execution-binding.js +243 -0
- package/dist/execution-binding.js.map +1 -0
- package/dist/formal-runtime-map.d.ts +42 -0
- package/dist/formal-runtime-map.d.ts.map +1 -0
- package/dist/formal-runtime-map.js +23 -0
- package/dist/formal-runtime-map.js.map +1 -0
- package/dist/index.d.ts +1002 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1736 -0
- package/dist/index.js.map +1 -0
- package/dist/key-registry.d.ts +60 -0
- package/dist/key-registry.d.ts.map +1 -0
- package/dist/key-registry.js +144 -0
- package/dist/key-registry.js.map +1 -0
- package/dist/mcp.d.ts +53 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +112 -0
- package/dist/mcp.js.map +1 -0
- package/dist/metering.d.ts +80 -0
- package/dist/metering.d.ts.map +1 -0
- package/dist/metering.js +227 -0
- package/dist/metering.js.map +1 -0
- package/dist/metrics.d.ts +68 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +242 -0
- package/dist/metrics.js.map +1 -0
- package/dist/network-witness.d.ts +167 -0
- package/dist/network-witness.d.ts.map +1 -0
- package/dist/network-witness.js +520 -0
- package/dist/network-witness.js.map +1 -0
- package/dist/protocol-state.d.ts +141 -0
- package/dist/protocol-state.d.ts.map +1 -0
- package/dist/protocol-state.js +106 -0
- package/dist/protocol-state.js.map +1 -0
- package/dist/receipt-program.d.ts +59 -0
- package/dist/receipt-program.d.ts.map +1 -0
- package/dist/receipt-program.js +1053 -0
- package/dist/receipt-program.js.map +1 -0
- package/dist/reliance-kernel.d.ts +66 -0
- package/dist/reliance-kernel.d.ts.map +1 -0
- package/dist/reliance-kernel.js +103 -0
- package/dist/reliance-kernel.js.map +1 -0
- package/dist/reliance-packet.d.ts +67 -0
- package/dist/reliance-packet.d.ts.map +1 -0
- package/dist/reliance-packet.js +176 -0
- package/dist/reliance-packet.js.map +1 -0
- package/dist/remedy-program-postgres.d.ts +37 -0
- package/dist/remedy-program-postgres.d.ts.map +1 -0
- package/dist/remedy-program-postgres.js +291 -0
- package/dist/remedy-program-postgres.js.map +1 -0
- package/dist/remedy-program-receipt.d.ts +97 -0
- package/dist/remedy-program-receipt.d.ts.map +1 -0
- package/dist/remedy-program-receipt.js +817 -0
- package/dist/remedy-program-receipt.js.map +1 -0
- package/dist/remedy-program.d.ts +76 -0
- package/dist/remedy-program.d.ts.map +1 -0
- package/dist/remedy-program.js +1346 -0
- package/dist/remedy-program.js.map +1 -0
- package/dist/reports/art14.d.ts +101 -0
- package/dist/reports/art14.d.ts.map +1 -0
- package/dist/reports/art14.js +340 -0
- package/dist/reports/art14.js.map +1 -0
- package/dist/reports/assurance-package.d.ts +197 -0
- package/dist/reports/assurance-package.d.ts.map +1 -0
- package/dist/reports/assurance-package.js +313 -0
- package/dist/reports/assurance-package.js.map +1 -0
- package/dist/reports/auditor-workpaper.d.ts +194 -0
- package/dist/reports/auditor-workpaper.d.ts.map +1 -0
- package/dist/reports/auditor-workpaper.js +545 -0
- package/dist/reports/auditor-workpaper.js.map +1 -0
- package/dist/reports/external-verification.d.ts +76 -0
- package/dist/reports/external-verification.d.ts.map +1 -0
- package/dist/reports/external-verification.js +209 -0
- package/dist/reports/external-verification.js.map +1 -0
- package/dist/reports/reperform.d.ts +122 -0
- package/dist/reports/reperform.d.ts.map +1 -0
- package/dist/reports/reperform.js +432 -0
- package/dist/reports/reperform.js.map +1 -0
- package/dist/reports/underwriter.d.ts +127 -0
- package/dist/reports/underwriter.d.ts.map +1 -0
- package/dist/reports/underwriter.js +348 -0
- package/dist/reports/underwriter.js.map +1 -0
- package/dist/retention.d.ts +88 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +90 -0
- package/dist/retention.js.map +1 -0
- package/dist/roster.d.ts +113 -0
- package/dist/roster.d.ts.map +1 -0
- package/dist/roster.js +261 -0
- package/dist/roster.js.map +1 -0
- package/dist/runtime-monitor.d.ts +132 -0
- package/dist/runtime-monitor.d.ts.map +1 -0
- package/dist/runtime-monitor.js +197 -0
- package/dist/runtime-monitor.js.map +1 -0
- package/dist/settlement.d.ts +68 -0
- package/dist/settlement.d.ts.map +1 -0
- package/dist/settlement.js +306 -0
- package/dist/settlement.js.map +1 -0
- package/dist/siem.d.ts +261 -0
- package/dist/siem.d.ts.map +1 -0
- package/dist/siem.js +247 -0
- package/dist/siem.js.map +1 -0
- package/dist/store-postgres.d.ts +107 -0
- package/dist/store-postgres.d.ts.map +1 -0
- package/dist/store-postgres.js +152 -0
- package/dist/store-postgres.js.map +1 -0
- package/dist/store.d.ts +95 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +202 -0
- package/dist/store.js.map +1 -0
- package/dist/strict-json.d.ts +14 -0
- package/dist/strict-json.d.ts.map +1 -0
- package/dist/strict-json.js +132 -0
- package/dist/strict-json.js.map +1 -0
- package/dist/trust-program-adapters.d.ts +122 -0
- package/dist/trust-program-adapters.d.ts.map +1 -0
- package/dist/trust-program-adapters.js +802 -0
- package/dist/trust-program-adapters.js.map +1 -0
- package/dist/trust-program-postgres.d.ts +41 -0
- package/dist/trust-program-postgres.d.ts.map +1 -0
- package/dist/trust-program-postgres.js +346 -0
- package/dist/trust-program-postgres.js.map +1 -0
- package/dist/trust-program-revocation.d.ts +206 -0
- package/dist/trust-program-revocation.d.ts.map +1 -0
- package/dist/trust-program-revocation.js +600 -0
- package/dist/trust-program-revocation.js.map +1 -0
- package/dist/trust-program.d.ts +197 -0
- package/dist/trust-program.d.ts.map +1 -0
- package/dist/trust-program.js +1351 -0
- package/dist/trust-program.js.map +1 -0
- package/dist/witness-postgres.d.ts +43 -0
- package/dist/witness-postgres.d.ts.map +1 -0
- package/dist/witness-postgres.js +92 -0
- package/dist/witness-postgres.js.map +1 -0
- package/dist/zk-range-proof.d.ts +78 -0
- package/dist/zk-range-proof.d.ts.map +1 -0
- package/dist/zk-range-proof.js +228 -0
- package/dist/zk-range-proof.js.map +1 -0
- package/eg1-conformance.js +3 -404
- package/enterprise.js +3 -180
- package/ep-assure.mjs +1 -0
- package/evidence-postgres.js +3 -356
- package/evidence.js +4 -360
- package/execution-binding.js +3 -212
- package/formal-runtime-map.js +4 -0
- package/index.js +4 -1249
- package/key-registry.js +3 -134
- package/mcp.js +3 -109
- package/metering.js +3 -226
- package/metrics.js +3 -231
- package/network-witness.js +3 -499
- package/package.json +47 -4
- package/receipt-program.js +4 -0
- package/reliance-kernel.js +3 -112
- package/reliance-packet.js +3 -175
- package/remedy-program-postgres.js +4 -0
- package/remedy-program-receipt.js +4 -0
- package/remedy-program.js +4 -0
- package/reports/art14.js +0 -0
- package/reports/assurance-package.js +2 -312
- package/reports/auditor-workpaper.js +3 -537
- package/reports/external-verification.js +3 -222
- package/reports/reperform.js +3 -424
- package/reports/underwriter.js +3 -339
- package/retention.js +3 -84
- package/roster.js +3 -264
- package/runtime-monitor.js +4 -0
- package/settlement.js +3 -299
- package/siem.js +3 -235
- package/src/action-control-manifest.ts +424 -0
- package/src/action-escrow-custodian.ts +427 -0
- package/src/action-escrow-evidence.ts +1178 -0
- package/src/action-escrow-package.ts +1821 -0
- package/src/action-escrow-postgres.ts +366 -0
- package/src/action-escrow-state.ts +498 -0
- package/src/action-escrow-verifiers.ts +484 -0
- package/src/action-escrow.ts +3039 -0
- package/src/action-packs.ts +170 -0
- package/src/adapters/_kit.ts +117 -0
- package/src/adapters/aws.ts +142 -0
- package/src/adapters/cloudflare.ts +78 -0
- package/src/adapters/gcp.ts +71 -0
- package/src/adapters/github.ts +110 -0
- package/src/adapters/jira.ts +56 -0
- package/src/adapters/k8s.ts +71 -0
- package/src/adapters/linear.ts +56 -0
- package/src/adapters/salesforce.ts +56 -0
- package/src/adapters/stripe.ts +82 -0
- package/src/adapters/supabase.ts +122 -0
- package/src/adapters/terraform.ts +60 -0
- package/src/adapters/vercel.ts +85 -0
- package/src/aec-execution.ts +354 -0
- package/src/breakglass.ts +647 -0
- package/src/capability-receipt.ts +1258 -0
- package/src/cf1-conformance.ts +120 -0
- package/src/challenge-store.ts +70 -0
- package/src/control-plane.ts +374 -0
- package/src/coverage.ts +832 -0
- package/src/deployment-attestation.ts +259 -0
- package/src/eg1-conformance.ts +471 -0
- package/src/enterprise.ts +195 -0
- package/src/evidence-postgres.ts +374 -0
- package/src/evidence.ts +367 -0
- package/src/execution-binding.ts +236 -0
- package/src/formal-runtime-map.ts +24 -0
- package/src/index.ts +2122 -0
- package/src/key-registry.ts +150 -0
- package/src/mcp.ts +120 -0
- package/src/metering.ts +237 -0
- package/src/metrics.ts +232 -0
- package/src/network-witness.ts +585 -0
- package/src/protocol-state.ts +143 -0
- package/src/protocol-state.typecheck.ts +94 -0
- package/src/receipt-program.ts +1105 -0
- package/src/reliance-kernel.ts +119 -0
- package/src/reliance-packet.ts +206 -0
- package/src/remedy-program-postgres.ts +367 -0
- package/src/remedy-program-receipt.ts +891 -0
- package/src/remedy-program.ts +1388 -0
- package/src/reports/art14.ts +361 -0
- package/src/reports/assurance-package.ts +330 -0
- package/src/reports/auditor-workpaper.ts +558 -0
- package/src/reports/external-verification.ts +232 -0
- package/src/reports/reperform.ts +441 -0
- package/src/reports/underwriter.ts +356 -0
- package/src/retention.ts +100 -0
- package/src/roster.ts +273 -0
- package/src/runtime-monitor.ts +217 -0
- package/src/settlement.ts +313 -0
- package/src/siem.ts +247 -0
- package/src/store-postgres.ts +167 -0
- package/src/store.ts +214 -0
- package/src/strict-json.ts +115 -0
- package/src/trust-program-adapters.ts +847 -0
- package/src/trust-program-postgres.ts +429 -0
- package/src/trust-program-revocation.ts +751 -0
- package/src/trust-program.ts +1460 -0
- package/src/witness-postgres.ts +106 -0
- package/src/zk-range-proof.ts +250 -0
- package/store-postgres.js +3 -161
- package/store.js +4 -198
- package/strict-json.js +3 -85
- package/trust-program-adapters.js +4 -0
- package/trust-program-postgres.js +4 -0
- package/trust-program-revocation.js +4 -0
- package/trust-program.js +4 -0
- package/witness-postgres.js +3 -96
- package/zk-range-proof.js +4 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Tenant-scoped Postgres sequence store for EP-GATE-NETWORK-WITNESS-v1.
|
|
4
|
+
*
|
|
5
|
+
* The migration owns synchronization and authorization. Runtime callers get
|
|
6
|
+
* EXECUTE on one SECURITY DEFINER function, not INSERT/UPDATE privileges on the
|
|
7
|
+
* checkpoint table. Database ambiguity is allowed to throw so the witness
|
|
8
|
+
* ingestion kernel can fail closed as `sequence_store_unavailable`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const PG_WITNESS_SEQUENCE_VERSION = 'EP-GATE-PG-WITNESS-SEQUENCE-v1';
|
|
12
|
+
export const WITNESS_CHECKPOINT_FUNCTION = 'emilia_gate_evidence.advance_network_witness_checkpoint';
|
|
13
|
+
|
|
14
|
+
const DIGEST_RE = /^sha256:[0-9a-f]{64}$/;
|
|
15
|
+
const CLOSED_REASONS = new Set(['statement_replay', 'sequence_rollback', 'sequence_equivocation']);
|
|
16
|
+
|
|
17
|
+
export const WITNESS_SEQUENCE_SQL = Object.freeze({
|
|
18
|
+
advance: `SELECT accepted, reason
|
|
19
|
+
FROM ${WITNESS_CHECKPOINT_FUNCTION}($1, $2, $3::bytea, $4::bigint, $5)`,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
function scopedId(value, label) {
|
|
23
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 256
|
|
24
|
+
|| /[\u0000-\u001f\u007f]/.test(value)) {
|
|
25
|
+
throw new Error(`${label} must be a non-empty control-free string of at most 256 characters`);
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function streamBytes(value) {
|
|
31
|
+
if (typeof value !== 'string' || value.length > 513) {
|
|
32
|
+
throw new Error('witness streamId is invalid');
|
|
33
|
+
}
|
|
34
|
+
const parts = value.split('\0');
|
|
35
|
+
if (parts.length !== 2 || parts.some((part) => part.length === 0 || part.length > 256
|
|
36
|
+
|| /[\u0000-\u001f\u007f]/.test(part))) {
|
|
37
|
+
throw new Error('witness streamId must contain one witness-id/capture-point separator');
|
|
38
|
+
}
|
|
39
|
+
return Buffer.from(value, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function definitiveRow(result) {
|
|
43
|
+
if (!result || result.rowCount !== 1 || !Array.isArray(result.rows)
|
|
44
|
+
|| result.rows.length !== 1 || typeof result.rows[0]?.accepted !== 'boolean') {
|
|
45
|
+
throw new Error('witness checkpoint outcome is unproven');
|
|
46
|
+
}
|
|
47
|
+
const { accepted, reason } = result.rows[0];
|
|
48
|
+
if (accepted) {
|
|
49
|
+
if (reason !== null && reason !== undefined && reason !== '') {
|
|
50
|
+
throw new Error('accepted witness checkpoint carried a refusal reason');
|
|
51
|
+
}
|
|
52
|
+
return { accepted: true, reason: null };
|
|
53
|
+
}
|
|
54
|
+
if (!CLOSED_REASONS.has(reason)) {
|
|
55
|
+
throw new Error('witness checkpoint returned an unknown refusal reason');
|
|
56
|
+
}
|
|
57
|
+
return { accepted: false, reason };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create the durable store expected by acceptNetworkWitnessStatement().
|
|
62
|
+
* `query` is a node-postgres style function such as pool.query.bind(pool).
|
|
63
|
+
* @param {{ query?: Function, tenantId?: string|number, gateId?: string|number }} [o]
|
|
64
|
+
*/
|
|
65
|
+
export function createPostgresWitnessSequenceStore({
|
|
66
|
+
query,
|
|
67
|
+
tenantId,
|
|
68
|
+
gateId,
|
|
69
|
+
}: {
|
|
70
|
+
query?: (sql: string, params?: any[]) => Promise<any>;
|
|
71
|
+
tenantId?: string | number;
|
|
72
|
+
gateId?: string | number;
|
|
73
|
+
} = {}) {
|
|
74
|
+
if (typeof query !== 'function') {
|
|
75
|
+
throw new Error('createPostgresWitnessSequenceStore: query must be an async pg-style function');
|
|
76
|
+
}
|
|
77
|
+
const tenant = scopedId(tenantId, 'tenantId');
|
|
78
|
+
const gate = scopedId(gateId, 'gateId');
|
|
79
|
+
return Object.freeze({
|
|
80
|
+
durable: true,
|
|
81
|
+
scope: Object.freeze({ tenantId: tenant, gateId: gate }),
|
|
82
|
+
async advance(streamId, sequence, statementDigest) {
|
|
83
|
+
if (!Number.isSafeInteger(sequence) || sequence < 0) {
|
|
84
|
+
throw new Error('witness sequence must be a non-negative safe integer');
|
|
85
|
+
}
|
|
86
|
+
if (typeof statementDigest !== 'string' || !DIGEST_RE.test(statementDigest)) {
|
|
87
|
+
throw new Error('witness statement digest is invalid');
|
|
88
|
+
}
|
|
89
|
+
const result = await query(WITNESS_SEQUENCE_SQL.advance, [
|
|
90
|
+
tenant,
|
|
91
|
+
gate,
|
|
92
|
+
streamBytes(streamId),
|
|
93
|
+
sequence,
|
|
94
|
+
statementDigest,
|
|
95
|
+
]);
|
|
96
|
+
return definitiveRow(result);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default {
|
|
102
|
+
PG_WITNESS_SEQUENCE_VERSION,
|
|
103
|
+
WITNESS_CHECKPOINT_FUNCTION,
|
|
104
|
+
WITNESS_SEQUENCE_SQL,
|
|
105
|
+
createPostgresWitnessSequenceStore,
|
|
106
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EP-ZK-RANGE-RECEIPT-v1.
|
|
5
|
+
*
|
|
6
|
+
* This is a genuine Bulletproof range proof adapter, not a commitment renamed
|
|
7
|
+
* as zero knowledge. It proves a hidden integer v satisfies 0 <= v <= max by
|
|
8
|
+
* proving ranges for v and max-v, while the second Pedersen commitment is
|
|
9
|
+
* publicly linked to the first. The base EP receipt and its signature remain a
|
|
10
|
+
* separate verification step; this envelope binds only its public digest.
|
|
11
|
+
*
|
|
12
|
+
* The cryptographic engine is intentionally injected/lazy-loaded. The main
|
|
13
|
+
* Gate package does not silently add a 60 MB mobile/WASM dependency. The
|
|
14
|
+
* compatible backend is @aptos-labs/confidential-asset-bindings, which exposes
|
|
15
|
+
* Bulletproofs over Ristretto255. A deployment must pin and audit that optional
|
|
16
|
+
* backend before enabling this path.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
20
|
+
import { ristretto255, ristretto255_hasher } from '@noble/curves/ed25519.js';
|
|
21
|
+
import { canonicalize } from './execution-binding.js';
|
|
22
|
+
|
|
23
|
+
export const ZK_RANGE_RECEIPT_VERSION = 'EP-ZK-RANGE-RECEIPT-v1';
|
|
24
|
+
export const ZK_RANGE_SCHEME = 'Bulletproofs-Ristretto255-range-v1';
|
|
25
|
+
export const ZK_RANGE_BACKEND_PACKAGE = '@aptos-labs/confidential-asset-bindings@1.1.2';
|
|
26
|
+
const ZK_RANGE_BACKEND_MODULE = '@aptos-labs/confidential-asset-bindings';
|
|
27
|
+
|
|
28
|
+
const ORDER = ristretto255.Point.Fn.ORDER;
|
|
29
|
+
const TEXT_ENCODER = new TextEncoder();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {Uint8Array|string} value
|
|
33
|
+
* @param {string} label
|
|
34
|
+
* @param {number|null} [length]
|
|
35
|
+
* @returns {Uint8Array}
|
|
36
|
+
*/
|
|
37
|
+
function bytes(value, label, length: number | null = null) {
|
|
38
|
+
const out = value instanceof Uint8Array ? new Uint8Array(value) : Buffer.from(value, 'base64url');
|
|
39
|
+
if (length !== null && out.length !== length) throw new TypeError(`${label} must be ${length} bytes`);
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function b64(value) {
|
|
44
|
+
return Buffer.from(value).toString('base64url');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function sha256Hex(value) {
|
|
48
|
+
return createHash('sha256').update(value).digest('hex');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function digest(value, label) {
|
|
52
|
+
if (typeof value !== 'string' || !/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`${label} must be a sha256: digest`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function safeU64(value, label) {
|
|
57
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${label} must be a non-negative safe integer`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function rangeBits(max) {
|
|
62
|
+
if (max <= 0xff) return 8;
|
|
63
|
+
if (max <= 0xffff) return 16;
|
|
64
|
+
if (max <= 0xffffffff) return 32;
|
|
65
|
+
return 64;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function bytesToBigIntLE(value) {
|
|
69
|
+
let result = 0n;
|
|
70
|
+
for (let i = value.length - 1; i >= 0; i -= 1) result = (result << 8n) | BigInt(value[i]);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function bigIntToBytesLE(value, length) {
|
|
75
|
+
const out = new Uint8Array(length);
|
|
76
|
+
let current = value;
|
|
77
|
+
for (let i = 0; i < length; i += 1) {
|
|
78
|
+
out[i] = Number(current & 0xffn);
|
|
79
|
+
current >>= 8n;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function negateScalar(value) {
|
|
85
|
+
const scalar = bytesToBigIntLE(value) % ORDER;
|
|
86
|
+
return bigIntToBytesLE(scalar === 0n ? 0n : ORDER - scalar, 32);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Derive independent, deterministic Pedersen bases for this proof domain. */
|
|
90
|
+
export function deriveZkRangeBases(domain = ZK_RANGE_RECEIPT_VERSION) {
|
|
91
|
+
if (typeof domain !== 'string' || domain.length === 0) throw new TypeError('ZK range domain is required');
|
|
92
|
+
const valBase = ristretto255.Point.BASE.toBytes();
|
|
93
|
+
const randBase = ristretto255_hasher.hashToCurve(TEXT_ENCODER.encode(`${domain}:randomness-base`)).toBytes();
|
|
94
|
+
if (Buffer.from(valBase).equals(Buffer.from(randBase))) throw new Error('ZK range bases unexpectedly collide');
|
|
95
|
+
return { valBase, randBase };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Lazy-load the audited/pinned optional Bulletproof WASM binding. */
|
|
99
|
+
export async function loadBulletproofBackend() {
|
|
100
|
+
try {
|
|
101
|
+
// Keep the backend genuinely optional at bundle time. Server deployments
|
|
102
|
+
// that enable ZK install the pinned module explicitly; default Gate builds
|
|
103
|
+
// must not fail or pull the WASM/mobile distribution into every route.
|
|
104
|
+
return await import(/* webpackIgnore: true */ ZK_RANGE_BACKEND_MODULE);
|
|
105
|
+
} catch {
|
|
106
|
+
throw new Error(`ZK range backend unavailable; install ${ZK_RANGE_BACKEND_PACKAGE} explicitly before enabling this path`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function requireBackend(backend) {
|
|
111
|
+
if (!backend || typeof backend.batchRangeProof !== 'function' || typeof backend.batchVerifyProof !== 'function') {
|
|
112
|
+
throw new TypeError('ZK range backend must implement batchRangeProof() and batchVerifyProof()');
|
|
113
|
+
}
|
|
114
|
+
return backend;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function linkedSecondCommitment(max, first, valBase) {
|
|
118
|
+
const valueBase = ristretto255.Point.fromBytes(valBase);
|
|
119
|
+
const firstPoint = ristretto255.Point.fromBytes(first);
|
|
120
|
+
return valueBase.multiply(BigInt(max)).subtract(firstPoint).toBytes();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function equalBytes(left, right) {
|
|
124
|
+
return left.length === right.length && Buffer.from(left).equals(Buffer.from(right));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function publicStatement({ policyHash, actionPredicate, max, baseReceiptDigest, issuerPublicKey, numBits }) {
|
|
128
|
+
return {
|
|
129
|
+
policy_hash: digest(policyHash, 'policy_hash'),
|
|
130
|
+
action_predicate: actionPredicate,
|
|
131
|
+
max,
|
|
132
|
+
base_receipt_digest: digest(baseReceiptDigest, 'base_receipt_digest'),
|
|
133
|
+
issuer_public_key: issuerPublicKey,
|
|
134
|
+
num_bits: numBits,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Mint a hidden-amount Bulletproof range receipt.
|
|
140
|
+
* @param {{ value?: any, max?: any, blindingFactor?: any, policyHash?: any, actionPredicate?: any, baseReceiptDigest?: any, issuerPublicKey?: any, nonce?: string, domain?: string, backend?: any }} [options]
|
|
141
|
+
*/
|
|
142
|
+
export async function mintZkRangeReceipt({
|
|
143
|
+
value,
|
|
144
|
+
max,
|
|
145
|
+
blindingFactor = randomBytes(32),
|
|
146
|
+
policyHash,
|
|
147
|
+
actionPredicate,
|
|
148
|
+
baseReceiptDigest,
|
|
149
|
+
issuerPublicKey,
|
|
150
|
+
nonce = randomBytes(16).toString('base64url'),
|
|
151
|
+
domain = ZK_RANGE_RECEIPT_VERSION,
|
|
152
|
+
backend = null,
|
|
153
|
+
}: {
|
|
154
|
+
value?: number;
|
|
155
|
+
max?: number;
|
|
156
|
+
blindingFactor?: Uint8Array | string;
|
|
157
|
+
policyHash?: string;
|
|
158
|
+
actionPredicate?: string;
|
|
159
|
+
baseReceiptDigest?: string;
|
|
160
|
+
issuerPublicKey?: string;
|
|
161
|
+
nonce?: string;
|
|
162
|
+
domain?: string;
|
|
163
|
+
backend?: { batchRangeProof?: Function; batchVerifyProof?: Function } | null;
|
|
164
|
+
} = {}) {
|
|
165
|
+
const hiddenValue = safeU64(value, 'value');
|
|
166
|
+
const upperBound = safeU64(max, 'max');
|
|
167
|
+
if (hiddenValue > upperBound) throw new TypeError('value exceeds the public range bound');
|
|
168
|
+
if (typeof actionPredicate !== 'string' || actionPredicate.length === 0) throw new TypeError('actionPredicate is required');
|
|
169
|
+
if (typeof issuerPublicKey !== 'string' || issuerPublicKey.length === 0) throw new TypeError('issuerPublicKey is required');
|
|
170
|
+
const normalizedBlinding = bytes(blindingFactor, 'blindingFactor', 32);
|
|
171
|
+
const numBits = rangeBits(upperBound);
|
|
172
|
+
const bases = deriveZkRangeBases(domain);
|
|
173
|
+
const engine = requireBackend(backend || await loadBulletproofBackend());
|
|
174
|
+
const result = await engine.batchRangeProof({
|
|
175
|
+
v: [BigInt(hiddenValue), BigInt(upperBound - hiddenValue)],
|
|
176
|
+
rs: [normalizedBlinding, negateScalar(normalizedBlinding)],
|
|
177
|
+
valBase: bases.valBase,
|
|
178
|
+
randBase: bases.randBase,
|
|
179
|
+
numBits,
|
|
180
|
+
});
|
|
181
|
+
if (!result || !(result.proof instanceof Uint8Array) || !Array.isArray(result.comms) || result.comms.length !== 2) throw new Error('ZK range backend returned a malformed proof');
|
|
182
|
+
const first = bytes(result.comms[0], 'first commitment', 32);
|
|
183
|
+
const second = bytes(result.comms[1], 'second commitment', 32);
|
|
184
|
+
if (!equalBytes(second, linkedSecondCommitment(upperBound, first, bases.valBase))) throw new Error('ZK range backend returned unlinked commitments');
|
|
185
|
+
const statement = publicStatement({ policyHash, actionPredicate, max: upperBound, baseReceiptDigest, issuerPublicKey, numBits });
|
|
186
|
+
const commitments = envelopeCommitments([first, second]);
|
|
187
|
+
const envelope = {
|
|
188
|
+
'@version': ZK_RANGE_RECEIPT_VERSION,
|
|
189
|
+
scheme: ZK_RANGE_SCHEME,
|
|
190
|
+
domain,
|
|
191
|
+
nonce,
|
|
192
|
+
statement,
|
|
193
|
+
commitments,
|
|
194
|
+
proof: b64(result.proof),
|
|
195
|
+
binding: `sha256:${sha256Hex(canonicalize({ statement, commitments, nonce, domain }))}`,
|
|
196
|
+
};
|
|
197
|
+
return envelope;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function envelopeCommitments(commitments) {
|
|
201
|
+
return commitments.map((value) => b64(value));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Verify the range proof and its public commitment relation. */
|
|
205
|
+
export async function verifyZkRangeReceipt(receipt, { backend = null } = {}) {
|
|
206
|
+
try {
|
|
207
|
+
if (!receipt || receipt['@version'] !== ZK_RANGE_RECEIPT_VERSION || receipt.scheme !== ZK_RANGE_SCHEME) return { ok: false, reason: 'malformed_zk_range_receipt' };
|
|
208
|
+
const statement = receipt.statement;
|
|
209
|
+
const max = safeU64(statement?.max, 'statement.max');
|
|
210
|
+
const numBits = rangeBits(max);
|
|
211
|
+
if (statement.num_bits !== numBits) return { ok: false, reason: 'range_bits_mismatch' };
|
|
212
|
+
publicStatement({
|
|
213
|
+
policyHash: statement.policy_hash,
|
|
214
|
+
actionPredicate: statement.action_predicate,
|
|
215
|
+
baseReceiptDigest: statement.base_receipt_digest,
|
|
216
|
+
issuerPublicKey: statement.issuer_public_key,
|
|
217
|
+
max,
|
|
218
|
+
numBits,
|
|
219
|
+
});
|
|
220
|
+
const bases = deriveZkRangeBases(receipt.domain);
|
|
221
|
+
const commitments = receipt.commitments?.map((value) => bytes(Buffer.from(value, 'base64url'), 'commitment', 32));
|
|
222
|
+
if (!Array.isArray(commitments) || commitments.length !== 2) return { ok: false, reason: 'commitments_malformed' };
|
|
223
|
+
if (!equalBytes(commitments[1], linkedSecondCommitment(max, commitments[0], bases.valBase))) return { ok: false, reason: 'commitment_relation_invalid' };
|
|
224
|
+
const expectedBinding = `sha256:${sha256Hex(canonicalize({ statement, commitments: envelopeCommitments(commitments), nonce: receipt.nonce, domain: receipt.domain }))}`;
|
|
225
|
+
if (expectedBinding !== receipt.binding) return { ok: false, reason: 'zk_binding_invalid' };
|
|
226
|
+
const engine = requireBackend(backend || await loadBulletproofBackend());
|
|
227
|
+
const valid = await engine.batchVerifyProof({
|
|
228
|
+
proof: bytes(Buffer.from(receipt.proof || '', 'base64url'), 'proof'),
|
|
229
|
+
comms: commitments,
|
|
230
|
+
valBase: bases.valBase,
|
|
231
|
+
randBase: bases.randBase,
|
|
232
|
+
numBits,
|
|
233
|
+
});
|
|
234
|
+
return valid === true
|
|
235
|
+
? { ok: true, scheme: ZK_RANGE_SCHEME, statement }
|
|
236
|
+
: { ok: false, reason: 'zk_range_proof_invalid' };
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return { ok: false, reason: 'zk_range_receipt_invalid', detail: (error instanceof Error && error.message) || 'invalid proof' };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export default {
|
|
243
|
+
ZK_RANGE_RECEIPT_VERSION,
|
|
244
|
+
ZK_RANGE_SCHEME,
|
|
245
|
+
ZK_RANGE_BACKEND_PACKAGE,
|
|
246
|
+
deriveZkRangeBases,
|
|
247
|
+
loadBulletproofBackend,
|
|
248
|
+
mintZkRangeReceipt,
|
|
249
|
+
verifyZkRangeReceipt,
|
|
250
|
+
};
|
package/store-postgres.js
CHANGED
|
@@ -1,162 +1,4 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* Replay defense that survives restarts. This module implements the backend
|
|
7
|
-
* contract consumed by `createDurableConsumptionStore` in ./store.js:
|
|
8
|
-
*
|
|
9
|
-
* backend = {
|
|
10
|
-
* async addIfAbsent(key, value, { ttlSeconds }?) : boolean // true iff inserted
|
|
11
|
-
* async compareAndSet(key, expected, replacement, { ttlSeconds }?) : boolean
|
|
12
|
-
* async deleteIfValue(key, expected) : boolean
|
|
13
|
-
* async has(key) : boolean
|
|
14
|
-
* }
|
|
15
|
-
*
|
|
16
|
-
* plus a `cleanupExpired(now)` garbage-collection statement.
|
|
17
|
-
*
|
|
18
|
-
* Each transition is one SQL statement. `addIfAbsent` decides first use;
|
|
19
|
-
* `compareAndSet` and `deleteIfValue` bind commit/release to the opaque owner of
|
|
20
|
-
* the current reservation. There is no read-then-write interval in which a
|
|
21
|
-
* delayed worker can overwrite or remove a newer worker's reservation.
|
|
22
|
-
*
|
|
23
|
-
* FAIL-CLOSED CONTRACT: if the injected `query` THROWS (connection down,
|
|
24
|
-
* timeout, constraint other than the PK, ...) the error PROPAGATES to the
|
|
25
|
-
* caller — nothing here catches it. The gate must refuse an action when
|
|
26
|
-
* one-time-use cannot be proven; a backend error is NEVER treated as
|
|
27
|
-
* not-consumed (which would admit replays during an outage) and never treated
|
|
28
|
-
* as consumed-silently (which would mask the outage as a replay verdict).
|
|
29
|
-
* Likewise, an EXPIRED row that cleanup has not yet removed still refuses:
|
|
30
|
-
* TTL expiry is garbage collection, never a grant — the gate already rejects
|
|
31
|
-
* stale receipts on freshness, so nothing is lost by refusing conservatively.
|
|
32
|
-
*
|
|
33
|
-
* Deterministic by construction: the pg-style `query(text, params)` function
|
|
34
|
-
* and the clock (`now`) are both injected, so tests run against an in-memory
|
|
35
|
-
* fake with real ON CONFLICT semantics — no network, no database.
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
export const PG_CONSUMPTION_VERSION = 'EP-GATE-PG-CONSUMPTION-v2';
|
|
39
|
-
|
|
40
|
-
/** Single consumption table. Timestamps are epoch milliseconds (BIGINT) so the
|
|
41
|
-
* injected JS clock maps 1:1 onto column values with no timezone ambiguity. */
|
|
42
|
-
export const CONSUMPTION_TABLE = 'ep_gate_consumption';
|
|
43
|
-
|
|
44
|
-
export const CONSUMPTION_TABLE_DDL = `CREATE TABLE IF NOT EXISTS ${CONSUMPTION_TABLE} (
|
|
45
|
-
consumption_key TEXT PRIMARY KEY,
|
|
46
|
-
state TEXT NOT NULL,
|
|
47
|
-
consumed_at BIGINT NOT NULL,
|
|
48
|
-
expires_at BIGINT
|
|
49
|
-
);
|
|
50
|
-
CREATE INDEX IF NOT EXISTS ${CONSUMPTION_TABLE}_expires_idx
|
|
51
|
-
ON ${CONSUMPTION_TABLE} (expires_at) WHERE expires_at IS NOT NULL;`;
|
|
52
|
-
|
|
53
|
-
/** The exact statements the backend issues — exported for transparency and so
|
|
54
|
-
* a fake client can implement them without parsing SQL. */
|
|
55
|
-
export const CONSUMPTION_SQL = {
|
|
56
|
-
health: `SELECT
|
|
57
|
-
to_regclass('public.${CONSUMPTION_TABLE}') IS NOT NULL AS table_ready,
|
|
58
|
-
CASE WHEN to_regclass('public.${CONSUMPTION_TABLE}') IS NULL THEN FALSE
|
|
59
|
-
ELSE has_table_privilege(current_user, to_regclass('public.${CONSUMPTION_TABLE}'), 'SELECT,INSERT,UPDATE,DELETE') END AS can_use`,
|
|
60
|
-
/** $1 key, $2 state, $3 consumed_at ms, $4 expires_at ms|null. rowCount 1 = consumed, 0 = replay. */
|
|
61
|
-
addIfAbsent: `INSERT INTO ${CONSUMPTION_TABLE} (consumption_key, state, consumed_at, expires_at) `
|
|
62
|
-
+ 'VALUES ($1, $2, $3, $4) ON CONFLICT (consumption_key) DO NOTHING',
|
|
63
|
-
/** $1 key, $2 expected state, $3 replacement, $4 consumed_at ms, $5 expires_at ms|null. */
|
|
64
|
-
compareAndSet: `UPDATE ${CONSUMPTION_TABLE} SET state = $3, consumed_at = $4, expires_at = $5 `
|
|
65
|
-
+ 'WHERE consumption_key = $1 AND state = $2',
|
|
66
|
-
/** $1 key, $2 expected state. */
|
|
67
|
-
deleteIfValue: `DELETE FROM ${CONSUMPTION_TABLE} WHERE consumption_key = $1 AND state = $2`,
|
|
68
|
-
/** $1 key. Any row — even an expired one — counts as consumed until cleaned. */
|
|
69
|
-
has: `SELECT 1 FROM ${CONSUMPTION_TABLE} WHERE consumption_key = $1`,
|
|
70
|
-
/** $1 now ms. Removes ONLY rows whose TTL has elapsed; NULL expires_at never expires. */
|
|
71
|
-
cleanupExpired: `DELETE FROM ${CONSUMPTION_TABLE} WHERE state LIKE 'committed:%' `
|
|
72
|
-
+ 'AND expires_at IS NOT NULL AND expires_at <= $1',
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Create the Postgres consumption backend.
|
|
77
|
-
* @param {object} o
|
|
78
|
-
* @param {(text: string, params: any[]) => Promise<{ rowCount: number, rows?: any[] }>} o.query
|
|
79
|
-
* pg-style query function (e.g. `pool.query.bind(pool)`). Injected so tests
|
|
80
|
-
* need no database. MUST throw on failure — errors propagate (fail closed).
|
|
81
|
-
* @param {number|function} [o.now=Date.now] injected clock (ms or () => ms).
|
|
82
|
-
* @returns backend conforming to createDurableConsumptionStore, plus cleanupExpired(now).
|
|
83
|
-
*/
|
|
84
|
-
export function createPostgresBackend({ query, now = Date.now } = {}) {
|
|
85
|
-
if (typeof query !== 'function') {
|
|
86
|
-
throw new Error('createPostgresBackend: query must be an async (text, params) => { rowCount } function '
|
|
87
|
-
+ '(e.g. pg pool.query). It must THROW on failure — a backend error must never look like a verdict.');
|
|
88
|
-
}
|
|
89
|
-
let lastObservedNow = Number.NEGATIVE_INFINITY;
|
|
90
|
-
const checkedNow = (candidate) => {
|
|
91
|
-
if (!Number.isSafeInteger(candidate) || candidate < 0) {
|
|
92
|
-
throw new Error('consumption clock must return a non-negative safe-integer epoch millisecond');
|
|
93
|
-
}
|
|
94
|
-
if (candidate < lastObservedNow) {
|
|
95
|
-
throw new Error(`consumption clock regression refused: ${candidate} < ${lastObservedNow}`);
|
|
96
|
-
}
|
|
97
|
-
lastObservedNow = candidate;
|
|
98
|
-
return candidate;
|
|
99
|
-
};
|
|
100
|
-
const nowMs = () => checkedNow(typeof now === 'function' ? now() : now);
|
|
101
|
-
const expiryFor = (opt) => {
|
|
102
|
-
const ttl = Number(opt?.ttlSeconds);
|
|
103
|
-
return Number.isFinite(ttl) && ttl > 0 ? nowMs() + ttl * 1000 : null;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
durable: true,
|
|
108
|
-
async health() {
|
|
109
|
-
const res = await query(CONSUMPTION_SQL.health, []);
|
|
110
|
-
if (!res || res.rowCount !== 1 || !Array.isArray(res.rows) || res.rows.length !== 1) {
|
|
111
|
-
throw new Error('consumption health: malformed Postgres result');
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
ok: res.rows[0].table_ready === true && res.rows[0].can_use === true,
|
|
115
|
-
version: PG_CONSUMPTION_VERSION,
|
|
116
|
-
};
|
|
117
|
-
},
|
|
118
|
-
/** True iff THIS call inserted the row — the atomic consumed-vs-replay decision. */
|
|
119
|
-
async addIfAbsent(key, value, opt) {
|
|
120
|
-
const res = await query(CONSUMPTION_SQL.addIfAbsent, [key, value, nowMs(), expiryFor(opt)]);
|
|
121
|
-
// Fail closed on a malformed driver result: without a numeric rowCount we
|
|
122
|
-
// cannot PROVE first-use, so refuse loudly rather than guess.
|
|
123
|
-
if (!res || typeof res.rowCount !== 'number') {
|
|
124
|
-
throw new Error('addIfAbsent: query result has no numeric rowCount — cannot prove one-time use');
|
|
125
|
-
}
|
|
126
|
-
return res.rowCount === 1;
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
/** Ownership-fenced reserved -> committed transition. */
|
|
130
|
-
async compareAndSet(key, expected, replacement, opt) {
|
|
131
|
-
const res = await query(CONSUMPTION_SQL.compareAndSet, [key, expected, replacement, nowMs(), expiryFor(opt)]);
|
|
132
|
-
if (!res || typeof res.rowCount !== 'number') {
|
|
133
|
-
throw new Error('compareAndSet: query result has no numeric rowCount — cannot prove reservation ownership');
|
|
134
|
-
}
|
|
135
|
-
return res.rowCount === 1;
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
/** Remove only the reservation owned by the caller. */
|
|
139
|
-
async deleteIfValue(key, expected) {
|
|
140
|
-
const res = await query(CONSUMPTION_SQL.deleteIfValue, [key, expected]);
|
|
141
|
-
if (!res || typeof res.rowCount !== 'number') {
|
|
142
|
-
throw new Error('deleteIfValue: query result has no numeric rowCount — cannot prove reservation ownership');
|
|
143
|
-
}
|
|
144
|
-
return res.rowCount === 1;
|
|
145
|
-
},
|
|
146
|
-
|
|
147
|
-
/** Present = consumed. Expired-but-uncleaned rows still count (conservative). */
|
|
148
|
-
async has(key) {
|
|
149
|
-
const res = await query(CONSUMPTION_SQL.has, [key]);
|
|
150
|
-
return (res?.rows?.length ?? res?.rowCount ?? 0) > 0;
|
|
151
|
-
},
|
|
152
|
-
|
|
153
|
-
/** Garbage-collect rows whose TTL elapsed. Returns the number removed. */
|
|
154
|
-
async cleanupExpired(at) {
|
|
155
|
-
const cleanupAt = at === undefined ? nowMs() : checkedNow(at);
|
|
156
|
-
const res = await query(CONSUMPTION_SQL.cleanupExpired, [cleanupAt]);
|
|
157
|
-
return res?.rowCount ?? 0;
|
|
158
|
-
},
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export default { createPostgresBackend, CONSUMPTION_TABLE_DDL, CONSUMPTION_TABLE, CONSUMPTION_SQL, PG_CONSUMPTION_VERSION };
|
|
2
|
+
// Compatibility entry point for the TypeScript store-postgres module.
|
|
3
|
+
export * from './dist/store-postgres.js';
|
|
4
|
+
export { default } from './dist/store-postgres.js';
|