Skip to content

Packaged API boots outside the repository root (OGUR-66)

Verdict: IN-MVP-1 · Estimate: 3 half-days · Status: DELIVERED — shipped in PR #240, released in v1.1.0 Serves gate: §1 MVP-1 — "follow an authenticated link": no link exists unless the API deploys, and §8 records it cannot boot from a wheel Scope contract: §2 item 2 — "One deployment, one URL, serving both packs"; §2 Checkpoints 1 — "packaged API boots outside the repository root" (due Aug 10) Date: 2026-08-10

Why this is in scope

This is a named §8 blocker on the deployment gate (OGUR-59, due Aug 11), and the scope contract's own checkpoint schedule lists "packaged API boots outside the repository root" as an Aug 10 deliverable. The wheel-boot proof from a temporary directory is the correct acceptance shape — §8 notes the defect is invisible when running from repo root, so any check that runs from repo root proves nothing. The stated scope restriction ("strictly to packaging") is consistent with §2's forecast warning that "any new feature, broad cleanup" misses Aug 13; hold the implementer to it.

Boundary: packaging, not pipeline correctness. If moving run_drug_briefing_pipeline tempts a refactor of the briefing pipeline itself, that is §3 territory — the move exists only so the import at ogur/api/routes/briefings.py:14 resolves from an installed wheel.

proposal: Move run_drug_briefing_pipeline into the ogur/ package, drop runtime archived_data dependence, and prove a built wheel boots create_app() from a temp directory — packaging only.
verdict: IN-MVP-1
milestone: MVP-1
scope_citation: '§2 item 2 "One deployment, one URL, serving both packs"; §2 Checkpoints 1 "packaged API boots outside the repository root"; §8 "API cannot boot from a wheel… Invisible today because uvicorn from repo root works" (ogur/api/routes/briefings.py:14; pyproject.toml:60)'
user_evidence: null
reversal_condition: 'A founder §9 cut or §6.1 amendment removing the one-URL deployment gate from MVP-1  nothing short of that; this is a named §8 blocker on a §2 line'
disposition: SPEC

What exists / what's new

Exactly one from scripts. import inside ogur/: ogur/api/routes/briefings.py:14run_drug_briefing_pipeline, used as a BackgroundTasks target at briefings.py:61. Routers import inside create_app() (ogur/api/app.py:22) and app.py:32 runs app = create_app() at module level, so the failure fires on import ogur.api.app.

archived_data runtime reads reachable from create_app(): only ogur/config.py:33-35 — the _REPO_ROOT-derived default for explore_report_pack_path (lazy per-request read; fails as 503 ReportPackUnavailable via report_pack.py:95-128, never blocks boot). In a wheel, _REPO_ROOT resolves into site-packages, so the default is already dead in the deployed case. EXPLORE_REPORT_PACK_PATH already overrides. ogur/engine/discovery_modality/cache.py:60 also derives from _REPO_ROOT but is not read at boot — out of scope.

What Where Reusable as-is?
The pipeline function itself scripts/briefing/generate_drug_briefing.py:38 yes — pure move, zero logic change
Multi-pack env override + 503 fail-closed ogur/api/report_pack.py:76-80, 95-128 yes — the archived_data fix is deleting a default, not building config
Sibling landscape pipeline (placement precedent) ogur/engine/pipeline.py::run_briefing_pipeline yes
Existing tests of the function tests/unit/engine/test_drug_briefing.py:16,98,150,171 needs import-path change + conftest.py:80 patch string
Genuinely new Where
ogur/engine/drug_briefing.py (moved code) new module in existing engine package
Wheel-boot smoke scripts/smoke/smoke_wheel_boot.sh
explore_report_pack_path default change ogur/config.py:33-35

Wheel ships ogur/ only (pyproject.toml:59-60); pack JSONs stay deployment artifacts configured via EXPLORE_REPORT_PACK_PATH, not package data.

Architectural seam & collisions

Move within the existing engine seam — scripts/briefing/generate_drug_briefing.py:38-136 imports only ogur.engine.* and ogur.store.* and mirrors pipeline.py::run_briefing_pipeline; a sibling module ogur/engine/drug_briefing.py keeps the diff reviewable. No new seam, no ADR.

Thin wrapper: scripts/briefing/generate_drug_briefing.py keeps its __main__ block (argv parsing, usage text) and module-level logging.basicConfig (must NOT move into ogur/), re-exporting run_drug_briefing_pipeline for back-compat (scripts/briefing/analyze_drug.py:85, scripts/smoke/smoke_drug_briefing.sh).

