Plans2026-09-09 · Implementation plan

Outlook automatic recovery — implementation plan

All seven slices implemented; final Astra and Opus finders CLEAN, no new actionable regression; NOT merged or deployed. Historical correction checkpoint 30dd5ad1, with subsequent corrections below. Metadata stays in-progress for lane acceptance pending. CI is not yet available and remains pending; NOT MERGE-READY. Clean reviews confer no merge/deploy authority. The design spec preserves acceptance and incident provenance; the durable verification report records exact evidence and limits.

Latest product/evidence checkpoint: 68452506, reported pushed by the user; 30dd5ad1 is historical only. No deployment or CI result is inferred. Final cleanup adds one existing-format changelog entry for this checkpoint, not product behavior.

Scope and invariant

Challenge → typed queue hint → guarded renewable-grant snapshot → ONE connector-outlook maintenance primitive → provider-proven two-folder coverage → ONE guarded recovery writer → bounded catch-up. Recovery admits authorized or unconfirmed requires_reauth; exact confirmed markers instead permit renewal-only. Pending/error also permit renewal-only active-row PATCH, never CREATE, authorize or catch up; revoked/archived excluded. Missing/dead/interaction-required user grants require action; transient/network/429/5xx/invalid_client app faults do not.

Review prerequisites

  1. Historical source/deletion reviews informed the plan, but Grok was subsequently deemed untrusted; Astra substitution was user-directed. Final Astra and Opus finders are CLEAN. Full reviews covered deletions and structural changes; the last finder pass covered only the final renewal predicate. Lane acceptance and CI remain pending.
  2. Dependency provenance: kwiss reports #400 merged at 7fa680c3; this branch is based on it. Historical private-branch integration fast-forwarded ONLY fix/outlook-automatic-recovery from f30b280b to 457741e3d024a1bdcea7e519293c56beee6f11f1, then the feat/connector-health head with 8 successful CI checks. Its former OPEN/MERGE-READY state is superseded and is not recovery CI evidence. Read-only source was /home/kwiss/workspace/north-os-connector-health. Public @workspace/db withConnectorStatusTransition is reused without copying or resetting authorized_at. No worker git actions.
  3. The slice briefs below retain their implementation instructions and original caps as historical acceptance criteria, not remaining unchecked work. Current contracts and residual limits supersede stale baseline descriptions; no new resource resolver, durable lease/schema or general auth framework.

Bounded implementation slices

All seven slices below are implemented. Original caps counted authored additions plus deletions, including tests, excluding generated output; they remain historical stop limits, not a retrospective size certification. Slice checks mean implementation, not final review/CI approval. “New” paths describe the original brief.

1. Lifecycle and queue adapter — ≤4 files / 360 lines

2. Guarded auth outcomes and credential persistence — ≤10 files / 1,250 lines

3. Single provider-proven maintenance primitive — ≤6 files / 1,150 lines

4. Thin worker convergence adapter — ≤2 files / 650 lines

5. Periodic discovery and renewal cutover — ≤5 files / 800 lines

6. API demand-recovery adapter — ≤7 files / 600 lines

7. Truthful Outlook status on existing surfaces — ≤12 files / 950 lines

Regression-first acceptance matrix

Supplied preimplementation baseline: 43 tests (7 lifecycle, 19 processor, 17 renewal) passed old behavior, not recovery. Matrix below retains acceptance criteria; current execution evidence and its boundaries are recorded separately, not inferred from this baseline.

