Skip to content

feat(serving): PostgresControlPlaneStore — the scale profile ships (ADR 0010 slice 5) - #135

Merged
brownjuly2003-code merged 3 commits into
mainfrom
feature/control-plane-slice5-postgres
Jul 2, 2026
Merged

feat(serving): PostgresControlPlaneStore — the scale profile ships (ADR 0010 slice 5)#135
brownjuly2003-code merged 3 commits into
mainfrom
feature/control-plane-slice5-postgres

Conversation

@brownjuly2003-code

Copy link
Copy Markdown
Owner

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 via ON CONFLICT DO NOTHING rowcount, FOR UPDATE SKIP LOCKED + self-expiring lease_expires_at for 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; psycopg is a new optional extra (the redis pattern); missing DSN/psycopg fails the boot loudly — no silent fallback.
  • Webhook registrations (state class 5) move behind the port — the sharpest split-brain of the ADR's inventory was still per-pod YAML outside the port after slices 1–4. Registration CRUD takes app and resolves the store inside (the same move alerts made in slice 2); embedded keeps byte-compatible config/webhooks.yaml.
  • Alert-tick single-flight (ADR §2): new claim_alert_tick/complete_alert_tick wired into AlertDispatcher — 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.
  • One shared store on the postgres profile: main.py injects it into AuthManager/OutboxProcessor; analytics entry points (analytics.py, routers/admin.py, admin_ui.py) route through AuthManager.store — usage/sessions land in PostgreSQL, not a per-pod DuckDB file.
  • Helm untouched by design: schema enum still pins embedded; the chart profile (env+secret wiring, enum extension) is rollout slice 6, which this PR unblocks.

Verification

  • Live, standalone PostgreSQL 17.5 (no Docker): 31/31 probes — the ADR-named suite (parallel claim exclusivity, lease-expiry re-drive, restart re-drive, enqueue-win uniqueness, outbox↔dead-letter atomicity incl. both rollback halves, alert-tick single-flight) + full contract parity sweep + an end-to-end app test: two boots on the postgres profile see each other's webhook registration. Evidence: docs/perf/control-plane-pg-verify-2026-07-03.md.
  • CI: new postgres:17 service in test-integration runs the same suite (AGENTFLOW_TEST_PG_DSN; self-skips when absent — the test_clickhouse_backend_live.py pattern).
  • Local gates: unit+property 1586 passed, integration 237 passed / 19 skipped, mypy strict clean, ruff check+format clean, bandit B608 surface pinned and documented (A-4 registry + docs/security-audit.md).

🤖 Generated with Claude Code

…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>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

DORA Metrics

  • Window: last 30 days
  • Branch: main
  • Deployment frequency: 129 total / 30.1 per week
  • Lead time for changes: avg 0.4h / median 0.0h
  • Change failure rate: 75.19% (97/129)
  • MTTR: 0.25h across 3 incident(s)

JuliaEdom and others added 2 commits July 3, 2026 01:11
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>
@brownjuly2003-code
brownjuly2003-code merged commit 4104382 into main Jul 2, 2026
22 checks passed
@brownjuly2003-code
brownjuly2003-code deleted the feature/control-plane-slice5-postgres branch July 2, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants