feat(trail): Postgres-backed snapshot store and source for trail - #16
Open
codemug wants to merge 4 commits into
Open
feat(trail): Postgres-backed snapshot store and source for trail#16codemug wants to merge 4 commits into
codemug wants to merge 4 commits into
Conversation
Tracked views land in the snapshot store rather than on local disk, so a view sits alongside the facts it was computed from. Frames are parquet bytes, not shredded columns: trail reads and writes whole frames, so a column-per-export schema would need a migration every time a model's exports change - which has happened twice already. write() upserts. Appending would silently double every tracked frame on the second build, and a doubled row count still looks plausible. Registered under trail.providers (the writeable view-store group), NOT trail.sources. Verified resolvable through trail's own resolver.
changed_since keys on published_at - the PIT clock - so a restatement, which arrives as a NEW row with a later published_at, re-dirties exactly the cells it should. Without that the view would never pick a correction up. Mirrors trail-fmp, which keys the same changefeed on filing date. trail already has the incremental engine; this feeds it rather than reimplementing it. Registered under trail.sources, NOT trail.providers - verified separated.
trail.yaml is a ConfigMap that lives in version control, so a password-bearing DSN cannot go in it. Options-first-then-environment is the precedence trail-fmp already uses for FMP_API_KEY; this follows it rather than inventing a scheme.
…ckages The working version of this file existed only as an uncommitted working-tree edit and was lost when local main was reset to origin/main (upstream untracked deploy/ in 1eb3b06). What remained on disk was the pre-ghcr version: vendoring trail-py and re-adding trail-edgar, which was deliberately removed after it aborted a full 5,368-name build on an unknown ticker. Reconstructed and verified against the running cache-fix-1 image before pushing: trail-lang 0.18.0, trail-fmp 0.3.0, trail-gmd 0.3.0, trail-scores 0.1.0, numpy 2.x, trail-edgar absent. Committed this time. Adds trail-snapshot-store (trail.providers) and trail-snapshot-source (trail.sources).
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.
Two new trail packages that let tracked views and facts live in a Postgres snapshot store, plus a reconstruction of the trail-mcp Dockerfile.
trail-snapshot-store— aViewStorebackendImplements trail's five-method
ViewStoreABC against Postgres. Registered asviews_pgunder thetrail.providersentry-point group.Frames are stored as parquet bytes rather than shredded into columns: trail reads and writes whole frames, so a column-per-export schema would buy nothing and would need a migration every time a model's exports change — which has already happened twice in this project.
write()upserts. Appending would silently double every tracked frame on the second build, and a doubled row count still looks plausible enough to go unnoticed.trail-snapshot-source— facts as panels, with a changefeedExposes
snapshot.factsas panel columns and implementschanged_since(cursor), keyed onpublished_at. A restatement arrives as a new row with a laterpublished_at, so it re-dirties exactly the cells it should — without that, a view would never pick a correction up.This mirrors
trail-fmp, which keys the same changefeed on filing date (trail_fmp/source.py:172). trail already has the incremental engine; these packages feed it rather than reimplementing it.Two entry-point groups, deliberately
trail-snapshot-storetrail.providerstrail/providers.py:17)trail-snapshot-sourcetrail.sourcestrail-fmpA store declared under
trail.sourcesresolves to nothing and trail silently falls back to a local-disk store — a deployment that looks healthy while writing views somewhere else entirely. Both groups are asserted in the tests.Note also that
trail/providers.py:store_for_configreadsconfig.providers.get("views")— a hardcoded key. The trail.yaml stanza must be namedviewswhatever the driver is.DSN handling
Both packages take the connection string from
options.dsnor theSNAPSHOT_DSNenvironment variable — the same precedencetrail-fmpuses forFMP_API_KEY.trail.yamlis a ConfigMap kept in version control, so a password-bearing DSN cannot live in it.Dockerfile reconstruction
deploy/Dockerfile.trail-mcphad drifted back to a pre-ghcr version: vendoringtrail-pyand re-addingtrail-edgar, which was deliberately removed after it aborted a full 5,368-name build by raisingCompanyNotFoundErroron an unknown ticker.The working version existed only as an uncommitted working-tree edit and was lost when local main was reset to origin/main (upstream untracked
deploy/in 1eb3b06). Reconstructed and verified against the running image before pushing — trail-lang 0.18.0, trail-fmp 0.3.0, trail-gmd 0.3.0, trail-scores 0.1.0, numpy 2.x,trail-edgarabsent — and committed this time so it cannot be lost again.Tests
Both suites run against a live Postgres carrying AGE + pgvector via
SNAPSHOT_TEST_DSN.Verified in the deployed cluster by mutation rather than status: renaming the view row in Postgres made trail report the new name, proving reads come from the database and not the stale
/viewsPVC that would otherwise mask a silent fallback.🤖 Generated with Claude Code