ScenarioObservable proof required
Exact reauthorizationRequiredNo web requires_reauth write; maintenance actually PATCHes provider; requires_reauth CAS only after both folders succeed.
Two / one / zero folders; expired activeRequired coverage established independently; no duplicate healthy folder; expired flag cannot bypass work; partial failure cannot authorize.
Graph PATCH 404; subscriptionRemovedMatched row retired, missing folder recreated and bounded catch-up exercised without personal downgrade.
FJ-shaped hypothetical: revoked + future expiry + remote aliveValidate custody, PATCH known ID; 200 can prove coverage without CREATE. Not a claim about Fjalloh's actual Graph state.
Already-revoked row revoked again during PATCHChanged last_renewed_at defeats reactivation despite unchanged status/updated_at; preserve audit row.
Requires_reauth with no subscriptionsPeriodic discovery reaches worker; usable grant recovers, missing/dead grant never authorizes.
Scheduled stale-activeReal delta/importRecent path runs even with two existing rows; lookback/cursor limits unchanged.
429 / 5xx / network / invalid_client / missing configNo new personal reconnect status; bounded retry/operator outcome; no false success. invalid_client caught even when wrapped in broad revoked exception class.
Cached bearer rejected; missing/interaction grantPATCH AND CREATE rejection stay typed after rollback; one refresh across the attempt, all three cache paths reject only that bearer with scope checks. Actual missing-row sources/absent refresh are typed; decryption/config failures never become missing consent.
Disconnect/reconnect and credential replacement racesRefresh replay cannot resurrect deleted tokens or overwrite concurrent OAuth replacement before connector bump. Existing requires_reauth reconnect stages pending before OAuth; stale Graph/local/final-CAS work loses; cleanup targets only its own IDs.
Negative credential racesOld-grant failure after OAuth token replacement but before connector bump cannot downgrade or change reason. Missing row filled before negative write also rejects stale absence, including reason-only.
Late event, retention and lease lossFresh-ID event after active delta schedules later work; retained jobs never block it. Shared serialization, owner-checked Lua release and actual provider/token fetch abort; no stale commit after lease loss.
CAS, row revocation and health historyConnector then deterministic locks on both exact post-write subscription generations before CAS, even when revocation did not bump connector. Exact microsecond strings; authorized_at unchanged, monotonic generation; loser none, winner one same-tx transition; history failure rolls back. Reason-only writes emit no false transition.
Identity and renewal-only guardsWrong organization/owner cannot use provider or mutate status; revoked/archived blocked. Pending/error active subscription PATCH remains observable, without CREATE, authorization or delta. Existing narrow refresh anchors retained.
Real UI outcomesMissing-grant detection → reason-only write → real gate/settings/onboarding reconnect action. Transient/unknown requires_reauth, including failed-query Try again, has no OAuth form; confirmed markers survive transients, successful recovery clears only owned markers and authorized reload redirects.

Explicit deletions and replacements for adversarial review

Durable review dispositions

Accepted historical corrections (1–3, 5–11, 13): separate renewal-only admission; connector-before-two-subscription final locks; credential write/snapshot fencing; one in-memory rejected-bearer refresh across all cache paths; provider/token AbortSignal propagation and atomic Lua ownership operations (not atomic Redis/SQL); typed CREATE cause/rollback; renewable grant and source-typed absence; fresh queue IDs; exact well-known resource parsing; additive authorized discovery/404 retirement; truthful three-surface UI with guarded reason-only writer. These replace conflicting instructions above, not an additional parallel architecture. Historical Grok assertions are not final review authority.

Rejected/clarified: (4) ordinary requires_reauth reconnect already calls reopenConnectorIfTerminal before beginOutlookConnect in actions.ts:494–510, staging pending/updated_at; no redundant staging, keep its race regression. This does NOT fix refresh token-write races. (11) possible loss of authorized discovery is a clarification to preserve that capability, not an approved deletion. (12) OAuth lookback is 60 days, not the old instruction; worker fallback stays one day. Grok's 70-minute lifetime claim is wrong: default is 69.5 hours. Earlier “no external assets” claim was false: both docs contained Typekit and CDN tags; remove them without changing the global generator.

Accepted follow-up (Astra): negative credential fencing and known non-active reconciliation, including repeated-revoke fencing; regressions above. Existing architecture/caps unchanged.

Original implementation review dispositions (supplied): Astra's original eight findings fixed, including nine renewal-fixture failures. Accepted history-poisoning, negative-after-self-refresh, scheduling and dedup findings fixed. Unconfirmed OAuth CTA rejected because the user expressly forbids it. v1 renewal/lazy-backfill race rejected: crypto-v2 explicitly throws for v1; it is not a supported runtime. Malformed-ciphertext InfrastructureError is intentional, including cross-provider impact.

Final corrections verified clean: fresh Astra's three findings—acknowledged lease expiry, stale orphan retirement CAS and final-completion CREATE cleanup—fixed. Opus 5's long catch-up budget, missing-refresh distinction and stale comments fixed. Final Opus low found active confirmed-grant rows starving renewal; latest two-file correction uses exact canonical NULL-safe SQL before LIMIT, with actual-SQL SQLite batch-1 regression failing pre-fix and passing post-fix. Final Astra and Opus finders both CLEAN, no new actionable regression. Full reviews examined deletions and structural changes; last finder scoped only to the final predicate.

Accepted tradeoffs and deletions: fresh demand-token enqueue volume accepted; no coalescer losing late work. Obsolete patchConnectorStatus and source-pinned tests deleted; actual password/middleware guards unchanged. Recovery errorCodes intentionally map to other; no ledger/alert-policy change. Per-event DB fence cost accepted; per-page alternative rejected. These are not unresolved findings.

Implemented correction contracts and proof

