Repository navigation
feat(api): build/rebuild the resource-state read model tables - #331
Open
AlexAxthelm wants to merge 3 commits into
Open
AlexAxthelm wants to merge 3 commits into
AlexAxthelm wants to merge 3 commits into
Conversation
Compute each listable resource's rows for the four permission profiles from the same winner query the live list uses, and add the rebuild command: a full rebuild in committed batches behind the readiness flag, --if-stale for the post-deploy step (resumes where a failed run stopped), and --ids to repair specific resources. A full rebuild records the commit that built it, redoes rows computed by other code, checks the result before marking it ready, and holds a Postgres advisory lock so two can't overlap. Run the Postgres-only tests in CI. Nothing reads the table yet and API writes don't refresh it, so answers are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CD summary
|
| service | url | fqdn |
|---|---|---|
| api | open | pr-0331-api.purplegrass-c07d0a94.westus2.azurecontainerapps.io |
| frontend | https://witty-mushroom-017a3dc1e-331.westus2.1.azurestaticapps.net | |
| stitch-llm | open | pr-0331-llm.purplegrass-c07d0a94.westus2.azurecontainerapps.io |
Database (1)
| db_name | postgres_host | postgres_port | postgres_db |
|---|---|---|---|
| pr_0331 | stitch-dev.postgres.database.azure.com |
5432 |
pr_0331 |
Jobs (2)
| job | image | postgres_db | api_url | auth_mode |
|---|---|---|---|---|
| db-migrations | ghcr.io/rmi/stitch-api:pr-0331@sha256:b21dda2d10ced3cecc8eb60384880443d742309d6825a6eaafffe48aa2cad07e |
pr_0331 |
||
| seed | ghcr.io/rmi/stitch-seed:pr-0331@sha256:540765ef12649182cc4f1e534b5c6ff517a3e9edc5e5a9469dd1183d4f48476f |
https://pr-0331-api.purplegrass-c07d0a94.westus2.azurecontainerapps.io/api/v1 |
stitch-client-bearer-token |
Images (3)
| build_time | commit_time | git_sha | image | image_digest |
|---|---|---|---|---|
| 2026-10-15T10:49:17Z | 2026-10-15T10:48:54Z | b05a99a | ghcr.io/rmi/stitch-api:pr-0331 |
ghcr.io/rmi/stitch-api:pr-0331@sha256:b21dda2d10ced3cecc8eb60384880443d742309d6825a6eaafffe48aa2cad07e |
| 2026-10-15T10:49:12Z | 2026-10-15T10:48:54Z | b05a99a | ghcr.io/rmi/stitch-seed:pr-0331 |
ghcr.io/rmi/stitch-seed:pr-0331@sha256:540765ef12649182cc4f1e534b5c6ff517a3e9edc5e5a9469dd1183d4f48476f |
| 2026-10-15T10:49:11Z | 2026-10-15T10:48:54Z | b05a99a | ghcr.io/rmi/stitch-stitch-llm:pr-0331 |
ghcr.io/rmi/stitch-stitch-llm:pr-0331@sha256:0459b1a2a674030b65e8539388292bb270ab2fbca252f991ed470e5c493823d5 |
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Concurrent repairs can leave mixed-version rows marked ready, and CLI failures do not consistently follow the documented error handling.
2 open findings
What changed in this PR
Adds read-model population and rebuild support on top of #330’s schema. API reads and write paths remain unchanged.
Changes:
- Builds permission-scoped rows using the existing live coalescer.
- Adds batched rebuilds, resumable execution, targeted repairs, and readiness checks.
- Adds tests, PostgreSQL CI support, and operational documentation.
| File | Description |
|---|---|
deployments/api/tests/db/read_model/test_state.py |
Tests parity, refreshes, rebuilds, and CLI guards. |
deployments/api/tests/db/read_model/test_rebuild_lock_postgres.py |
Tests PostgreSQL rebuild locking and keepalives. |
deployments/api/tests/db/read_model/postgres.py |
Controls PostgreSQL test requirements. |
deployments/api/tests/db/read_model/dataset.py |
Provides representative resource fixtures. |
deployments/api/tests/db/read_model/conftest.py |
Adds rebuild and isolated PostgreSQL fixtures. |
deployments/api/src/stitch/api/db/read_model/state.py |
Implements row building, locking, and rebuild readiness. |
deployments/api/src/stitch/api/db/read_model/rebuild.py |
Adds rebuild and repair CLI commands. |
deployments/api/src/stitch/api/db/queries.py |
Exposes shared resource-listability query. |
deployments/api/READ_MODEL.md |
Documents rebuild operations and testing. |
.github/workflows/python-test.yml |
Supplies PostgreSQL for CI tests. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A full rebuild's integrity check couldn't see an --ids repair's uncommitted rows, so a repair from another image could commit between the check and readiness, leaving a table marked as built by one commit holding rows from another. Do the final check for rows from other code in the transaction that sets readiness, holding the status row exclusively (repairs hold it shared): a repair already running is waited for and its rows get another pass; one starting later waits, then refuses because other code built the table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the rebuild's expected failures print as a single ERROR line; unexpected ones (e.g. an unreachable database) keep their traceback, which is what you want for debugging them. Both exit with status 1; invalid arguments exit with status 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
Draft
This was referenced Oct 16, 2026
Closed
This branch was successfully deployed
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.


