docs: agent & strategy persistence design + plan#113
Conversation
…ends) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPTbEcj871EPmEyXwJb2Fi
Task-by-task TDD plan for the DATABASE_URL Postgres backends spec (docs/superpowers/specs/2026-07-15-agent-strategy-persistence-design.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPTbEcj871EPmEyXwJb2Fi
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Review of #113 raised 5 findings; adversarial verification of the fixes surfaced a 6th that defeated one of them. All are folded in here. - CI had no Postgres service, so the @pg_only tier skipped on every PR and ~450 lines of new SQL would reach prod unexecuted -- where the first run is _init_schema() at import time under fail-loud semantics, on a free tier with no zero-downtime deploys. New Task 2 adds a postgres:16-alpine service + TEST_POSTGRES_URL, plus a guard test so dropping it fails loudly instead of silently reverting to all-skip. - "backend: postgres" could not distinguish the intended Neon DB from a stray DATABASE_URL. New Task 3 adds db_url.py::describe_database_url() -> host/db, defined once because it is credential-scrubbing code. - print(), not logger.info(): uvicorn's LOGGING_CONFIG has no 'root' key, so dashboard.backend.* loggers sit at WARNING and info() emits nothing in prod. caplog.at_level() hides this -- green suite, silent prod, which is the exact failure this feature exists to kill. Verified by reproduction. - "Fail loud" was a hard constraint with no tests; added one per twin (needs no live Postgres, and is the only tier that runs a twin's real __init__). - test_strategy_store.py ALREADY EXISTS at tests/domain/strategies/; the plan said "Create" at tests/. Now Modify, reusing its _store() helper. - Reconciled the spec/plan contradiction on ALTER TABLE migrations (none are needed; the tables are born with every column). - Merge discipline: the PR opens as a draft with the DO NOT MERGE gate in its first line, and marking it ready stays a human decision after the Render env var is set. A gate that lives only in a plan file is not a gate. - Dropped a miscited test (test_agent_repository_compatibility.py builds stores directly and is indifferent to DATABASE_URL) that would have shipped into a committed docstring. Verified rather than asserted: describe_database_url and its tests run green; psycopg raises OperationalError on a refused port in 0.00s; the collision tests pass against the real StrategyStore (calls == 21); the CI yaml parses and deploy-prod cannot fire from a draft PR; print() survives uvicorn's real config where logger.info() does not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPTbEcj871EPmEyXwJb2Fi
|
Reviewed this branch and pushed the fixes as The Postgres half would have shipped to prod unexecuted. CI has no
Also: "fail loud" was a hard constraint with zero tests (added one per twin — no live Postgres needed, and it's the only tier that runs a twin's real Verified rather than asserted: Still docs-only and safe to merge. Task count went 7 → 9. |
Reverses Decision 2's naming call. DATABASE_URL is the Heroku de-facto standard: auto-injected by managed-Postgres add-ons and plausibly already exported in a dev's shell, so an ambient value could silently bind the app to the wrong database. Nothing can protect a local uvicorn run from that -- reading the env var is the feature -- so the collision is designed out of the name instead of merely logged. Drops the users-store fallback as a consequence: a var named for content has no business selecting the account database. USERS_DATABASE_URL and CONTENT_DATABASE_URL are now scoped per store and never resolve against each other; a fully durable deployment sets both at the same Neon DB. Prod already sets USERS_DATABASE_URL, so its behavior is unchanged. Not a find/replace -- the prose that argued for the old name was rewritten: - Decision 2 becomes a settled choice; the log-line rationale is re-anchored to typo'd/staging URLs and the unset->SQLite branch, which the rename does NOT address (so a later reader can't read it as redundant). - Task 4 shrinks to "log line only"; its two fallback tests are replaced by one inverted test pinning that users IGNORES CONTENT_DATABASE_URL, so re-adding the fallback can't pass green. - Task 1's urgency recalibrated: a scoped name narrows the ambient trigger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPTbEcj871EPmEyXwJb2Fi
|
Two settled changes: 1. Scoped name. 2. Users fallback dropped (the "cost of the one-var goal" the old Decision 2 predicted). Not a find/replace — the prose that argued for the old name was rewritten:
Verified with a 4-lens review + adversarial refutation, which caught two mechanical-rename leftovers I'd missed (Task 3's intro and the File Structure table both still called the new name "the most collision-prone name in the ecosystem" / "Precedence"); a follow-up grep caught a third in |
Approved design spec + task-by-task implementation plan for making user-created content survive Render redeploys:
external_agents,agent_versions, andstrategiesget optional Postgres backends behind one new env varCONTENT_DATABASE_URL, cloning the shippedUSERS_DATABASE_URLusers fix (hand-written twin class per store, per-module factory, singletons unchanged). Deliberately not namedDATABASE_URL— that is the Heroku-convention name add-ons inject and unrelated projects export, and an ambient value would silently bind the app to the wrong database.Docs only — no code changes; safe to merge on its own. Implementation lands in a follow-up PR off the plan (
docs/superpowers/plans/2026-07-15-agent-strategy-persistence.md), which opens as a draft and carries its own gate:CONTENT_DATABASE_URLmust be set in the Render dashboard before that PR merges (unset silently selects ephemeral SQLite).🤖 Generated with Claude Code
https://claude.ai/code/session_01VPTbEcj871EPmEyXwJb2Fi