Source-inspected: consumed-credential outcomes retain snapshot/context; renewal negatives use the shared writer; disconnect locks connector before token. Token persistence checks renewed lease/deadline around transaction lock waits/writes. packages/connector-outlook/src/subscriptions.ts:137–215,299–338 fences acknowledged lease expiry and keeps final CAS plus a separate five-minute post-authorization catch-up budget under the same renewed lease. Final-completion failures reach independent five-second own-CREATE rollback (:740–806). Orphan CAS proves continued absence and exact scanned fields, including NULL-safe last_renewed_at (packages/connector-auth/src/tokens/outlook-recovery.ts:180–253); missing refresh on a present row is not an orphan. Renewal SQL excludes exact confirmed reasons before LIMIT (apps/worker-mail/src/loops/outlook-subscription-renewal.ts:280–301). Pre-emit generation fences, bounded custody and stale-active dedup remain intact.

Supplied results: root lint passed, existing warnings/no errors; root typecheck 64/64 tasks plus scripts passed. Auth 122, connector-outlook 80, core 68, provider 108 + 1 skip, chassis 80 + 1 skip. Worker combined four-file run at 20:40 UTC: 4 files passed, 112 passed + 1 opt-in SQL skipped (113); replaces older 102 summary, not a whole-package total. Earlier focused renewal+processor 96 passed, separate scope. API 518 + 5 opt-in DB skips; web 223 earlier + 27 webhook-focused. Disposable SQL 18 passed/0 skipped after 113 migrations/provision; five lifecycle-flow scenarios with dependency doubles passed. Separate scheduler SQL 4 passed/0 skipped is not added to worker totals. Exact combined command is in the durable report; no rerun.

Separate scheduler SQL target: disposable 127.0.0.1:5440/app_outlook_recovery_verify_scheduler_20260909, created from template0, migrated and provisioned with login app. Actual queries passed the status × reason × coverage sweep. Exact-target validation with OUTLOOK_RECOVERY_SQL_TEST_URL absent: 3 passed, 1 skipped; eight invalid explicit URLs threw before connect. Cleanup removed the target database and two scripts totaling 108 lines; cluster roles, membership and global settings unchanged. Supplied evidence, not rerun by the docs worker.

Provenance: actual Opus 5 after Fable unavailable fallback (degraded:fable); Fable spend acknowledged, no amount or exact Opus cost available here. Astra substitution was user-directed after Grok was deemed untrusted. Main implementation high effort; small-fix writer medium and final finder low per workflow. Earlier token-read-comment/turbo mechanical changes are historical worker work; this final alignment edits only the three recovery docs and changelog, with no new behavior.

Limits: >1,000 history unresolved; running importRecent transport not cancelled but late emits fenced; Redis/SQL final-hook-to-COMMIT not atomic. No browser visual test: ss found no :5140 listener, no server started. No real Graph, session or live writes. Completed smoke cleanup removed the disposable database, two SQL scripts and two /tmp flow files. Exact boundaries and dispositions: durable report.

Verification and delivery gates

  1. Run configured bun run test from web, worker-chassis, connector-outlook, connector-auth, provider, connectors-core, worker-mail and connectors-api directories. Focus via each package's Vitest script, never Bun's own runner on Vitest files. Include subscriptions.test.ts, all refresh-*.test.ts, outlook-access-token.test.ts, both scheduled-resync locations and the named UI rendering/OAuth/actions/gate suites; export/dep-isolation guards cover the new browser-safe subpath.
  2. Exercise queue retention/late events and Graph/token interleavings with deterministic isolated fakes/Redis namespace. DB races require a disposable migrated/provisioned database under proper RLS roles per AGENTS.md, never shared dev/preprod, real sessions or endpoints. Render actual UI surfaces, then use ui-test on an authorized isolated local runtime; no dev-server launch or live mutations by a worker.
  3. Keep regression tests only for plausible observable failures in the matrix. Shared-helper/API tests must assert outcomes, not field forwarding or source strings. A throwaway local smoke driver must exercise challenge→worker→provider test double→guarded status/delta end to end; no live Graph.
  4. Root bun run lint and bun run typecheck, all new focused regressions and affected suites green before PR. Run cheap eval only if actual retrieval/routing/memory behavior changes; this plan does not change those contracts.
  5. Two independent different-model adversarial reviews of implementation and deletions to convergence before PR. Worker reports commands/output and omissions; never commits. Orchestrator owns PR and CI-green gate. No merge/deploy without kwiss.

Open dependencies and stop conditions

#400 dependency is fulfilled on the user-reported merged base 7fa680c3. All seven recovery slices and subsequent corrections are implemented; final Astra and Opus finders CLEAN as scoped above. Metadata remains in-progress for lane acceptance pending; CI not yet available, pending, NOT MERGE-READY. Recovery is NOT merged/deployed; kwiss retains authority. Extra schema, durable lease/framework, alert-policy changes or over-cap work still require reviewed scope correction. Personal-owner paging remains prohibited. Residual limits are not claims of atomicity or mailbox delivery.

Generator caveat: future regeneration can reinsert Typekit/CDN assets; keep these documents self-contained.

Mailbox metadata and the completed operator watch remain in the design; neither proves current grant validity or mailbox delivery.