You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Child of #7. Where indexed Cogs live and how they get there.
Problem
#7 requires a catalog populated by indexing the registry, surviving restarts, with installs pinned to digests. There is no store and no indexer.
Proposal
Store
Tables added through the collab_schema.py migration registry (advisory-locked, versioned), not a per-store CREATE TABLE IF NOT EXISTS (#42 explains why):
collab_cog_artifacts — one row per (source_id, repository, digest): source_id, host, repository, digest (PK with source+repo), tags text[], pushed_at, indexed_at, manifest_media_type, card jsonb (the reader's output, full profile preserved as structured JSON), cog_id (profile id, e.g. openteams/cog-media-transcriber), name, version, kind, publisher, manifest_schema, read_errors jsonb, removed_at (null while present in the registry).
Indexes on cog_id, kind, removed_at, and a GIN index on card for filtering by requires/provides/io.
Identity is the digest. cog_id/name are search keys; the repository path is not identity (Nebi-published repo names carry an id suffix, and one Cog may be published to several repos).
Indexer
cogs/indexer.py, a reconciliation loop:
For each configured source: enumerate repositories and artifacts.
Skip digests already indexed with the same tag set; upsert tag changes without refetching.
For new digests: fetch manifest, select COG.md + the profile file, read the card, insert. Artifacts with no COG.md are recorded as non-Cog with a reason, so a repo full of images does not get re-read every cycle.
Mark rows whose digest is no longer present as removed_at = now(); never hard-delete (installs and runs may reference them).
Per-artifact failures are stored in read_errors and do not abort the sweep; a sweep summary is logged and exported as metrics (indexed, skipped, failed, removed).
Runs at startup (after migrations) and on an interval (cogs.index.interval_seconds, default 300), and on demand from the webhook receiver (#86). Single-flight under a Postgres advisory lock so replicas do not sweep concurrently. Optional cogs.index.run_on_startup=false for tests.
Tests
Live-Postgres tests following the existing test_postgres_* pattern (also skipped-in-CI today, see The live database test suites are skipped in CI #56): insert/upsert/removal, tag change without refetch, GIN filter queries.
Indexer against the static adapter with mocked OCI: new artifact, unchanged artifact, retagged artifact, removed artifact, reader failure recorded and sweep continues.
Acceptance
After a sweep against the dev registry, every Cog in the cogs project has a row whose card matches the reader's output; a second sweep changes nothing.
Deleting an artifact in the registry marks the row removed on the next sweep; the row is still readable by digest.
Two replicas starting together do not double-index (verified with the advisory lock test pattern from collab_schema).
Migrations are appended to COLLAB_SCHEMA_MIGRATIONS; no new bare DDL.
Child of #7. Where indexed Cogs live and how they get there.
Problem
#7 requires a catalog populated by indexing the registry, surviving restarts, with installs pinned to digests. There is no store and no indexer.
Proposal
Store
Tables added through the
collab_schema.pymigration registry (advisory-locked, versioned), not a per-storeCREATE TABLE IF NOT EXISTS(#42 explains why):collab_cog_artifacts— one row per(source_id, repository, digest):source_id,host,repository,digest(PK with source+repo),tags text[],pushed_at,indexed_at,manifest_media_type,card jsonb(the reader's output, full profile preserved as structured JSON),cog_id(profileid, e.g.openteams/cog-media-transcriber),name,version,kind,publisher,manifest_schema,read_errors jsonb,removed_at(null while present in the registry).cog_id,kind,removed_at, and a GIN index oncardfor filtering byrequires/provides/io.cog_id/nameare search keys; the repository path is not identity (Nebi-published repo names carry an id suffix, and one Cog may be published to several repos).Indexer
cogs/indexer.py, a reconciliation loop:COG.md+ the profile file, read the card, insert. Artifacts with noCOG.mdare recorded as non-Cog with a reason, so a repo full of images does not get re-read every cycle.removed_at = now(); never hard-delete (installs and runs may reference them).read_errorsand do not abort the sweep; a sweep summary is logged and exported as metrics (indexed, skipped, failed, removed).Runs at startup (after migrations) and on an interval (
cogs.index.interval_seconds, default 300), and on demand from the webhook receiver (#86). Single-flight under a Postgres advisory lock so replicas do not sweep concurrently. Optionalcogs.index.run_on_startup=falsefor tests.Tests
test_postgres_*pattern (also skipped-in-CI today, see The live database test suites are skipped in CI #56): insert/upsert/removal, tag change without refetch, GIN filter queries.Acceptance
cogsproject has a row whosecardmatches the reader's output; a second sweep changes nothing.collab_schema).COLLAB_SCHEMA_MIGRATIONS; no new bare DDL.