feat(serving): PostgresControlPlaneStore — the scale profile ships (ADR 0010 slice 5) - #135
Merged
Merged
Conversation
…DR 0010 slice 5) All six control-plane state classes as PostgreSQL tables behind the ControlPlaneStore port, with real claim semantics: enqueue-win by ON CONFLICT rowcount, FOR UPDATE SKIP LOCKED + self-expiring leases for queue/outbox claims (work-stealing, no leader election), invariant 8 as an ordinary transaction. Selection via AGENTFLOW_CONTROLPLANE_STORE=postgres + AGENTFLOW_CONTROLPLANE_PG_DSN; psycopg is a new optional extra (the redis pattern); the slice-1 NotImplementedError ratchet is gone. Two scope additions the extraction slices left open, both needed for the ADR's "all six classes" to hold: - webhook registrations (class 5, the sharpest split-brain) move behind the port; registration CRUD takes `app` and resolves the store inside (the slice-2 alerts move); embedded keeps byte-compatible config/webhooks.yaml. - alert-tick single-flight (ADR §2): claim_alert_tick/complete_alert_tick wired into AlertDispatcher — per-rule state persistence in the same transaction as the claim release, replacing the full-set save that would let two replicas clobber each other's rule runtime state. On the postgres profile main.py injects the shared store into AuthManager and OutboxProcessor, and the analytics entry points route through AuthManager.store — usage/sessions land in PostgreSQL, not a per-pod file. Verified live (standalone PostgreSQL 17.5, no Docker): 31/31 probes — parallel claim exclusivity, lease-expiry re-drive, restart re-drive, enqueue-win uniqueness, outbox<->dead-letter atomicity incl. rollback halves, alert-tick single-flight, full contract parity sweep, and an end-to-end two-boot app test (docs/perf/control-plane-pg-verify-2026-07-03.md). The same suite runs in CI against a new postgres:17 service and self-skips without AGENTFLOW_TEST_PG_DSN. Helm untouched by design: the schema enum still pins embedded until slice 6 ships the chart profile. Full unit+property 1586, integration 237/19skip, mypy strict, ruff check+format, bandit surface pinned (B608 sites documented, A-4 registry). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DORA Metrics
|
The lint job installs no optional extras, so psycopg resolves as Any and warn_return_any flags _connect's bare return; an annotated local pins the declared context-manager type in both environments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… has no optional extras) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PostgresControlPlaneStore(src/serving/control_plane/postgres.py): all six ADR 0010 state classes as PostgreSQL tables behind the existing port. Real claim semantics: enqueue-win viaON CONFLICT DO NOTHINGrowcount,FOR UPDATE SKIP LOCKED+ self-expiringlease_expires_atfor webhook-queue/outbox claims (work-stealing across replicas, no leader election; crash recovery = lease expiry), invariant 8 as an ordinary transaction (every store method is one transaction). Payloads stay TEXT/JSON-strings, one connection per call (pooling out of ADR scope). Selection:AGENTFLOW_CONTROLPLANE_STORE=postgres+AGENTFLOW_CONTROLPLANE_PG_DSN;psycopgis a new optional extra (theredispattern); missing DSN/psycopg fails the boot loudly — no silent fallback.appand resolves the store inside (the same move alerts made in slice 2); embedded keeps byte-compatibleconfig/webhooks.yaml.claim_alert_tick/complete_alert_tickwired intoAlertDispatcher— only the claim winner evaluates a rule, and its state persists per rule in the same transaction as the claim release (a full-set save would let two replicas clobber each other's runtime state). Embedded grants every claim, so the single-replica profile is unchanged.main.pyinjects it intoAuthManager/OutboxProcessor; analytics entry points (analytics.py,routers/admin.py,admin_ui.py) route throughAuthManager.store— usage/sessions land in PostgreSQL, not a per-pod DuckDB file.embedded; the chart profile (env+secret wiring, enum extension) is rollout slice 6, which this PR unblocks.Verification
docs/perf/control-plane-pg-verify-2026-07-03.md.postgres:17service intest-integrationruns the same suite (AGENTFLOW_TEST_PG_DSN; self-skips when absent — thetest_clickhouse_backend_live.pypattern).docs/security-audit.md).🤖 Generated with Claude Code