Skip to content

R3a: breadth-to-ten live journey unlocks Compare@10 - #5

Merged
phibkro merged 9 commits into
agent/f3-catalogue-fixture-freefrom
agent/r3a-breadth-to-ten
Jul 23, 2026
Merged

R3a: breadth-to-ten live journey unlocks Compare@10#5
phibkro merged 9 commits into
agent/f3-catalogue-fixture-freefrom
agent/r3a-breadth-to-ten

Conversation

@phibkro

@phibkro phibkro commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Feature

Delivers the R3a breadth-to-ten journey on Cloudflare: live NTNU/DBH ingestion publishes 10 fixture-free programmes, preserves source/R2 provenance, and unlocks Compare at exactly 10 (with the 9-programme lock boundary covered). This stack also includes the deploy fixes and SOPS-backed credential wrapper used to put the API, ingest worker, and PWA live.

Experience it

Open the live PWA:

Drive the public API directly:

API=https://course-data-platform-api.philib-krogh-d23.workers.dev

# Returns HTTP 200 with 10 published programmes and compareEnabled=true.
curl --fail "$API/v1/programmes"

# Returns the live, fixture-free published course catalogue.
curl --fail "$API/v1/courses"

# Returns freshness/provenance status.
curl --fail "$API/v1/data-status"

# Compare two live programme versions; returns HTTP 200.
curl --fail --get "$API/v1/compare" \
  --data-urlencode 'leftProgrammeVersionId=no.ntnu:BBEV:2026:9c919e1b1187dc21' \
  --data-urlencode 'rightProgrammeVersionId=no.ntnu:BERGO:2026:5e97a6f7a40be0a6'

# Public protocol description.
curl --fail "$API/openapi"

The live ingest worker is https://course-data-platform-ingest.philib-krogh-d23.workers.dev/; mutation endpoints remain bearer-token gated.

Real vs deferred

Real: Cloudflare Workers/D1/R2/Queue deployment, 10 live-published programmes, Compare@10, and encrypted deploy credentials; deferred: custom domain and F4 error-body namespace.

phibkro and others added 9 commits July 22, 2026 21:00
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>
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>
@phibkro
phibkro merged commit 01d591f into agent/f3-catalogue-fixture-free Jul 23, 2026
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.

1 participant