6.2 KiB
6.2 KiB
| created | path | project | tags | type | |||||
|---|---|---|---|---|---|---|---|---|---|
| 2026-06-25 | Sources/Dev | petal-dispatch-phase-4-2-ingress-split |
|
session-notes |
Planning + recon session (no code changes). Mapped how the petal-dispatch-android client must change to ride the split surface (ingress LXC) instead of the fused service, and verified the app↔ingress API contract against the live phase-4.2/ingress-split branch (4002b17). Travis added a major direction change mid-session: at cutover the CF Access front-end is dropped in favor of his self-hosted Authentik layer.
Outcome
The app-side change to reach the split is smaller than feared. Verified findings:
- Transport is already split-compatible.
SseConsumeralready uses?from_seq=Nwith a live-only first connect and ignoresLast-Event-ID— exactly the ingress contract (harness_routesreuse). Host coupling is centralized in one constant (DispatchHttp.BASE_URL). - Contract investigation — four suspected gaps resolved (source-authoritative; route table grepped from
src/petal_ingress/app.py):POST /api/tasks/{session_id}/action— MISSING. The one real functional gap. No/actionhandler exists inpetal_ingress. The app's worker pause/stop/delete (DispatchApi.taskAction) has no backend. Matches thread-605's deferred "small admin endpoint, not tonight" note. Fix = DB air-gap pattern (ingress writes action to a queue, executor consumes).GET /api/threads?hide_archived=false— param ignored, non-breaking. Split derives threads fromthread_stateBIGSERIAL (no archived concept). "Show archived" toggle is inert, not an error.POST /api/threads/unassigned/activate— PRESENT (app.py:523). Not a gap.dispatch_session_idinPOST /api/chat— hardcodednull(app.py:658), but the app never reads it (ChatProjection.sendOnebranches ontargetThreadId). Benign.
- Design decision (Travis): the dispatch host becomes the app's first-ever setting, which dissolves the earlier Path A (DNS repoint) vs Path B (point app at split) fork.
- Current-state drift caught: loopback curls to
127.0.0.1:8001returned 401 on/api/*— the ingress app itself is auth-gating now (CF-oriented middleware live), NOT the_auth_noopthe checkpoints described.
Topics Covered
- Recon of plan 300 (security rationale: ingress/executor split, DB as air-gap) + thread 605 (Phases 1–4 complete, parallel-run live, Phase 5 cutover gated on Travis).
- Read the Android client end-to-end:
DispatchHttp,DispatchApi,SseConsumer,AuthManager,AndroidManifest. - Live + source contract verification against the running ingress on this host.
- The Authentik-replaces-CF-Access cascade and its (simplifying) effect on
AuthManager.
Key Learnings
- The URL-setting dissolves the host fork but NOT the auth/manifest coupling. A runtime
BASE_URLcleanly drives REST/SSE and derivesREDIRECT_URI+RESOURCE. But Android App Link<data android:host>is compile-time — autoVerify links are fixed in the APK and verified at install. So auth-capable hosts remain a bounded, pre-declared set. Recommended shape: makeBASE_URLthe setting; declare bothdispatch.herbylab.devandsplit.herbylab.devas App Links (each origin servesassetlinks.json); register both redirect URIs at DCR (currentlybuildDcrRequestBodysends a single-element array). Host change ⇒ re-auth (token is audience-scoped) + SSE reconnect (SseConsumer.restart()exists). - Dropping CF Access for Authentik DELETES app code rather than adding it. Most of
AuthManager's complexity exists only to paper over CF's non-standard OAuth: the hand-rolledparseDiscoveryConfig(CF omitsjwks_uri), the runtime DCR path (ensureClientId/buildDcrRequestBody/parseDcrClientId), the RFC 8707resourceindicator, and theoauth:bearer-prefix uncertainty. Authentik is a full OIDC provider → revert to AppAuth standard discovery, ship a static pre-registeredclient_id, drop the resource indicator (also removes one host-coupled auth constant). Unchanged: auth-code+PKCE flow, https redirect, manifest App Link + assetlinks, the Chromium Custom-Tab handoff workaround. Server side: ingress middleware swaps CF-assertion validation → Authentik JWT validation (issuer/audience/jwks). - Keep IdP discovery/client_id pinned in config, NOT free-text user settings — only the dispatch host should be user-facing. Pointing auth discovery at an arbitrary URL is a security smell.
- Code answers "what is"; vault/trellis answer "what we decided and why." (Self-correction from earlier in session: should run
list_projects/list_threadsscoped to a subsystem before reconstructing intent from source.)
Follow-ons
- OPEN DECISION (blocks sequencing the auth work): Authentik flow topology — does the app do auth-code+PKCE directly with Authentik (keeps a slimmed
AuthManager), or does Authentik sit as a Traefik forward-auth outpost and the app rides an edge-managed session (removes most ofAuthManager)? This fork decides how much auth code survives. - Build
POST /api/tasks/{session_id}/actionon ingress (DB air-gap queue → executor consumer) to restore worker lifecycle. - Add the dispatch-host setting (app's first settings screen):
BASE_URLconst → runtime read;DispatchApi.BASEdynamic; deriveREDIRECT_URI. - Manifest: declare both hosts as App Links; serve
assetlinks.jsonon each origin with the app's signing fingerprint. - Rewrite auth for Authentik: static
client_id, standard AppAuth OIDC discovery, drop RFC 8707 resource, simplify token handling. - Server: swap ingress in-app middleware from CF Access assertion → Authentik JWT validation (or forward-auth header trust), depending on topology decision above.
- Confirm before public exposure: ingress CF→Authentik cutover removes the
_auth_noophistory entirely; the loopback 401 shows in-app gating is already active. - Optional context-source improvement: there is almost certainly a dedicated vault plan + trellis thread for the Android client (it has
phase-4.1-cfauthversioning,docs/RECON.md,docs/HANDOFF-4b.md) — locate it before the build to avoid re-deriving intent. Thread 588 (android + web build, long) is the likely home.