Skip to content

Phase 1: Application spine (Reflex + reflex-local-auth + worker + admin auth) - #2

Merged
noelsaw1 merged 36 commits into
mainfrom
marathon/reflex-hybrid-analytics-starter-p1-2026-07-21
Jul 21, 2026
Merged

Phase 1: Application spine (Reflex + reflex-local-auth + worker + admin auth)#2
noelsaw1 merged 36 commits into
mainfrom
marathon/reflex-hybrid-analytics-starter-p1-2026-07-21

Conversation

@noelsaw1

@noelsaw1 noelsaw1 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 1 of the Reflex Hybrid Analytics Starter build plan — the application spine on top of Phase 0's proven PostgreSQL 16 + AGE + pgvector image (merged in PR #1). Ships:

  • uv workspace (apps/* + packages/*), shared config, SQLAlchemy session management, Alembic migrations.
  • Core relational models (UserProfile, Dataset, AnalysisJob, AuditEvent).
  • Reflex app shell with embedded /api/v1/{health,readiness,version} endpoints.
  • Worker skeleton + a mock analysis engine, wired end-to-end (dataset → job → worker → result).
  • reflex-local-auth integration: app-owned user_profiles table carrying role, admin/member/viewer authorization matrix, bootstrap-admin CLI, registration gated off by default.

Full Phase 1 exit gate (10/10 items) is met — see PROJECT/2-WORKING/PROJECT.md's Phase 1 section for the itemized checklist with commands and evidence for each. Independently reviewed and approved via a cli-driven relay with Agy (verdict Round 3: "The branch is safe and appropriate to merge into main as an alpha"; content preserved at a6635e2).

What changed in this session (beyond the marathon-driven Phase 1 code)

CI had never once passed on this branch before this session. Fixed, in order:

  1. Missing ruff/pyright dev dependencies (CI failed before reaching any check).
  2. 46 real E701/E702 ruff violations and 2 real pyright errors — one of which (auth_adapter.py's do_logout missing @rx.event, and create_user discarding require()'s return value) were live bugs, not just lint noise.
  3. reflex export had never once succeeded — wrong Reflex module convention (app_module_import missing) plus a missing working-directory: apps/web on the CI step.
  4. A real fresh-clone bootstrap bug: README.md said uv sync, but this uv workspace requires --all-packages or packages/core never installs — migrations failed immediately with ModuleNotFoundError on a genuine fresh clone.
  5. Found by opening this PR: .github/workflows/database-image.yml (Phase 0's compatibility check, pull_request-triggered) never ran uv sync --all-packages or alembic upgrade head — the exact same class of bug as Technical assessment — implementation state, defects, and test coverage (read-only review of main @ e1bb8c6) #4, but pre-existing and unrelated to this session's new tests. It never surfaced before because that workflow only triggers on pull_request/push-to-main, so it never ran against this branch until this PR opened. Even pre-existing tests/test_auth.py and tests/test_vertical_slice.py failed collection there. Fixed and verified against a truly fresh container (docker compose down -v + rebuild).

Then closed all 5 remaining Phase 1 exit-gate gaps with real, running tests (not just imports): tests/test_admin_bootstrap.py, tests/test_auth_sessions.py (real login/logout/disabled-user/role-resolution/orphan-profile behavior through reflex_local_auth's actual state classes, not reimplemented logic), tests/test_registration_gating.py.

Known, still-open gaps (not blockers for this alpha merge)

  • The full web+worker+redis Docker Compose stack has never been brought up together (only compose.test.yaml's postgres service has).
  • Redis wiring is declared but unverified.
  • No test yet drives the vertical slice through the actual Reflex UI (only the service/worker chain is proven end-to-end).

These are documented in PROJECT.md's Deliverables list as open, separate from the exit gate this PR closes.

Test plan

  • uv run ruff check apps packages migrations — clean
  • uv run pyright apps packages — 0 errors
  • uv run pytest -v — 16/16 pass (compose.test.yaml postgres up)
  • uv run alembic upgrade head — clean, 0001_application_spine (head)
  • uv run reflex run --backend-only + curl on all 3 API endpoints — correct responses
  • Fresh-clone dry run of README's documented steps, twice (once failing pre-fix, once passing post-fix)
  • All 3 real GitHub Actions PR checks green (quality ×2, compatibility)
  • Independent review: Agy, via relay, Approved

🤖 Generated with Claude Code

noelsaw1 and others added 30 commits July 21, 2026 07:42
Same "one phase at a time" pattern as Phase 0's MARATHON-p0.yaml.
Added a note to the p1 brief: extend Phase 0's existing pyproject.toml
into the uv workspace root rather than overwrite it, and keep its
tests/test_database_smoke.py 4/4 passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gitignoring these in the earlier cleanup broke marathon-drive.sh
itself: it commits its own relay file under phases/<name>--<id>/
RELAY.md as part of firing a phase, and refuses when that path is
gitignored (git add: "paths are ignored ... use -f"). Confirmed by
Phase 1's first fire attempt failing before any build turn started
(marathon-drive exit 1). Keep .tick/ ignored (genuinely per-device,
never committed by the driver); handle phases/relay-system/ cleanup
manually before a marathon branch merges to main instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First fire attempt escalated (exit 6, "off-lane edits") even though
every file Codex touched (30 total, checked via the turn transcript)
was semantically within apps/,packages/,migrations/,.github/,
docs/architecture/,scripts/. Root cause: the harness's containment
check (relay-turn-lib.sh rtl_in_allow) only matches an EXACT literal
path, or a directory git reports as entirely new/untracked in one
collapsed porcelain line -- it has no general prefix/glob matching.
.github/ and docs/architecture/ already had Phase 0 files
(database-image.yml, database.md), so new siblings there
(.github/workflows/ci.yaml, two ADR .md files) were reported by git as
INDIVIDUAL new-file entries, not a collapsed directory -- and neither
matched the bare .github/ / docs/architecture/ allowlist tokens.

Fixed by pinning exact expected paths instead of bare directories:
.github/workflows/ci.yml (PROJECT.md's own canonical name -- also
fixes a .yml/.yaml mismatch) and docs/architecture/decisions/ (a
brand-new subdirectory, so it collapses and matches directly -- also
the structurally correct ADR location per PROJECT.md's required
layout, not loose files under docs/architecture/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Same containment-allowlist class of bug as the .github//docs
architecture fix, missed on the first pass: scripts/ also already has
Phase 0 content (verify-extensions), so a new scripts/bootstrap-admin*
file there is reported by git as an individual new-file entry, not a
collapsed new directory, and fails the bare scripts/ allowlist token's
exact-match check.

Fixed architecturally, not just by allowlisting another exact path:
PROJECT.md's own bootstrap-admin invocation (`uv run starter-admin
create-initial-admin`) names a registered console-script entry point,
not a standalone script file -- so it belongs inside packages/core/
(fully open, brand-new scope) via [project.scripts], never under the
pre-populated scripts/ directory. Dropped scripts/ from Phase 1's
artifact scope entirely since nothing in the brief now needs it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codex's build turn produced a real, working uv workspace: apps/web
(Reflex + reflex-local-auth + FastAPI), apps/worker (ARQ), packages/core
(models, db, auth, config, services, analysis, worker, logging, CLI),
Alembic migration scaffolding, a full docker-compose stack (postgres +
redis + web + worker), a CI workflow, and 2 ADRs (worker choice, ARQ;
reflex-local-auth security review) numbered per PROJECT.md's own
pre-planned ADR scheme (0004, 0006).

Every marathon fire attempt escalated on a containment violation before
reaching the reviewer round (agy never reviewed this), so this landed
via direct hands-on verification, not an approved relay -- same
discipline as Phase 0's manual close-out, but Phase 1's scope is much
larger and this verification is intentionally BOUNDED, not exhaustive:

Verified:
- `uv sync --all-packages` resolves the whole workspace cleanly.
- starter_core, starter_web, starter_worker, and every starter_core
  submodule import without error.
- Migration files compile (py_compile) without a syntax error.
- Phase 0's own gate is unaffected: uv run pytest -m 'database or
  integration' -> still 4 passed after the pyproject.toml/uv.lock change.
- `starter-admin = "starter_core.cli:main"` is a registered
  [project.scripts] entry point, per the brief's redirect.

NOT verified (real gaps, not swept under the rug):
- migrations/alembic.ini is MISSING -- `alembic upgrade head` cannot
  run yet, so "migrations run" (an exit-gate criterion) is unmet.
- No fresh-clone bootstrap attempted.
- No health/readiness endpoint actually exercised.
- No auth flow (login/logout/bootstrap-admin/role-enforcement) actually
  run end-to-end -- only that the code imports.
- No mock-analysis vertical-slice job actually executed.
- Redis wiring in apps/worker/main.py not exercised against a live Redis.

Next: author migrations/alembic.ini, then work through the remaining
exit-gate criteria in briefs/p1-application-spine.md one at a time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
README replaces the "Coming soon" placeholder with a real stack/layout overview;
LICENSE is Apache 2.0 (c) Neochrome, Inc. Also marks the exact trigger in the
build plan (Phase 1 exit gate verified + merged to main) for when to rewrite the
README against actual repo contents and run /front-door + /shakedown before
calling onboarding solid.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… progress

Frontmatter/status table were stuck at "Phase 0 not yet started" despite Phase 0
being closed and merged to main. Updated the Status table, added a Status column
to the phase table of contents, and converted Phase 0 and Phase 1's deliverable/
exit-gate bullet lists to checkboxes reflecting what's actually verified vs. only
code-written — so a fresh agent can pick up Phase 1 from the unchecked items
instead of re-deriving the gap list (missing alembic.ini, no app-level tests, no
reviewer approval, vertical slice not exercised end-to-end).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Configured alembic.ini for psycopg
- Added reflex-local-auth tables to Alembic migration
- Registered localuser in SQLAlchemy metadata
- Added unit tests for auth and vertical slice
- Marked README.md as Alpha (Phase 1 Complete)
FRONTDOOR.md marked FD-01/02/03 as fixed but the fixes never landed:
README lacked the uv prereq + alembic upgrade head step, and pytest
required manually exporting DATABASE_URL. Verified: uv run pytest now
passes 5/5 out of the box via the documented steps.

Also fixes phase-status drift across README/ROADMAP/marathon docs —
README claimed "Phase 1 Complete" while PROJECT.md (source of truth)
records Phase 0 done/merged and Phase 1 in progress/unverified/unmerged.
ROADMAP.md's Phase 0 gate line was stale for the same reason.

Adds missing PDDA frontmatter, Status tables, and ROADMAP coverage
(roadmap_exempt for sub-briefs) to MARATHON-PLAN.md and its 7 phase
briefs, clearing all 25 errors from `utils/pdda/pdda.sh run` — the
plan's stale "Draft — not fired" status is corrected to reflect that
Phase 0 was fired and escalated (turn-timeout-or-hang).

Also lands the .claude/skills/pdda/SKILL.md path-anchor fix found by
a prior /shakedown run (utils/pdda/pdda.sh was invoked CWD-relative,
breaking outside the repo root).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
REPO_MAP.md and .aider/studio/repomap-cache.json are regenerated by
Aider Studio and already gitignored going forward; untrack them so
they stop showing as dirty.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.github/workflows/ci.yml runs `uv run ruff check` and `uv run pyright`
but neither was declared anywhere in the workspace — CI's last 3 runs
failed on "Failed to spawn: ruff: No such file or directory" before
any lint, type-check, or test evidence was produced.

Note: with the tools actually installed, ruff finds 46 real E701/E702
violations (compound one-liner statements, all in packages/core and
apps/*) and pyright finds 2 real type errors in auth_adapter.py. Not
fixed here — that's app-code changes beyond the dependency gap; left
for a separate pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n, export working-directory

- Split 46 E701/E702 compound one-liners (if/raise, semicolon-chained
  statements) across auth_adapter.py, migrations/env.py, 0001_application_spine.py,
  analysis.py, auth.py, cli.py, models.py, services.py, worker.py — cosmetic,
  no behavior change.
- auth_adapter.py: add missing @rx.event on do_logout (fixes pyright
  reportIncompatibleVariableOverride — parent's do_logout is @rx.event,
  the override wasn't); capture require()'s return in create_user instead of
  discarding it (fixes reportOptionalMemberAccess on principal.user_id, and
  was a real narrowing bug, not just a type-checker complaint).
- rxconfig.py: add app_module_import="starter_web.app" — Reflex's default
  convention expects starter_web/starter_web.py, but the app lives in
  src/starter_web/app.py (src-layout). Without this, `reflex export`/`reflex run`
  never worked at all: "Module starter_web.starter_web not found". This was
  never caught because no CI run ever got past the earlier ruff/pyright gap
  to reach this step.
- ci.yml: add working-directory: apps/web to the reflex export step —
  rxconfig.py lives there, not at repo root, so the step 404'd on
  "rxconfig.py not found" even with the module fix applied.
- apps/web/.gitignore, apps/web/reflex.lock/: first successful `reflex init`
  in this repo's history generated these; committing the lockfile for
  reproducible frontend builds (same rationale as uv.lock).

Verified locally: ruff check, pyright, pytest -m "not database", and
reflex export --frontend-only --no-zip (from apps/web) all pass clean.
PROJECT.md's Phase 1 exit gate said 0/9 items verified and alembic.ini was
missing. Both were stale: alembic.ini exists, and with CI now actually green
it was possible to run real commands instead of re-reading checkboxes.

Verified this pass, with commands recorded in PROJECT.md:
- alembic upgrade head / alembic current -> 0001_application_spine (head)
- reflex run --backend-only starts; /api/v1/{health,readiness,version} all
  respond correctly over real HTTP
- tests/test_vertical_slice.py passes (dataset -> job -> worker -> mock
  engine -> SUCCEEDED)
- uv run pytest -v: 7/7 passing

5 of 9 exit-gate items flip from unverified to verified. The 4 that remain
open are real and named explicitly: fresh-clone bootstrap, admin-bootstrap
idempotency, login/logout/disabled-user session tests, and
orphan-profile-resolves-unauthorized. No reviewer has approved Phase 1 --
that is still the actual blocker to merge, not the exit gate itself.

ROADMAP.md and CHANGELOG.md updated to match. pdda.sh run: all checks pass.
Real, running tests against a live Postgres session -- not just imports:

- tests/test_admin_bootstrap.py: starter-admin create-initial-admin succeeds
  once, is refused on a second run while an admin exists, and rejects a
  too-short password.
- tests/test_auth_sessions.py: login success + logout ends the session
  (asserted against localauthsession row count, not just is_authenticated),
  wrong password rejected, disabled user rejected with no session created,
  principal resolves the correct role after login, and a LocalUser with no
  user_profiles row resolves to unauthorized (principal is None) -- the
  orphan-profile gate PROJECT.md calls out explicitly.
- tests/test_registration_gating.py: the register route is absent from the
  compiled app when ALLOW_OPEN_REGISTRATION is off (the default); login
  route is present.

Both new integration files instantiate reflex_local_auth's real State
classes rather than re-implementing their logic: building a proper root
state tree via rx.State(_reflex_internal_init=True) + get_substate(...) so
inherited vars (auth_token) resolve through a real parent_state, instead of
the "'NoneType' object has no attribute 'auth_token'" crash a bare
AppAuthState()/LoginState() hits. rx.session()'s DB engine resolves through
reflex's own config (rxconfig.py), discoverable only with CWD=apps/web, so
both files briefly chdir there for the triggering import and restore CWD.

Also removed an unused `import os` in test_vertical_slice.py (unrelated,
noticed in passing -- tests/ isn't in CI's ruff/pyright scope so it was
never caught, but there's no reason to leave it).

16/16 tests pass, ruff and pyright both clean (CI scope: apps packages
migrations). Remaining exit-gate item: a real fresh-clone bootstrap dry run
(next commit).
CI Runner added 6 commits July 21, 2026 16:25
Fixed README.md: plain 'uv sync' doesn't install this uv workspace's
members (only --all-packages does), so migrations broke immediately on a
genuine fresh clone with ModuleNotFoundError: No module named 'starter_core'.
Confirmed the fix by re-cloning and running the documented steps verbatim
a second time: 12/12 database/integration tests pass.

Reconciled PROJECT.md/ROADMAP.md/CHANGELOG.md against the new test
evidence (see prior commit) and this fix. Also corrected a miscount in the
earlier same-day exit-gate update: it's 10 items, not 9.

pdda.sh run: all checks pass. Full suite: 16/16.
phases/ (marathon-drive.sh's own turn logs from this branch's 6 fires) and
relay-system/ (this session's p1-alpha-merge-qa relay thread + a stray
codex-turn log) are build-tooling transcripts, not application
deliverables -- same rationale as f1c89d6, and the documented policy in
.gitignore's own comment: "before merging a marathon branch, manually
git rm -r phases/ relay-system/".

PROJECT.md's pointer to the relay thread is updated to a permalink at the
last commit where the file existed (a6635e2), since the relative link
would otherwise 404 on main once this lands.
…ations

Discovered opening PR #2: this workflow only triggers on pull_request (and
push to main), so it never ran against this branch until the PR was
opened -- meaning this bug was pre-existing and unrelated to today's new
tests. uv run pytest implicitly syncs, but not with --all-packages, so
starter_core/reflex/reflex_local_auth were never installed; even
pre-existing tests/test_auth.py and tests/test_vertical_slice.py failed
collection here, not just the new auth/bootstrap tests.

Added uv sync --all-packages (matching ci.yml) and uv run alembic upgrade
head (the app-level tables test_vertical_slice.py and the new auth tests
need don't exist without it -- this workflow never ran migrations).

Verified locally against a truly fresh container (docker compose down -v,
rebuild, up) running the exact new step sequence: 12/12 database or
integration tests pass.
@noelsaw1
noelsaw1 merged commit 2bec0d3 into main Jul 21, 2026
3 checks passed
noelsaw1 pushed a commit that referenced this pull request Jul 22, 2026
Replaced the example block's placeholder values with the real release:
Phase 0 (Postgres 16 + AGE + pgvector) plus Phase 1 (Reflex app,
reflex-local-auth roles, worker, mock engine) -- exit gate 10/10 verified,
merged via PR #2, relay-approved.

Kept the QA-gate fields to the contract's exact Yes/No format
(PROJECT/PDDA.md's "RELEASES.md -- release ledger" section) rather than
the free-text detail from an earlier draft, which broke pdda.sh releases'
block parser. `pdda.sh releases`: 0 errors, 0 warnings.
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