Patch-where-used ripple: tests/conftest.py:80 patch string scripts.briefing.generate_drug_briefing.get_sessionogur.engine.drug_briefing.get_session, else the in-memory-SQLite fixture silently stops covering the moved copy.

Collisions (checked gh pr list, git worktree list): no open PR touches briefings.py or report_pack.py. Stale April drafts (#82, #106, #66, #63,

30, #18, #129) overlap generate_drug_briefing.py / config.py /

pyproject.toml additively only. No sequencing blocker.

Surface contract

No surface impact. No route, page, component, or schema change. Invariants:

  1. Route paths and status codes unchanged (/briefing/{id}/drug/{name} 404s, POST .../generate 202).
  2. ogur/api/schemas.py untouched.
  3. §2.3 pack byte-identity unbroken — the config default change must not change which pack is served; meta.source_digests compare is the evidence.

No new claims displayed → no new <SourceChip> / <ConfidenceBadge> (the legitimate no-provenance case).

Acceptance criteria

# Criterion How it's checked Layer
1 Zero scripts imports under ogur/ rg -n 'from scripts\.\|^import scripts' ogur/ exits 1 grep in smoke
2 Built wheel imports the app off-repo-root smoke_wheel_boot.sh: uv build → fresh temp venv → pip install dist/ogur-*.whlcd "$TMPDIR"python -c "from ogur.api.app import app" exits 0 e2e smoke
3 Negative control — smoke env cannot lie Same venv/cwd: python -c "import scripts" exits non-zero e2e smoke
4 Booted app serves /health 200 Same env: TestClient(app)GET /health → 200 e2e smoke
5 Missing pack → 503, never 500, never blocks boot Same env, env -u EXPLORE_REPORT_PACK_PATH: report route → 503 ReportPackUnavailable; /health still 200; new default must not resolve into site-packages e2e smoke
6 Env override still wins In-repo with EXPLORE_REPORT_PACK_PATH set: report route → 200; existing explore-report tests green unmodified unit
7 §2.3 byte-identity tests/unit/api/test_report_pack_integrity.py::test_source_digests_match_the_artifacts_on_disk passes unmodified unit
8 Re-export is a move, not a copy New test: wrapper import is ogur.engine.drug_briefing.run_drug_briefing_pipeline (identity, not equality) unit
9 Pipeline tests follow the move test_drug_briefing.py:16 imports new module; conftest.py:80 patch string updated; full suite green at ~1,932 baseline unit
10 Route surface unchanged app.openapi() paths+status-codes diff before/after is empty; git diff --stat ogur/api/schemas.py empty unit + grep
11 Lint gate make lint (never make fmt) lint

Regression gap named by qa: nothing exercises trigger_drug_briefing's task target (briefings.py:56-62). Add one test patching ogur.api.routes.briefings.run_drug_briefing_pipeline (patch-where-used) and asserting the 202 path schedules it.

False positives

Looks like success Actually is Discriminator
Wheel smoke green from repo root cwd puts repo on sys.path; wheel completeness never tested Criterion 2's cd "$TMPDIR" + criterion 3's import scripts must-fail probe
Green via pip install -e . / reused venv .pth points back at the source tree Smoke installs dist/*.whl into a venv it creates; criterion 3 re-verifies
Report route 200 off-root "proving" the default fix EXPLORE_REPORT_PACK_PATH leaked from .env/shell (known repo trap) Criterion 5 runs with env -u and asserts 503, not 200
Re-export test green after copy-paste "move" Two divergeable copies Criterion 8 asserts is; criterion 1 asserts no scripts import remains
/health 200 read as "boot works" Lazy-import regression hiding behind an unhit route Criterion 5 exercises the report route, the path reading the changed default
Full suite green read as "trigger endpoint survived" No test exercises the task target The new patch-where-used test above

Human judgement required

Question Who decides Against what
Is smoke_wheel_boot.sh wired into CI or left a manual pre-deploy gate? Founder / tech-lead OGUR-59; §2 checkpoint 1 implies it runs at least once before Aug 11
Off-root default for explore_report_pack_path: empty (fail-closed 503) vs repo-root probe that keeps make dev working? Tech-lead §8 deployment shape (packs baked into image, path via env); criterion 5 must hold either way

Open questions

None blocking. Status READY.