fix(ci): add Postgres service container to backend job, close fresh-install migration gap - #1502
Conversation
…nstall migration gap
backend job in app-ci.yml had no `services:` block, so every
@pytest.mark.postgres real-Postgres test connected to nothing and always
silently pytest.skip'd — nobody knew this ecosystem-wide until it was run
for real. Adds a pgvector/pgvector:pg16 service (test/test/test_db,
pg_isready healthcheck) with a matching DATABASE_URL, matching
tests/conftest.py's existing default.
Running the suite for real against local PostgreSQL 16 + pgvector surfaced
3 more latent bugs beyond the CI wiring itself:
- 0011_email_read_state still hardcoded the pre-rename legacy `emails`
table (`op.add_column("emails", "is_read", ...)`), so `alembic upgrade
head` against a genuinely fresh database crashed with
`relation "emails" does not exist` even after bootstrap_db.py's existing
legacy-index statement was guarded. email_records (the current ORM
table) already has is_read, so a fresh install needs this migration to
be a no-op; guarded it the same way, behind
sa.inspect(...).has_table("emails").
- Raw SQL INSERT INTO email_records in test_bootstrap_db.py and
test_data_api.py omitted is_read (Python-side ORM default only, no DB
server default), so real Postgres rejected them with
NotNullViolationError. Filled it in explicitly at all 4 call sites.
- test_bootstrap_db.py's local _execute_schema_backfill test helper
duplicated the old unguarded backfill loop instead of using the new
scripts.bootstrap_db.execute_schema_backfill; pointed it at the real
guarded function instead.
Verified against real PostgreSQL 16 + pgvector: full backend suite 1837
passed / 2 skipped (unrelated LIVE_BASE_URL live-API smoke), ruff clean,
`alembic upgrade head` and scripts/bootstrap_db.py both idempotent against
a fresh database. Documented the new hard CI gate in CLAUDE.md and
AGENTS.md so it isn't assumed best-effort going forward.
Closes the follow-up tracked in ContextualWisdomLab/.github's
docs/product-technical-gap-baseline.md (2026-09-01 entry).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YgmaEKnPhrsbSyQBierFH
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Exact-head audit found and fixed one migration symmetry defect in |
|
Correction to the preceding audit note: the exact pushed SHA is |
|
PR governance metadata gate is not ready for
|
|
|
|
|
|
이 PR에서 뜬 건 정확히는 문제 보고가 아니라 의도한 상태입니다 — real Postgres로 검증하는 동안 머지 자동화가 개입하지 않도록 일부러 draft로 열어뒀고, 더 넓은 질문(왜 announce만 하고 contextual-orchestrator/orchestrator-free로 OpenCode Agent를 띄워 직접 고치지 않는가)에 대해서는, 코드를 읽어보면 이 워크플로 자체가 이 gate가 잡는 다른 blocker(CHANGES_REQUESTED, 미해결 스레드, CodeRabbit blocking finding 등)까지 autofix 범위를 넓히고 싶으시면, 그건 naruon이 아니라 Generated by Claude Code |
|
추가하신 "OpenCode Agent가 Ready for Review 전환까지 의사결정" 부분은 제 답변이 놓쳤네요 — 그 부분만 짚습니다. 이 PR 한정으로는 이미 그렇게 되고 있습니다: 이 PR을 연 게 저(이 세션)이고, CI가 실제로 통과하는지 계속 지켜보다가 확인되면 제가 직접 ready로 바꿀 겁니다 — 그 판단을 하는 agent가 이미 붙어 있는 셈입니다, 별도로 하나 더 띄울 필요 없이. owner께서 그리시는 건 아마 더 일반적인 경우 — PR을 연 세션이 다시 돌아오지 않고 draft로 방치되는 경우, org 차원에서 그런 draft들을 지켜보다가 체크가 다 통과하면 자동으로 ready로 승격하는 별도 상시 agent — 인 것 같은데, 맞다면 이것도 naruon이 아니라 Generated by Claude Code |
|
근데 왜 동작 안 해? |
|
확인해봤습니다 — 이건 이 PR의 문제가 아니라 org 전체의 Actions 러너 용량 고갈로 보입니다.
즉 org 차원에서 동시에 굉장히 많은 자율 세션이 PR을 계속 push하고 있고, 그 총 동시 요청량이 GitHub Actions 동시 실행 한도를 넘어서 있는 것으로 보입니다. 제 쪽에서 이 PR 안에서 고칠 수 있는 문제가 아니라, org/계정의 Actions concurrency 한도(플랜 상향 또는 self-hosted runner 추가)나, 동시에 push되는 세션 수를 줄이는 쪽에서 다뤄야 할 것 같습니다. 이 PR은 큐가 풀릴 때까지 계속 지켜보고 있겠습니다. Generated by Claude Code |
|
Fresh PostgreSQL runtime evidence for current HEAD |
… change 11377dd removed the develop/master/release/** branch restriction from app-ci.yml's and docker-publish.yml's pull_request: triggers (so governed checks run on stacked PRs too), but left two test_release_governance.py assertions pinned to the old branch-restricted shape (`assert "release/**" in workflow`, `assert "develop" in pull_request_block`) — genuine RED, reproduced locally. Updated both to assert the new intended shape (no branches: restriction on the pull_request trigger), matching tests/test_stacked_pr_workflow_contract.py. Verified: full backend suite 1838 passed/2 skipped against real Postgres, ruff clean, scripts/ci/test_pr_governance_gate.sh PASS, tests/test_stacked_pr_workflow_contract.py passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YgmaEKnPhrsbSyQBierFH
PR #1502 (opened in parallel, ~5 minutes before this one) independently diagnosed the same underlying gap from the same root cause -- running the real backend suite against actual PostgreSQL for the first time -- and found something this PR didn't: app-ci.yml's backend job has never had a Postgres services: container, so every @pytest.mark.postgres test (including all the new ones in this PR) has always silently skipped in real CI. It ships the authoritative fix for that, plus its own version of the 0011_email_read_state /bootstrap_db.py fix, with a pinned contract test. Landing two different versions of the same files from two open PRs would conflict. Adopt #1502's exact pattern for the overlapping files instead of this PR's earlier approach: - 0011_email_read_state.py: has_table("emails")-guarded no-op, keeping the original "emails" target, rather than retargeting to "email_records". (0011_email_model_reconciliation's own docstring clarifies no migration ever renamed "emails" to "email_records" for a real managed database -- email_records was the actual table name since inception; "emails" was only ever a stale copy-pasted string. Both approaches are safe in practice, so there's no reason to diverge from the already-tested pattern.) - bootstrap_db.py / 0001_initial_control_plane.py: schema_backfill_sql()'s callers now go through execute_schema_backfill(), which skips the legacy ix_emails_owner_date statement via identity-matching a LEGACY_EMAILS_INDEX sentinel rather than this PR's simpler unconditional deletion. - test_alembic_migrations.py: contract test now asserts execute_schema_backfill, plus #1502's own test_email_read_state_legacy_table_guard_is_reversible pinning the reconciled 0011 file's shape. - test_bootstrap_db.py / test_data_api.py: the 4 raw SQL `INSERT INTO email_records` smoke-seeding call sites now set is_read explicitly (Python-side ORM default only, no DB server default, so real Postgres rejects the omission) -- the exact bug flagged as a follow-up earlier in this PR's own investigation. Re-verified end-to-end: fresh-database migration to head, and the true historical-database reproduction (migrate to 0015, drop the workspace registry tables, continue to head crossing both 0011_email_read_state and 0016_document_org_scope) both still complete cleanly. Full backend suite: 1841 passed, 0 failed, 3 skipped (up from 1838/2 failed -- the last 2 pre-existing failures are now fixed too), ruff clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ErwZSYW3pm585NiM3Q7aN
Devin Review found two real gaps in the has_table("emails")-only pattern
adopted from PR #1502 in the previous commit:
1. A genuinely historical database -- one whose own 0001 ran before
is_read was added to the Email model, so it has email_records without
is_read -- silently never gets the column: has_table("emails") is False
(per 0011_email_model_reconciliation's docstring, no managed database
ever really had a table literally named "emails"), so upgrade() returned
without touching email_records at all. Reproduced directly: migrated to
0009, dropped email_records.is_read to simulate that historical state,
continued to head with the has_table("emails")-only version -- it
completed with no error, but is_read was permanently missing. Confirmed
the same reproduction now correctly adds is_read to email_records.
2. Not idempotent: a legacy "emails" table that already has is_read (e.g.
from a partial/earlier application) made upgrade() crash with a
duplicate-column error, since it only checked has_table before calling
op.add_column. Reproduced directly (manually created an "emails" table
with is_read already present, migrated to head) and confirmed it no
longer crashes.
Now checks both "email_records" (the table that actually matters) and
"emails" (defensive, in case a real one somehow exists), guarded by column
existence via the same _has_column helper this repo's other migrations
already use, so upgrade/downgrade are safely idempotent either way.
This diverges from PR #1502's exact pinned file shape (its
test_email_read_state_legacy_table_guard_is_reversible asserted the
has_table-only version byte-for-byte), so updated this PR's own contract
test to check for the corrected shape instead of matching that exact text.
Worth flagging on #1502 too, since the same gaps apply to its own version
of this file if it hasn't already been fixed there.
Full backend suite: 1841 passed, 0 failed, 3 skipped, ruff clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ErwZSYW3pm585NiM3Q7aN
|
Exact-head governance audit confirmed the CodeRabbit issue comment was an approval-pending notice, not a finding: it says there are no unresolved comments and that the latest commit has not yet been reviewed. The prior parser matched the phrase “blocking issues” inside CodeRabbit’s hypothetical approval sentence and falsely published a blocker. Current head |
|
@coderabbitai review |
|
|
Correction: the exact pushed current-head SHA is |
…ing CodeRabbit notice Devin Review flagged a real regression in 154cff0: once a CodeRabbit approval-pending issue comment routes to add_waiting, it does so unconditionally — even when CODERABBIT_COUNT was 0 and the documented no-check-run fallback (a structured, exact-current-head OpenCode adversarial approval) had already been accepted. That fallback exists specifically so governance isn't stuck when CodeRabbit itself is what's unavailable; a CodeRabbit issue comment saying it hasn't reviewed yet is exactly that unavailability, not a new blocker. Tracks whether the fallback was accepted (OPENCODE_FALLBACK_APPROVED) and skips the approval-pending wait when it was. Added missing_coderabbit_adversarial_approval_with_pending_notice, combining a valid OpenCode fallback approval with a pending CodeRabbit notice, and asserting governance completes (conclusion=success) rather than waiting. Verified the fixed conditional directly against all 4 (OPENCODE_FALLBACK_APPROVED, CODERABBIT_APPROVAL_PENDING_COUNT) input combinations — only the previously-broken combination changes outcome, from "waiting" to "ready", matching intent. Could not execute scripts/ci/test_pr_governance_gate.sh itself in this sandbox pass (a persistent tool restriction on that specific script, unrelated to the change); the new scenario mocks and assertion follow the file's existing patterns exactly. Please re-run the harness locally to confirm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YgmaEKnPhrsbSyQBierFH
|
Devin Review's finding on Fixed in One caveat: I could not execute Generated by Claude Code |
|
Two items in the latest gate comment, neither needs another push: "Current-head CodeRabbit issue comment has blocking warning/failure evidence" — this is the same approval-pending CodeRabbit comment as before, and it's a false positive for a specific, expected reason: "Required check Nothing left to fix in the diff itself — still waiting on the queue to clear so real checks can run, and/or the branch-ruleset bypass. Generated by Claude Code |
|
Fresh overlap audit against #1503 shows this current branch is no longer a single bounded CI slice. #1502 still owns a unique, useful root capability — provisioning real The overlap is semantic, not title-only: both branches modify the historical email migration/bootstrap path and real-Postgres evidence, while #1503 now also carries the forward Please converge this external lane without rewriting another active branch: retain #1502 as the dependency-root CI-enablement slice only (Postgres service + the minimum executable contract/docs proving real-Postgres tests run), and move/drop any migration behavior already owned and more completely covered by #1503. The unrelated PR-governance/stack-trigger behavior should remain in its own existing owner lane rather than hitchhike on the Postgres service PR. After narrowing, regenerate exact-head CI/security/review evidence. Then #1503 can be restacked on the fresh #1502 head or protected |
|
Fresh post-extraction check: #1531 now exists at Please finish the split on this existing external branch: remove from #1502 every governance/stack-trigger delta now owned by #1531 and preserve only the real-Postgres CI enablement plus the minimum causally required executable migration/test/docs changes that are not supplied by its product/data successor. Then re-diff against #1503: any historical |
…on fix with #1503 Owner's request on this PR (2026-09-02): this branch had accumulated unrelated content over a long, CI-congestion-heavy session. Narrows it back to its stated purpose -- the dependency-root CI-enablement slice (Postgres service container + the minimum fix needed to make the newly real-executing tests pass) -- and converges the overlapping migration fix with #1503's independently-derived, more complete implementation so the two branches don't carry conflicting historical-migration semantics. Moved out entirely (zero diff vs develop now), extracted to #1531: - .github/workflows/{bandit,dependency-review,docker-publish}.yml and app-ci.yml's `pull_request: branches:` removal (stacked-PR base support) - tests/test_stacked_pr_workflow_contract.py - backend/tests/test_release_governance.py's stacked-PR assertions - scripts/ci/pr_governance_gate.sh + scripts/ci/test_pr_governance_gate.sh (CodeRabbit approval-pending + OpenCode fallback governance-gate fixes) Converged with #1503 (adopted its exact implementation, verified byte-identical via diff against its branch): - backend/alembic/versions/0011_email_read_state.py: #1503 independently found and fixed the same relation "emails" does not exist crash, more completely -- guards on column existence (not just table existence) and checks both "email_records" and legacy "emails", vs. this branch's simpler has_table-only guard. Adopting it here means #1503's later rebase onto this PR (once merged) is a clean no-op on this file instead of a conflict between two divergent fixes for the same bug. - backend/scripts/bootstrap_db.py: same convergence for execute_schema_backfill's legacy-index handling. - backend/tests/test_alembic_migrations.py: swapped this branch's test_email_read_state_legacy_table_guard_is_reversible (asserted the old implementation's exact shape) for #1503's test_email_read_state_guards_both_legacy_and_current_table_names, which asserts the now-shared implementation instead. backend/alembic/versions/0001_initial_control_plane.py, backend/tests/test_bootstrap_db.py, and backend/tests/test_data_api.py needed no changes -- diffed identically against #1503's branch already (both PRs independently reached the exact same is_read raw-SQL fix and 0001 refactor while investigating the same real-Postgres failures). Net result: 10 files changed vs. develop (down from 18), matching the "dependency-root CI-enablement slice" scope the owner asked for. Verification (real-Postgres re-run blocked by this sandbox having no usable Docker daemon -- ExpressJS-style hashes/lint/isolated-assertion checks substitute; the point of this very PR is to make GitHub's own CI runners do the authoritative check): - python3 -m py_compile on every touched Python file: clean. - python3 -m ruff check on every touched backend file: all checks passed. - backend/tests/test_alembic_migrations.py's three targeted test functions executed directly (bypassing this sandbox's unrelated conftest.py/cryptography import breakage): all 3 pass. - git diff against develop for every "moved out" file: zero lines, confirming a clean revert. - git diff against #1503's branch for the converged migration/bootstrap files: zero lines, confirming byte-identical adoption. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YgmaEKnPhrsbSyQBierFH
|
Narrowed per the request above. New exact head Moved out entirely (zero diff vs
Converged with
Verification: this sandbox has no usable Docker daemon (dockerd won't start — permission-restricted), so I couldn't re-run the real-Postgres suite here; that's exactly what this PR's own CI now does. Substituted: Once this merges to Claude-Session: https://claude.ai/code/session_019YgmaEKnPhrsbSyQBierFH Generated by Claude Code |
|
This is already done as of my previous comment (issuecomment-5503879697), which landed just before this one — head is now Separately: your and Devin's review on Claude-Session: https://claude.ai/code/session_019YgmaEKnPhrsbSyQBierFH Generated by Claude Code |
|
The reviewing model hallucinated a line number that isn't among this file's actual changed lines (nearest real changed lines: 7-11, +47 more elsewhere in the diff) — the fail-closed gate ( Generated by Claude Code Generated by Claude Code |
|
The re-run I triggered earlier on run Same signature just showed up on Generated by Claude Code Generated by Claude Code |
|
Fresh exact-head RCA on Repository-local Application CI, SAST Semgrep, Dependency Review, Bandit, Security Scan, and Docker validation are terminal-success on this head. The required That run used trusted central workflow source Do not churn this Naruon branch merely to retrigger the historical Noema implementation. This PR remains non-mergeable in the governance sense: |
There was a problem hiding this comment.
Pull request overview
OpenCode could not approve from deterministic current-head evidence because GitHub Checks have failed.
Findings
1. HIGH Current-head GitHub Checks - Fix failed required checks before approval
- Problem: Failed same-head checks remain for
c6ed2e6f9d4f8667d9003bdd2df1085f8a3f9aa5. - Root cause: The model-unavailable evidence fallback is allowed only when peer GitHub Checks are complete and clean.
- Fix: Read and fix the failed check logs below, then rerun the current-head checks.
- Regression test: Keep the model-unavailable fallback gated on an empty failed-check rollup.
Failed checks:
- Required Noema Review/noema-review: FAILURE (https://github.com/ContextualWisdomLab/naruon/actions/runs/33587140022/job/100251945105)
- Strix Security Scan/strix: CANCELLED (https://github.com/ContextualWisdomLab/naruon/actions/runs/33587139914/job/100113539187)
- Strix Security Scan/strix: cancelled (https://github.com/ContextualWisdomLab/naruon/actions/runs/33587139914/job/100113539187)
- noema-review check run: failure (https://github.com/ContextualWisdomLab/naruon/actions/runs/33587140022/job/100251945105)
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Workflow: app-ci.yml"]
S1 --> I1["GitHub Actions review job"]
I1 --> R1["Review risk: Workflow: app-ci.yml"]
R1 --> V1["actionlint plus required checks"]
Evidence --> S2["Repository file: AGENTS.md"]
S2 --> I2["repository behavior"]
I2 --> R2["Review risk: Repository file: AGENTS.md"]
R2 --> V2["required checks"]
Evidence --> S3["Repository file: CHANGELOG.md"]
S3 --> I3["repository behavior"]
I3 --> R3["Review risk: Repository file: CHANGELOG.md"]
R3 --> V3["required checks"]
Evidence --> S4["Repository file: CLAUDE.md"]
S4 --> I4["repository behavior"]
I4 --> R4["Review risk: Repository file: CLAUDE.md"]
R4 --> V4["required checks"]
Evidence --> S5["Backend: 0001_initial_control_plane.py (3 files)"]
S5 --> I5["API and service runtime"]
I5 --> R5["Review risk: Backend: 0001_initial_control_plane.py (3 files)"]
R5 --> V5["backend tests"]
Evidence --> S6["Test: test_alembic_migrations.py (3 files)"]
S6 --> I6["regression suite"]
I6 --> R6["Review risk: Test: test_alembic_migrations.py (3 files)"]
R6 --> V6["targeted test run"]
OpenCode Review Overview
|
Bind the pgvector CI service to the verified multi-architecture digest and keep the pin covered by release-governance tests. Signed-off-by: Seongho Bae <me@seonghobae.me>
|
Exact-head restack repair:
The failed frontend job This PR now inherits that prerequisite through an ordinary non-force merge and targets the prerequisite branch. Its effective eleven-file database/bootstrap delta is unchanged. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b9e324a91
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| inspector = sa.inspect(op.get_bind()) | ||
| for table_name in _CANDIDATE_TABLES: | ||
| if inspector.has_table(table_name) and not _has_column( | ||
| inspector, table_name, "is_read" | ||
| ): |
There was a problem hiding this comment.
Add a new revision for already-stamped databases
For any installation whose Alembic history already includes 0011_email_read_state—for example, a database bootstrapped and then stamped before adopting the managed migration path—command.upgrade(..., "head") will never execute this edited upgrade() again, so email_records.is_read remains absent and ORM reads or inserts can still fail. Keep this guard for fresh installs if needed, but also ship the guarded email_records addition in a new revision descending from the current head and verify upgrading a schema already stamped past 0011.
Useful? React with 👍 / 👎.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Non-force merge of PR #1565 into migration owner #1503. Exact synchronization exposed the undeclared TestClient dependency previously masked by an extra local package. Preserve both complete deltas and use #1565 as the direct prerequisite, superseding the historical #1502-before-#1503 proposal without closing either lane. Verified merged tree 793aadd with fresh and repeat Alembic upgrade to 0019_email_read_state_repair, 75 strict PostgreSQL/dependency tests, Ruff, and diff checks. Consumer propagation and hosted gates remain unverified. Co-Authored-By: Codex <noreply@openai.com> Signed-off-by: Seongho Bae <me@seonghobae.me>
Current repair finding — 2026-09-05
Exact head
0b9e324a91fd2148b2b2759cca875ac7d50c86a0. Restored to Draft because the already-stamped database finding still requires owner integration; this is not a queued-check blocker or a closure.A disposable PostgreSQL 16.15 + pgvector run of the current tree completed fresh and repeat Alembic upgrade to
0017_merge_newsdom_carddav_headsand 64 focused tests. This is supplemented-local evidence, not clean-lock, forward-repair, hosted-check, or release proof. The forward repair already belongs to #1503, now19d5860bc27e860acba940390f5792721cd99e5eon #1565, with exact-lock fresh/repeat migrations and 75 strict tests verified locally. Integrate that ancestry; do not copy another0019migration.Keep this PR's unique pinned CI PostgreSQL service/configuration/governance delta and its workflow prerequisite #1562 → #1502. #1562 is itself on #1531; its live head must be refreshed before restacking. The historical #1502-before-#1503 proposal is superseded by #1565 → #1503, so do not introduce a dependency cycle. Fresh current-head review, checks, and immutable protected integration remain required.
Historical description and receipts
Older readiness or source-head statements below are historical where inconsistent with the current repair finding.
Description
The
backendjob in.github/workflows/app-ci.ymlhad noservices:block, so every@pytest.mark.postgresreal-Postgres test connected to nothing and always silentlypytest.skip'd — every run, since this pattern was introduced. This was surfaced by running the suite for real against local PostgreSQL 16 + pgvector for the first time in a recent session (recorded as a follow-up inContextualWisdomLab/.github'sdocs/product-technical-gap-baseline.md, 2026-09-01 entry, and innaruon#1486's in-progress CHANGELOG entry).Adds a
pgvector/pgvector:pg16service container (test/test/test_db,pg_isreadyhealthcheck) with a matchingDATABASE_URL, which happens to matchtests/conftest.py's existing default exactly.Running the suite for real against local PostgreSQL 16 + pgvector surfaced two latent bugs beyond the CI wiring itself, both of which
#1503also independently hit and fixed while investigating the same real-Postgres failures:0011_email_read_statestill hardcoded the pre-rename legacyemailstable, soalembic upgrade headagainst a genuinely fresh database crashed withrelation "emails" does not exist.#1503's fix is more complete (guards on column existence, not just table existence, and handles bothemail_records/legacyemails) — adopted verbatim here (byte-identical, verified viagit diffagainst its branch) so the two PRs don't carry conflicting historical-migration semantics.INSERT INTO email_recordsintest_bootstrap_db.pyandtest_data_api.pyomittedis_read(Python-side ORM default only, no DB server default), so real Postgres rejected them withNotNullViolationError. Both PRs independently reached the identical fix (verified byte-identical).Scope note (2026-09-02): per an owner overlap-audit request, this PR was narrowed from an earlier, broader diff down to just this dependency-root CI-enablement slice. PR-governance/stacked-PR-trigger content that had accumulated here was extracted to
#1531. Full narrowing rationale: #1502 (comment)Also updates
CLAUDE.mdandAGENTS.mdso agents know this is now a hard CI gate (not best-effort), and closes the follow-up tracked inContextualWisdomLab/.github'sdocs/product-technical-gap-baseline.md.Fixes: none (CI infrastructure gap-closure + latent bugs it surfaced, not a product feature).
Type of change
Checklist:
CLAUDE.md,AGENTS.md,CHANGELOG.md)#1503's independent real-Postgres run, adopted here byte-identical)LIVE_BASE_URLlive-API smoke) against real PostgreSQL 16 + pgvector,ruff check .clean,alembic upgrade headandscripts/bootstrap_db.pyboth idempotent against a fresh database. (This sandbox's own Docker daemon is unavailable for a fresh re-run post-narrowing; substitutedpy_compile/ruff/isolated-assertion verification — see the narrowing comment linked above for exact commands.)Generated by Claude Code