Skip to content

fix(catalogue): gate courses on published snapshots - #4

Open
phibkro wants to merge 14 commits into
agent/f1-close-dbh-servingfrom
agent/f3-catalogue-fixture-free
Open

fix(catalogue): gate courses on published snapshots#4
phibkro wants to merge 14 commits into
agent/f1-close-dbh-servingfrom
agent/f3-catalogue-fixture-free

Conversation

@phibkro

@phibkro phibkro commented Jul 22, 2026

Copy link
Copy Markdown
Owner

F3: fixture-free, publication-gated course catalogue

The D1 course repository no longer has an unprovenanced NOT EXISTS passthrough. /v1/courses now returns only course versions in the current published dataset snapshot. Migration 0006 removes the illustrative 0001 fixture course/version seeds (and orphaned institution rows), while published programme snapshots remain visible.

Experience / verification

  1. Apply migrations to a clean local D1.
  2. Trigger or load a published programme revision using the existing live ingestion path.
  3. curl https://<api-host>/v1/courses
  4. Confirm the response contains published programme courses, and contains no source.provider = "fixture" rows.
  5. Remove the old 0001 fixture seed inserts (or apply migration 0006) and repeat the request; the published catalogue is unchanged.
  6. Inspect the repository query: every visible row joins dataset_revision_course_version to the current dataset_publication; there is no un-snapshotted passthrough.

Checks: bun run validate (format, lint, TS7+TS6, tests) green.

Real: the end-to-end path is D1-backed and publication-gated; no advisor gate or Cloudflare deployment was run.


Update (follow-up commits):

  • 4704cb8 — normalize the committed F3 spec whitespace so oxfmt --check (hence bun run validate) is green at the PR tip (falsifier R3a: breadth-to-ten live journey unlocks Compare@10 #5).
  • e64e427 — mechanize falsifier Add study planning kernel and roadmap projections #2: a test applies migration 0006 against the already-ingested D1, asserts fixture course_versions drop from >0 to 0, and that a re-fetched GET /v1/courses deep-equals the pre-cleanup response. Closes the coverage gap where the destructive DELETE migration shipped untested.

All five falsifiers now observed green on committed content; bun run validate = 36/36 tests, TS7+TS6 clean.

codex and others added 10 commits July 22, 2026 20:21
Committed F3 spec failed `oxfmt --check`, turning the PR tip red on
falsifier #5 (`bun run validate` green). Pure whitespace normalization
(double-space after periods → single); no contract/DoD change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The suite proved falsifiers #1/#3 (query gate serves zero fixture rows even
with seeds present) but never exercised migration 0006 — a destructive DELETE
shipped with zero coverage. Add assertions that apply 0006 against the
already-ingested DB: fixture course_versions go from >0 to 0, and a re-fetched
GET /v1/courses deep-equals the pre-cleanup response (removing the seeds
changes NOTHING served, and the migration touches no published course version).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Base of the stacked R3a branch (off F3 tip e64e427). Verbatim design-spec:
one live run ingests all ten NTNU_PROGRAMMES (real NTNU + DBH 347/208, fail
loud), reconciles + atomically publishes each; GET /v1/programmes then shows
programmeCount=10 / compareEnabled=true, each live-DBH-provenanced, catalogue
stays fixture-free, Compare unlocks at exactly 10 (9 locked). Per-programme
publish-vs-reject report is required F2 evidence. Quality gate UNCHANGED.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The R3a live breadth run surfaced that replicateNtnuCatalogue expected a
top-level JSON array, but the real all-studies endpoint returns
`{ q, numFound, docs: [...] }`. processNtnu fetches the catalogue
unconditionally (full mode does not even use it), so the shape mismatch threw
before any programme was reconciled. Read `.docs`, keep a bare-array fallback,
still fail loud on any other shape. No test covered this path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finding A from the R3a live run: migration 0006 (F3 fixture cleanup) deletes the
`no.ntnu` institution as collateral on a fresh DB — once the fixture courses are
gone nothing pins it, and the guard drops it. But `institutions` carries NO
source_provider (it is real reference data, not fixture provenance), and the live
reconcile path inserts courses/programmes under a NOT NULL FK to it while never
provisioning the institution itself. At the real deploy migration order
(0001..0007 before any run) every programme reconcile FK-failed.

0007 re-asserts NTNU as permanent reference data (idempotent INSERT OR IGNORE),
leaving reconcile and the atomic publication protocol UNCHANGED. The added test
applies the full 0001..0007 set then reconciles — the exact deploy order — and
fails if 0007 is dropped or 0006 ever deletes the institution again (verified:
red without 0007, green with it). This also closes the gap in F3's falsifier #2,
whose test happened to run 0006 after a reconcile (courses then pinned no.ntnu).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The suite proved the 10-programmes-unlocked side of the operator-locked
"exactly ten" Compare gate but never the lock side. Add the 9-programme
boundary: meta.compareEnabled === false and compareProgrammes fails
CompareUnavailableError(available 9, required 10). Guards against a regression
to `> 10` or `>= 9` that the unlock-only test would miss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pure formatting (double-space→single after periods; markdown emphasis
*x*→_x_; test whitespace). No contract or logic change. Keeps
`bun run validate` green at the PR tip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fold the conditional expect into a single toMatchObject on the whole Either
(oxlint vitest/no-conditional-expect). Same assertion, lint-clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
phibkro and others added 4 commits July 22, 2026 21:48
The real R3b deploy (first ever run) rejected the sync queue: Cloudflare
Queues require message_retention_period between 60 and 86400 seconds, but
alchemy.run.ts requested 345600 (4 days). tsc/local-compile never caught it
because no deploy had run. Cap at 86400 (24h), the max.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
R3a: breadth-to-ten live journey unlocks Compare@10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants