Repository source: docs/plans/app_sessions/app_sessions_step3_development_plan.md
Debug mode: enabled (debug log)
Effort study: railspike-effort / v3/app_sessions (ledger); standing enrollment, protocol P2
Extended estimates: enabled
Cycle estimate: 0.95
Status: Approved Step 3 (2026-11-03T02:37Z, maintainer: "Approved step 3."). Implementation on feat/app-sessions.
Completion Contract
Normal entry: sign in from Account, Lobby, Access history or the resume page, over http or https.
Outcome: the identity is recognized on every cookie-bearing request (including Deploy POSTs) for 400 days; sign-out ends it on the server; same-identity sign-in reuses the session.
Recovery: an old
PHPSESSIDis expired on first contact and a browser with a saved key signs back in silently; operators use Access history sign-in.External verification: real iPhone Safari over http after an https visit (maintainer); recorded as pending if not done before closeout.
Release: Stages 1–7 committed; focused suites pass; full suite failures match the
mainbaseline; HTTP round-trip and shadowing replay pass; zero dependency delta.
Key Risks
High risk: cutover signs everyone out at once. Impact: every member re-signs on next page load; cold POSTs fail once. Validate in Stage 6 with a browser holding a legacy cookie. Mitigation: rely on existing silent sign-in; say so in release notes.
Test conversion volume (12 files, about 120 sites). Validate in Stage 2 by converting the largest file first. Mitigation: behavior-neutral helper before the cutover, so only the helper changes in Stage 4.
SQLite contention from per-request writes. Validate in Stage 1 with concurrent requests. Mitigation: no write on read; "last seen" updated at most once per interval.
Session store reaching exports or backups. Validate in Stage 1 against archive, backup and checkout inputs. Mitigation:
state/private/(gitignored, not exported); store only token hashes.Stage 4 size (largest stage, mostly deletions). Validate by diff size before commit. Mitigation: if over about 150 changed non-deletion lines, split readers from sign-in before committing.
Stage 1
Estimate: 0.15
Goal: a tested viewer session store, resolver and per-scheme cookie, not yet wired.
Dependencies: none.
Expected changes:
ViewerSessionStoreatstate/private/viewer_sessions.sqlite3(path override registered inPrivateConfigSchema, not in the template): sessions (token hash, identity, lobby identity, scheme, created, last seen, expires) and single-use challenges;issue(identity, scheme),resolve(token, scheme),signOut(token),createChallenge(),consumeChallenge(value),purgeExpired(); injectable clock.ViewerSessionCookie:__Host-v3_session(https, Secure) andv3_session(http); https ignores the http cookie.Verification approach: unit tests for issue/resolve/expiry/sign-out/same-identity reuse/single-use challenges/scheme isolation; concurrent-request check; confirm archive, backup and checkout code never read
state/private/viewer_sessions*.Estimate details: driver: new store plus cookie rules; uncertainty: contention behavior 0.10–0.20.
Risks or open questions:
Impact: a wrong cookie rule repeats the shadowing bug. Early validation: scheme-isolation tests. Mitigation: names differ per scheme by construction.
Canonical components/API contracts touched:
PrivateConfigSchema(new path setting); existing*DatabaseConfigpattern reused.
Stage 2
Estimate: 0.10
Goal: one shared signed-in test helper;
LocalAppSmokeTestuses it with no behavior change.Dependencies: none (may run before Stage 1).
Expected changes:
tests/SupporthelpersignedInAs(identity)/signedOut()that still drives the current PHP session; replace the 49 direct$_SESSIONsites inLocalAppSmokeTest.Verification approach:
LocalAppSmokeTestresult identical to baseline.Estimate details: driver: mechanical replacement; uncertainty: child-script setups 0.07–0.15.
Risks or open questions:
Impact: the pilot shows conversion is not mechanical. Early validation: this stage. Mitigation: rescope Stage 3 before continuing.
Canonical components/API contracts touched: test support only.
Stage 3
Estimate: 0.10
Goal: every other test uses the helper; no test touches
$_SESSIONorsession_*except rotation tests due for deletion.Dependencies: Stage 2.
Expected changes: the remaining 11 PHP test files and
tests/browser/testing_self_approval.mjsfixtures.Verification approach: each converted file's result identical to baseline; grep shows no remaining direct session setup.
Estimate details: driver: 11 files of varied setup; uncertainty: child-process fixtures 0.07–0.15.
Risks or open questions: none beyond Stage 2's.
Canonical components/API contracts touched: test support only.
Stage 4
Estimate: 0.25
Goal: cutover; the app uses only the new store.
Dependencies: Stages 1–3.
Expected changes: resolver runs once at request start for any cookie-bearing request;
viewerIdentityId()/lobbyIdentityId()replace every$_SESSIONread (Application,RouteServices,DeployController,UserAccessController,IdentityHintController);AuthApiControlleruses store challenges, issues or reuses sessions and keeps response text unchanged; sign-out deletes the row and keeps the lobby identity; an incomingPHPSESSIDis expired. Removed:SessionRotation, start allowlist,shouldResumeViewerSession/resumeViewerSession, all PHP session calls,SessionRotationTest,session_rotation_http.mjs. The Stage 2 helper switches to issuing real store sessions and cookies.Verification approach: full PHP suite vs baseline; grep proves no
session_or$_SESSIONinsrc/; tests for Deploy POST signed in, idle past 24 minutes (clock), sign-out invalidating a copied cookie, same-identity reuse.Estimate details: driver: many call sites in one switch; uncertainty: lobby and sign-out interplay 0.20–0.35. Templates and
public/*.phphave no session reads (checked 2026-11-03).Risks or open questions:
Impact: a missed reader signs someone out. Early validation: grep
src/before editing. Mitigation: one accessor; grep gate in verification.Impact: static pages turn dynamic if a cookie is set for keyless readers. Early validation: static/high-traffic tests. Mitigation: cookies only at sign-in.
Canonical components/API contracts touched:
/api/auth_challenge,/api/authenticate_identity,/api/auth_status,/api/clear_identity(responses unchanged); static publication cookie checks (unchanged).
Stage 5
Estimate: 0.07
Goal: the testing Approve cookie no longer shadows across schemes.
Dependencies: Stage 1 cookie rules.
Expected changes: per-scheme name for the testing self-approval cookie, same rule as the session cookie.
Verification approach: testing self-approval PHP and browser tests; scheme-isolation unit test.
Estimate details: driver: small; uncertainty: browser test fixtures 0.05–0.10.
Risks or open questions: none.
Canonical components/API contracts touched:
RouteServices::testingSelfApprovalToken,testing_self_approval.js(unchanged).
Stage 6
Estimate: 0.15
Goal: replay the reported failures through real browsers and cookies.
Dependencies: Stages 4–5.
Expected changes: an HTTP test serving the app over http and over https (self-signed, same host, different ports, so cookies are shared as on the iPhone) using
php -Sand Node's built-in TLS; headless Chrome flows: https sign-in, then http sign-in succeeds and persists; browser with a legacy SecurePHPSESSIDsigns back in silently; Deploy POST after a long idle; sign-out then reuse of the old cookie fails.Verification approach: the replay passes and fails on
mainfor the shadowing and idle cases (proves the replay is faithful).Estimate details: driver: TLS harness with no new dependency; uncertainty: Chrome certificate flags 0.10–0.25.
Risks or open questions:
Impact: harness cannot reproduce shadowing. Early validation: run it on
mainfirst. Mitigation: record as unverified in Chromium; iPhone check remains the acceptance evidence either way.
Canonical components/API contracts touched:
private_site_auth.jsbackground sign-in and resume page (exercised, unchanged).
Stage 7
Estimate: 0.08
Goal: documentation, concept entry and closeout.
Dependencies: Stages 1–6.
Expected changes:
docs/reference/authentication_modes.mdsession section; status notes onsession_reauthentication_reference.mdandsession_rotation_race_decision.md; newdocs/concepts/viewer_session.md; iPhone check steps for the maintainer; closeout in the Step 4 summary and ledger.Verification approach: concept paths checked against code; links resolve; full suite compared to baseline; dependency delta zero.
Estimate details: driver: docs; uncertainty: low 0.05–0.10.
Risks or open questions: none.
Canonical components/API contracts touched: none.
Concepts touched
Viewer session (sign-in): created in Stage 7 (
docs/concepts/viewer_session.md).Static publication: used unchanged; verified in Stage 4.
Execution and study checkpoint
Step 3 handoff 2026-11-03 UTC: whole-cycle 0.95, revised from 0.90 (new understanding, same scope v1): tests need a behavior-neutral pre-pass and the replay needs a TLS harness. Under 1.0, so no split proposed. Stage work S1–S7 originals 0.15/0.10/0.10/0.25/0.07/0.15/0.08, independent annotations, not summed.
Baseline
e6493e2ff4869f8bf98e5f43b3f6388814742c26; R1 hashes verified at Step 2; P2. Claude (claude-opus-5-5), single exposed rater. No implementation, measured labor or accepted outcome. Awaiting Approved Step 3; records uncommitted.
— Claude · 2026-11-03 UTC · Model: claude-opus-5-5