Skip to content

Releases: microsoft/pg_durable

Release v0.2.4-rc1

Release v0.2.4-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Jul 22:52
05cd2b6

Release candidate for v0.2.4. This is a pre-release for validation only — not a final release. The notes below describe the full v0.2.4 changes.


Provider-line note: v0.2.4 stays in the duroxide-pg provider compatibility line, so the upgrade source is v0.2.3 (sql/pg_durable--0.2.3--0.2.4.sql).

Added

  • Instance retention/pruning (#265): terminal instances are now pruned by a hard cap and a retention window, bounding unbounded growth of df.instances.
  • df.list_instances() pagination & filtering (#278): added a label_filter, keyset pagination (after_cursor/next_cursor), and created_at/updated_at timestamps to the result set.

Changed

  • df.wait_for_schedule() cron timing: the next cron tick is now computed at execution time using duroxide's deterministic clock (ctx.utc_now()) inside the execute_function_graph orchestration, instead of being pre-computed at df.start() time. This makes recurring @> schedules and any start-to-execution delay target the correct upcoming tick (#130).

    ⚠️ Replay-breaking for in-flight wait_for_schedule instances. This change adds a recorded utc_now() decision before the WAIT_SCHEDULE timer, altering the orchestration's history sequence. Any durable function that was started under a <= 0.2.3 binary and is mid-wait_for_schedule (parked on its timer) when this .so is loaded will fail with a duroxide nondeterminism error on replay, because its recorded history no longer matches the new code. Drain or allow such in-flight wait_for_schedule instances to complete before upgrading. Instances that are not currently inside a wait_for_schedule node are unaffected. We accepted this break (rather than introducing orchestration versioning) given the early pre-1.0 stage of the project.

  • Instance/node ID collision hardening (#129): df.start() now reserves IDs with INSERT ... ON CONFLICT DO NOTHING RETURNING id and re-rolls the random 8-hex value on collision — instances arbitrate on the df.instances primary key (id), nodes on the new composite PRIMARY KEY (instance_id, id) — replacing the previous SELECT EXISTS pre-check. Doing the conflict check at the index level (rather than a pre-check SELECT) closes a TOCTOU window and, for instances, an RLS blind spot where the pre-check could not see another role's rows. df.nodes now uses the composite PRIMARY KEY (instance_id, id) instead of a global single-column key, so the random 8-hex node ID is no longer the sole cross-instance collision guard. The update-node-status activity now scopes its df.nodes update by instance_id (a required activity-input field) and asserts it affects exactly one row. IDs stay VARCHAR(8) HEX; the 0.2.3 → 0.2.4 upgrade restructures the df.nodes keys in place (#238).

    • Breaking for in-flight work: the new activity-input shape changes the string duroxide records in orchestration history, and duroxide validates activity inputs by exact equality on replay, so any instance left in flight across the 0.2.3 → 0.2.4 binary upgrade cannot complete. Drain or cancel in-flight instances before deploying 0.2.4. The in-place df.nodes key restructure also takes an ACCESS EXCLUSIVE lock whose duration scales with table size — run the upgrade in a maintenance window. See the #129 section of docs/upgrade-testing.md for the full drain-before-upgrade contract.
  • df.grant_usage() / df.revoke_usage(): dropped the explicit per-function EXECUTE allowlist. Schema USAGE on df is the real access gate for ordinary df.* functions, so the helpers now grant/revoke schema USAGE, the table privileges, and EXECUTE only on the sensitive functions (df.http, df.grant_usage, df.revoke_usage). Function signatures are unchanged and existing privileges are unaffected (#242).

  • df.list_instances() page-size cap is now a loud error (#146): df.list_instances() previously truncated limit_count silently to a fixed ceiling of 10000. It now raises an error when limit_count exceeds the new pg_durable.list_instances_max_limit GUC (SUSET context, default 1000, range 11000000), so an over-cap request fails fast instead of returning a silently short page that is indistinguishable from "no more rows". Both the basic and paginated overloads enforce the cap; clients needing more rows should lower limit_count or use the paginated overload (after_cursor/next_cursor). A superuser can change the cap at runtime without a restart; by default an ordinary caller cannot.

  • Renamed df.wait_for_completion() (#164): the function was renamed and hardened against unsafe use. Breaking: callers of the old name must update to the new name.

  • Node statuses derived from execution lineage (#263, #283): node status is now derived from the durable engine's execution lineage, reconciling the df control-plane with the engine so reported statuses match actual execution.

  • df.start() fails fast on engine hand-off failure (#282): if the hand-off to the durable engine fails, df.start() now returns an error immediately instead of leaving a stuck instance behind.

  • Dependencies: bumped reqwest to 0.13.4 (#260) and uuid to 1.23.4 (#273).

Fixed

  • explain race branches (#276): race (|) branches now render correctly in df.explain() output.
  • Loop safety (#254): df.loop() now enforces a max-iteration guard and detects malformed loop configuration instead of looping unboundedly or misbehaving.
  • $name.* expansion cap (#255): a row-count limit (10,000) is now enforced when expanding $name.*, preventing unbounded expansion.
  • df.http() User-Agent (#270): requests now send a default User-Agent header.
  • Connection reliability (#251, #252): the client is now recoverable after a connection failure, and epoch/extension polling is isolated onto a dedicated connection pool so it can no longer contend with execution work.
  • df.list_instances() N+1 (#275): instance-info lookups are now batched, removing an N+1 query pattern.
  • Indexes (#271): added a created_at index and a composite status index on df.instances to speed up listing and status queries.

Security

  • SSRF CGNAT range (#253): the 100.64.0.0/10 CGNAT range is now blocked by SSRF protection in df.http().
  • df.metrics() access (#184): df.metrics() is now gated behind an explicit EXECUTE grant rather than being callable by default.

Removed

  • df.debug_connection(): removed from the SQL surface as non-security, surface-reduction cleanup (#110). The function returned the worker connection string (postgres://role@host:port/db) with no password or credential, and the worker role is already visible through native PostgreSQL channels (the world-readable pg_durable.worker_role GUC and pg_stat_activity.usename) — so issue #110 is reclassified from a security finding to cleanup. Fresh installs no longer create the function and the 0.2.3 → 0.2.4 upgrade drops it; a binary-compatibility shim retains the underlying C symbol so pre-0.2.4 schemas keep resolving the function until ALTER EXTENSION pg_durable UPDATE runs.

Documentation

  • Documented SECURITY DEFINER df.start() behavior (#185), corrected documentation examples (#257), and clarified that df.status()/df.result() take an instance_id rather than a label (#167).

Acknowledgements

Thanks to everyone who contributed to this release: @crprashant @iemejia @nanookclaw @pinodeca @Saithej2k @snvtac @tjgreen42.

New Contributors

Full Changelog: v0.2.3...v0.2.4-rc1

Release v0.2.3

Choose a tag to compare

@github-actions github-actions released this 17 Jun 22:42
b4579ae

What's new in v0.2.3

Provider-line note: v0.2.3 stays in the duroxide-pg provider compatibility line started in v0.2.2, so the upgrade source is v0.2.2 (sql/pg_durable--0.2.2--0.2.3.sql).

Added

  • Debian release packages: AMD64 .deb packages for PostgreSQL 17 and 18, built and validated by the Package Release workflow on tagged releases (#190, #203).
  • Public Docker images: ghcr.io/microsoft/pg_durable images are published from the released .deb packages for PG 17 and 18. These images are for evaluating and learning pg_durable only - not for production (#218, #222, #223).

Changed

  • duroxide provider schema: fresh installs now use _duroxide as the duroxide-pg provider schema, while installations upgraded from earlier versions keep the legacy duroxide schema. The active schema is resolved at runtime via df.duroxide_schema(), so the change is transparent to existing deployments (#201).
  • Default worker role: the background worker's default role is now postgres instead of azuresu (#206).
  • df.break() internals: df.break() now carries its value as a typed NodeError instead of a JSON sentinel, with a compatibility fallback for envelopes written before #148 (#229).
  • JSON conversion: internal SQL-to-JSON value conversion now goes through try_from_json() for more robust error handling (#235).
  • Dependencies: bumped reqwest to 0.13 to match the lockfile (#237) and updated five crates in the cargo dependency group (#236). Added Dependabot for weekly cargo updates (#231).

Fixed

  • Reliability audit: fixed a set of correctness and safety bugs found during a reliability audit (#220):
    • df.if() / df.loop() conditions whose SQL returns zero rows now correctly evaluate as false instead of true (previously the empty result envelope was treated as truthy).
    • Graphs nested deeper than 256 levels are now rejected, preventing stack overflow from deeply nested operator chains.
    • Graphs with more than 10,000 nodes are now rejected, preventing unbounded INSERT storms and out-of-memory conditions.
    • Per-user SQL connections now have a 30-second connect timeout, so a stalled connection can no longer hold an execution slot indefinitely.
  • Non-finite floats: SQL columns containing NaN or Infinity now map to JSON null instead of failing the workflow (#144).
  • Execution-history errors: df.instance_executions now surfaces execution-history lookup failures instead of silently hiding them (#225, closes #168).

Security

  • Docker/GHCR hardening: hardened the published Docker image and the GHCR publish workflow, including least-privilege permissions and provenance/SBOM attestations on published images (#223).

Documentation

  • Clarified df.http() security scope versus SQL extension execution (#216).
  • Corrected stale identity-model documentation and examples (#219, #224).
  • Added the documentation website, refreshed the README, and standardized terminology to "durable functions" (#198, #204, #205, #207, #208, #211).
  • Referenced the pg_durable.database GUC instead of the PGDATABASE environment variable (#200).

New Contributors

Full Changelog: v0.2.2...v0.2.3

Release v0.2.2

Choose a tag to compare

@github-actions github-actions released this 10 Jun 16:26

Release v0.2.2