Second PR of splitting #324 into smaller PRs for ease of review. Note that this is the biggest of the batch, since it contains the core of the functionality, and tests for same checking against current behavior
Builds on #330, introducing mechanism to build rows in the table, and rebuild the full table. The individual-row builds will be used to keep resources up to date as they are operated on (merge, attach, etc), while rebuild is intended for initial population and maitenence. Importantly, because the git SHA is the cache key, they will need to be rebuilt on each re-deploy, but this will be automated in followup PRs. Using the code version as the cache key lets us lean on an automatable step, rather than tracking a separate invalidation key.
Uses the same query as the live coalescing route, but
INSERTs the result into the table, rather than reporting the results.Other notes: This PR depends on some Postgres-specific features, so the tests for them are Opt-in tests locally, since they require a real PG database to test against (all the other tests still work fine in SQLite), and there are changes to the CI python tests to run them on GH.
Summary
Second PR in the STIT-766 read-model stack (builds on #330). It adds the code that fills
og_field_resource_stateand the command to rebuild it. Nothing reads the table yet and API writes don't refresh it, so answers are unchanged.Building rows
coalesced_winner_rows, the same SQL ranking the live list uses, run once per profile with that profile's exact source set. There's no second ranking implementation, so stored rows can't drift from live answers.refresh_resource_states(session, ids)recomputes given resources inside the caller's transaction.read_model.refresh.resource_universe()inqueries.py(previously private) decides listability for both the live list and the read model, so they can't diverge.Rebuilding
python -m stitch.api.db.read_model.rebuild--batch-size, default 500).code_version.--if-stale(the post-deploy step): does nothing if the table is already built by this commit. Otherwise it recomputes only what isn't already current, so re-running after a failed or cancelled run picks up where it stopped.--ids 12 34: repairs specific resources and never changes readiness.--if-stalewaits up to 10 minutes, then re-checks. The lock's connection asks Postgres to drop vanished clients within about a minute, so a killed rebuild (e.g. a cancelled CI job) doesn't hold the lock for hours.ERRORline and exit with status 1.CI
python-test.ymlgets a throwaway Postgres service for the tests SQLite can't exercise (here: the rebuild lock). The workflow setsSTITCH_REQUIRE_POSTGRES_TESTS, so a missing database URL fails the run instead of silently skipping those tests.Docs
deployments/api/READ_MODEL.mdgains "Running a rebuild" (commands, where to run it, what happens, failures, a stuck lock) and "Tests" (how to run the Postgres tests locally).The stack
Related issues
Part of: STIT-766
Follow-up: STIT-809 (incremental
--sincerepair)Replaces: #324 (split for review)
Testing
tests/db/read_model/test_state.pycovers:--if-stale;tests/db/read_model/test_rebuild_lock_postgres.py(Postgres only):--if-stalewaits for a running rebuild, then rebuilds or skips; gives up after its limit; a full rebuild fails at once; keepalive settings on the lock's connection.make checkpasses.--if-staleskip;--idsrefusal and its "not ready" exception.GIT_SHA=pr2-check.--if-stalerebuilt 52,017 resources (208,068 rows, all stamped with that commit) in 90 s.--if-staledid nothing.--idswith an unknown id refused and changed nothing.Checklist
AI use: Claude Code split this out of #324 and wrote most of the code, tests and docs. I reviewed it before committing. Verified by the tests, the deliberate breakage check and the 50K run above.
🤖 Generated with Claude Code