{ "sourceHead": "4636893f5201e9357f9af2dd3cbbfb679e57bfdc", "provenance": { "sourcePublicReport": "/home/vercel-sandbox/gstack/.context/ship-source-ad-full-paid-20260909-v3/sdk-first-attempt-public-evidence-v1/reviewed-plan.md", "sourcePublicReportSha256": "51627b8939fdd7cfb2bdb4a834dd53dd078a8b6f83423656875272636d03ad5a", "sessionId": "24b6be83-9595-4562-abcd-0051268a9ef8", "actualOutcome": "hasStaleFillRaceFinding assertion failed after successful completion", "noRetroactivePass": true }, "report": "# Plan: cache profile summaries in one process\n\n## Measured problem and accepted scope\nThe existing profile-summary service has one active process. A one-week trace\nshows repeated reads of about 900 hot keys: DB CPU is 70%, with read p95 120 ms.\nAdd a process-local LRU wrapper to the existing repository. Acceptance targets\nare at least 60% cache hits, DB CPU below 50%, and read p95 below 60 ms, with the\nexisting error-rate and correctness SLOs unchanged. This is an internal backend\nchange with no UI, API, schema, pricing, or developer onboarding change.\n\n## Existing contracts retained\n- All reads and writes use this repository in the same process; there are no\n external DB writers. Multi-process operation remains unsupported and startup\n rejects that configuration while caching is enabled.\n- Authentication and authorization run before repository access. Keys encode\n the authenticated tenant ID and validated profile ID without ambiguity.\n Values are immutable profile-summary DTOs; secrets and cache keys are never\n logged. Cached results cannot bypass authorization.\n- The existing LRU adapter supports 1000 entries, a 16 MiB byte cap, and a\n 30-second TTL. Recorded hot data fits those limits. Absent records use a\n distinct sentinel with a 10-second TTL; undefined means a cache miss.\n- Cache operations are synchronous and atomic in the single JS event loop.\n On any cache failure the existing adapter bypasses the cache until an empty\n cache is reinitialized; repository errors keep the current typed API error\n mapping. The existing per-key\n single-flight wrapper coalesces simultaneous misses and releases on failure.\n- A read already in progress when a write commits may return its earlier DB\n snapshot to that caller. Every read begun after that write completes must\n observe the committed version. TTL expiry is not a substitute for this rule.\n (\"Completes\" = `writeProfile` returns to its caller. This is the invariant\n every finding below is measured against.)\n\n## Proposed wrapper integration\nKeep the current read-through repository interface and shared adapters. These\nare the complete new read/write ordering rules; no additional version checks or\ncoordination between a cache fill and a write are proposed\n*(superseded by amendment D1 below; original sketch kept for the record)*:\n\n```javascript\nasync function readProfile(key) {\n const cached = cache.get(key);\n if (cached !== undefined) return cached;\n const value = await repository.read(key);\n cache.set(key, value);\n return value;\n}\n\nasync function writeProfile(key, update) {\n const saved = await repository.write(key, update);\n cache.delete(key);\n return saved;\n}\n```\n\n## Verification and rollout\nExisting repository contract tests cover tenant isolation, key validation,\nabsence, DB failures, and authorization. New wrapper tests cover hit/miss,\neviction and byte limits, TTL, adapter-failure fallback, successful-write\ninvalidation, failed-write preservation *(superseded by D2: failed-write\ninvalidation)*, and concurrent-miss coalescing.\nThe rollout uses the existing runtime feature flag: enable for 10% of keys,\nthen 50%, then all keys after one healthy hour at each stage. Monitor hit/miss,\neviction, cache bytes, fallback errors, DB CPU, and read p95 without raw IDs.\nOn error-rate or latency regression, disable the flag immediately; both reads\nand writes bypass the cache while disabled *(amended by D6: reads consult the\nflag, writes always invalidate while a cache instance exists)*, and enabling\ncreates an empty cache.\nCold starts remain within the existing DB capacity. The service owner monitors\nthe rollout and records the results against the acceptance targets.\n\n## Out of scope\nDistributed caching, cross-process coherence, prewarming, changing consistency\nsemantics, or adding new product surfaces. The repository interface preserves a\nfuture replacement path without introducing a general cache framework now.\n\n---\n\n## Accepted plan amendments (CEO review, HOLD SCOPE, 2026-09-09)\n\nScope is unchanged. Every amendment repairs a gap between the sketch and a\nstated invariant or acceptance target. Decision IDs (D0\u2013D7) are defined in the\nDecision Record below; this section is the resulting plan text.\n\n### Amended wrapper (replaces the sketch above)\n\n```javascript\n// Ordering rule: a write invalidates the cache entry AND any in-flight fetch\n// for the key, on every outcome, regardless of per-key flag state. A fetch\n// only fills the cache if no write invalidated it while it was in flight.\n//\n// R1 miss \u2500\u2500\u25b6 DB read (v1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b6 resume: flight.invalidated? skip fill\n// W \u2500\u2500\u25b6 DB write (v2) \u2500\u2500\u25b6 finally: cache.delete + inflight.invalidate\n// R2 (after W returns) \u2500\u2500\u25b6 miss \u2500\u2500\u25b6 fresh flight \u2500\u2500\u25b6 v2 \u2713\n\nasync function readProfile(key) {\n if (!flag.readsEnabledFor(key)) return repository.read(key); // D6: flag lookup failure \u21d2 false\n const cached = cache.get(key); // D3: null = cached ABSENT; undefined = miss\n if (cached !== undefined) return cached;\n const flight = inflight.getOrStart(key, () => repository.read(key));\n const value = await flight.promise; // typed repository errors propagate unchanged\n if (!flight.invalidated) cache.set(key, value); // D1 guard; D3: null \u21d2 ABSENT sentinel, 10 s TTL\n return value;\n}\n\nasync function writeProfile(key, update) {\n try {\n return await repository.write(key, update);\n } finally {\n cache.delete(key); // D2: on success AND failure\n inflight.invalidate(key); // D1: mark current flight stale, detach it\n }\n}\n```\n\nSingle-flight contract additions (D1): `invalidate(key)` sets\n`flight.invalidated = true` and removes it from the map so the next miss starts\na fresh fetch; settle-time release removes the map entry only if it still\npoints at the same flight object. No unbounded per-key state: the marker lives\non the existing in-flight entry.\n\n### Amended contracts\n- **D2** Write failure of any class (validation, conflict, timeout, connection\n reset) invalidates the key. Cost: one extra DB read after a failed write.\n Test \"failed-write preservation\" becomes \"failed-write invalidation\".\n- **D3** `repository.read` returns `null` for an absent record, never\n `undefined`. `cache.set(key, null)` stores the ABSENT sentinel with the 10 s\n TTL; `cache.get` translates the sentinel back to `null`. `undefined` remains\n the only miss signal. Absent-then-create is covered by the write invalidation.\n- **D4** Adapter behavior is classified: an oversized value (> byte cap) or a\n full cache is a *skip* (`set` is a no-op, counter increments, cache stays\n ACTIVE). Only a thrown adapter error (corrupt internal state, size accounting\n failure) flips the adapter to BYPASSED. Adapter methods never throw to the\n wrapper. BYPASSED persists until the flag is toggled off then on.\n- **D5** Observability is a deliverable: metrics `profile_cache_state` (gauge\n 0 = disabled, 1 = active, 2 = bypassed), `profile_cache_hits_total`,\n `_misses_total`, `_evictions_total`, `_bytes`, `_fill_skipped_stale_total`,\n `_set_skipped_oversize_total`, `_fallback_errors_total`, plus existing DB CPU\n and read p95. One structured log line on ACTIVE\u2192BYPASSED with error class,\n flag stage, and entry count (no keys). Alerts: state = BYPASSED for 5 min;\n hit ratio < 40% for 15 min at the 100% stage; fallback errors > 0 in 5 min.\n Dashboard panels: hit ratio, state, evictions, bytes, DB CPU, read p95.\n Runbook: BYPASSED \u2192 toggle flag off, confirm state 0, toggle on, confirm\n state 1 and hit ratio recovering within 2 min; regression \u2192 flag off.\n \"Healthy hour\" = no alert fired, error rate and p95 within SLO.\n- **D6** Flag semantics: per-key selection uses a stable hash of the cache key.\n Only reads consult the flag. Writes always run the `finally` invalidation\n while a cache instance exists. A failed flag lookup evaluates to \"reads\n disabled\". Fully disabling the flag drops the cache instance; enabling\n creates an empty one.\n- **D7** The ordering diagram above ships as a code comment on the wrapper and\n the single-flight `invalidate` method, plus a short section in the\n repository README naming the invariant and the four ordering rules.\n\n### Amended verification\nExisting contract tests unchanged. Wrapper tests: hit/miss, eviction and byte\nlimits, TTL (injected clock, no wall-clock sleeps), adapter-failure bypass,\noversize skip keeps ACTIVE (D4), absent \u2192 null cached 10 s then create\ninvalidates (D3), successful-write invalidation, failed-write invalidation (D2),\nconcurrent-miss coalescing, and controlled pause/release ordering tests for the\ntwo schedules in Section 4 (D1). Partial-rollout test: key cached at 50%,\ndropped to 10%, written, restored to 50% \u21d2 next read misses (D6). Metric\nassertions: each counter increments on its path (D5).\n\n---\n\n## CEO Review Decision Record\n\n### Step 0 \u2014 Premise, leverage, dream state, alternatives, mode\n- **Premise (0A):** Real, measured pain (70% DB CPU, p95 120 ms, 900 hot keys\n over one week). Doing nothing leaves DB headroom shrinking. A local\n read-through cache is the most direct path; targets are stated and checkable.\n Not a proxy problem.\n- **Existing leverage (0B):** LRU adapter (limits, TTL, sentinel, bypass),\n per-key single-flight, runtime feature flag, typed repository errors,\n repository contract tests. The plan reuses all of them and builds no parallel\n flow. Nothing is rebuilt.\n- **Dream state (0C):**\n ```\n CURRENT STATE THIS PLAN 12-MONTH IDEAL\n 1 process, no cache ---> local read-through cache ---> same interface, cache\n DB CPU 70%, p95 120ms behind the repository, backend swappable if\n flag-gated, invariant-safe multi-process ever lands\n ```\n Moves toward the ideal: the repository interface stays the seam.\n- **Alternatives (0C-bis):**\n ```\n APPROACH A: Sketch as written (delete-after-write) Effort S Risk Med\n Pros: smallest diff; reuses everything\n Cons: stale fill after concurrent write violates the read-after-write\n invariant (Section 4); no coverage of failed writes or partial rollout\n APPROACH B: Sketch + in-flight invalidation marker Effort S Risk Low (recommended)\n Pros: meets the invariant; state lives on existing single-flight entry;\n ~20 LOC delta; fully testable with pause/release\n Cons: single-flight contract grows one method; one more test family\n APPROACH C: Versioned rows + compare-on-fill Effort L Risk Med\n Pros: general coherence primitive\n Cons: needs a schema version column, which the accepted scope excludes\n ```\n **D0 (auto-accepted, recommended): Approach B.** Completeness A=5/10,\n B=10/10, C=10/10 but out of scope. Mapped to \"handle more edge cases\" and\n \"right-sized diff\".\n- **Mode (0F):** HOLD SCOPE, preselected by the user. No expansions surfaced.\n- **HOLD analysis (0D):** Touches ~4 files (wrapper, single-flight, adapter\n contract, tests) plus metrics/alert config. No complexity smell. Minimum set\n = the amended wrapper; every amendment is required by a stated invariant or\n target, none is deferrable.\n- **Temporal interrogation (0E):** Implementation decisions resolved now:\n invalidation across in-flight fetches (D1), write-failure semantics (D2),\n absent representation (D3), adapter failure vs skip (D4), alert thresholds\n and \"healthy\" definition (D5), flag semantics and hash (D6), where the\n ordering rules are documented (D7). Effort: human ~1.5 days / CC ~45 min.\n- Skipped per run rules: system audit, environment setup, landscape web\n search, brain context, prior learnings, telemetry.\n\n### Findings (each recorded once)\n\n| ID | Section | Evidence (plan text) | Failure mode | Selected remedy | Residual risk | Verification |\n|----|---------|----------------------|--------------|-----------------|---------------|--------------|\n| D1 | 1, 4 | \"no additional version checks or coordination between a cache fill and a write\"; invariant \"every read begun after that write completes must observe the committed version\" | Read misses, awaits DB (v1); write commits v2 and returns; read resumes and fills v1; later reads hit stale v1 up to 30 s. Also a post-write miss joins the pre-write in-flight fetch and receives v1 | In-flight invalidation marker; fill only if not invalidated; post-write misses start a fresh fetch. Original reader still returns v1 to its own caller (allowed: it began before commit) | None against the invariant. Extra DB read per invalidated flight, counted | Pause/release tests for both schedules in Section 4; `fill_skipped_stale_total` increments |\n| D2 | 2 | Sketch deletes only after a successful write; test \"failed-write preservation\" | Write times out after the DB committed; cache keeps v1 up to 30 s; caller retry or re-read sees stale data | `finally` invalidation on every write outcome; rename test | One wasted DB read after a failed write | Test: write rejects \u21d2 next read misses and hits DB |\n| D3 | 5 | \"undefined means a cache miss\"; sketch `cache.set(key, value)` with unspecified absent value | If `repository.read` returns `undefined` for absent, absents are never cached and every missing-profile read hits the DB; if the sentinel leaks, callers receive an internal object | Pin: absent = `null`; adapter maps null \u21c4 sentinel; wrapper passes null through | None | Test: absent read hits DB once, then served null for 10 s; create invalidates |\n| D4 | 2 | \"On any cache failure the existing adapter bypasses the cache until an empty cache is reinitialized\" | One oversized value or a full cache classified as \"failure\" trips a sticky bypass; hit rate drops to 0 with no read errors | Classify skip vs failure; adapter never throws to wrapper; bypass only on thrown adapter error | Sticky bypass still requires operator toggle (alerted by D5) | Test: oversize set is a no-op, state stays ACTIVE, counter increments; thrown adapter error \u21d2 BYPASSED |\n| D5 | 8, 9 | \"Monitor hit/miss, eviction, cache bytes, fallback errors, DB CPU, and read p95\"; no alerts, gauge, runbook, or \"healthy\" definition | Sticky BYPASSED or hit-ratio collapse is visible only if someone is watching; rollout \"healthy hour\" is undefined | Named metrics, state gauge, three alerts, dashboard panels, runbook, healthy definition | Alert thresholds may need tuning after first week | Metric assertions in tests; alert rules reviewed before 10% stage |\n| D6 | 9, 4 | \"both reads and writes bypass the cache while disabled\"; \"enable for 10% of keys\" | Stage 50% \u2192 10% \u2192 50%: keys cached at first 50% stage are not deleted by writes during the 10% stage and revive stale when restored. Flag lookup failure behavior unspecified | Writes always invalidate while a cache instance exists; stable key hash; flag failure \u21d2 reads disabled | None against the invariant | Partial-rollout test in Amended verification |\n| D7 | 10 | Ordering rules exist only in this plan | New engineer in 12 months cannot see why `invalidate` exists; stale mental model recreates D1 | Code-comment diagram on wrapper and `invalidate`; README section | Diagram maintenance is part of future changes | Reviewer checks comment matches code in PR |\n\n### Section outcomes (11 of 11 evaluated)\n1. **Architecture:** 1 finding \u2192 D1. Diagram in Diagrams \u00a71. Coupling added: wrapper \u2194 single-flight `invalidate` (justified, in-process). Single point of failure: the one process, unchanged. 10x load: LRU at 1000 entries thrashes first (watch item, see Section 7). Rollback: flag off, seconds.\n2. **Error & rescue map:** 9 error paths mapped (registry below); 2 gaps \u2192 D2, D4. No catch-all handlers introduced; `finally` runs cleanup only and rethrows nothing new.\n3. **Security & threat model:** No issues found. No new endpoint, input, dependency, or secret. Keys are tenant-scoped after authz, so no cross-tenant hit or existence oracle. Negative cache is within an already-authorized tenant. DTOs in process memory match existing handling. Keys never logged (D5 log line carries none).\n4. **Data flow & interaction edge cases:** 2 unhandled orderings \u2192 D1 (schedule in Diagrams \u00a72b), 1 rollout ordering \u2192 D6. No UI interactions; interaction table not applicable.\n5. **Code quality:** 1 finding \u2192 D3. Naming, DRY, and structure fit the existing adapter/single-flight patterns. Branch count per function \u2264 3. No over-engineering: the marker reuses existing state.\n6. **Test review:** Diagram in Diagrams \u00a77. Gaps are the consequences of D1\u2013D6 (tests listed in Amended verification) plus test hygiene: TTL tests use an injected clock. No new behavioral choice needed. 2 a.m. Friday test: pause/release stale-fill schedule. Hostile QA: write during in-flight read at every stage of the flag. Chaos: force adapter throw mid-traffic and confirm BYPASSED alert and unchanged error rate. Pyramid: many unit, 1 integration (flag + wrapper + repository fake), 0 E2E. Load test: replay the one-week hot-key trace against a staging DB before 10%.\n7. **Performance:** No issues found. Cache ops O(1) sync. Memory bounded at 16 MiB. TTL floor \u2248 30 DB reads/s for 900 keys. Watch item (not a TODO, capacity growth is an expansion): 900 hot keys against 1000 entries leaves 10% headroom; if `evictions_total` climbs while hit ratio < 60%, the entry cap is the cause.\n8. **Observability:** 1 gap \u2192 D5.\n9. **Deployment & rollout:** 1 risk \u2192 D6; \"healthy\" definition folded into D5. No migration. Deploy order: code deploy with flag off \u2192 10% \u2192 50% \u2192 100%, one healthy hour each. Diagrams \u00a75, \u00a76.\n10. **Long-term trajectory:** Reversibility 5/5 (flag plus small wrapper). Debt: 1 item \u2192 D7. Path dependency: none; repository seam preserved for a future backend. 1-year question: answered by D7.\n11. **Design & UX:** SKIPPED, justified: plan states no UI, API, or product surface change.\n\n### Outside voice\nSkipped. `.gstack-section-state-QPMuNw/config.yaml` sets `codex_reviews: disabled`. Per the documented control, no challenge prompt was built, no outside CLI or Agent fallback was dispatched, and outside coverage is reported as **disabled**. Re-enable: `gstack-config set codex_reviews enabled`. The `outside_status: disabled` review-log record was not persisted: no shell tool is available in this run.\n\n### Required outputs\n\n**NOT in scope** (considered, deferred, with reason)\n- Distributed or cross-process cache: excluded by the accepted scope statement.\n- Versioned rows / compare-on-fill (Approach C): needs a schema change.\n- Prewarming and automatic recovery from BYPASSED: alternatives to adequate accepted remedies (D5 alert + runbook); HOLD SCOPE.\n- Raising the 1000-entry cap: hypothetical capacity; watch item only.\n\n**What already exists** \u2014 LRU adapter (limits, TTL, sentinel, bypass): reused, contract pinned by D3/D4. Single-flight wrapper: reused, gains `invalidate` (D1). Runtime feature flag: reused, semantics pinned by D6. Typed repository errors and contract tests: reused unchanged.\n\n**Dream state delta** \u2014 After this plan the service has a flag-gated, invariant-safe local cache behind the unchanged repository interface. The 12-month ideal (swappable backend if multi-process arrives) remains reachable through the same seam; nothing here forecloses it.\n\n**Error & Rescue Registry**\n\n| Method/codepath | What can go wrong | Error class | Rescued? | Action | User sees |\n|---|---|---|---|---|---|\n| readProfile \u2192 flag lookup | flag service unavailable | FlagLookupError | Y (D6) | treat as reads disabled, counter | nothing |\n| readProfile \u2192 cache.get | adapter internal error | AdapterError (internal) | Y (D4) | adapter \u2192 BYPASSED, log once, read continues to DB | nothing |\n| readProfile \u2192 repository.read | record absent | (null result) | Y (D3) | cache ABSENT 10 s, return null | existing not-found |\n| readProfile \u2192 repository.read | DB timeout / connection error | DbTimeoutError / DbConnectionError | Y (existing) | single-flight releases, no fill, typed API error | existing error mapping |\n| readProfile \u2192 repository.read | authorization failure | AuthorizationError | Y (existing, before repository) | never reaches cache | existing 403 |\n| readProfile \u2192 single-flight joiners | leader rejects | same error as leader | Y (existing) | all joiners receive leader's typed error | existing error mapping |\n| readProfile \u2192 cache.set | value > byte cap / cache full | (skip, no error) | Y (D4) | no-op, `set_skipped_oversize_total` | nothing |\n| readProfile \u2192 cache.set | adapter internal error | AdapterError (internal) | Y (D4) | \u2192 BYPASSED, log once, value still returned | nothing |\n| writeProfile \u2192 repository.write | validation / conflict / timeout | typed repository errors | Y (existing + D2) | rethrow unchanged; `finally` invalidates | existing error mapping |\n\n**Failure Modes Registry**\n\n| Codepath | Failure mode | Rescued? | Test? | User sees? | Logged? |\n|---|---|---|---|---|---|\n| read fill vs concurrent write | stale fill after write returned | Y (D1) | Y (pause/release) | fresh data | counter |\n| post-write miss joins pre-write flight | stale value to new reader | Y (D1) | Y | fresh data | counter |\n| write fails after DB commit | stale entry survives | Y (D2) | Y | fresh data on re-read | typed error log (existing) |\n| absent record returns undefined | negative cache never fills | Y (D3) | Y | not-found, one DB read per 10 s | hits/misses |\n| sentinel leaks to caller | internal object returned | Y (D3) | Y | null | n/a |\n| oversized value | sticky bypass | Y (D4) | Y | nothing | counter |\n| adapter throws | sticky bypass, hit rate 0 | Y (D4, D5) | Y | nothing; alert to operator | log line + gauge + alert |\n| stage 50\u219210\u219250 | stale revival | Y (D6) | Y | fresh data | n/a |\n| flag lookup fails | undefined read path | Y (D6) | Y | nothing (DB read) | counter |\n| LRU thrash at 10x keys | hit ratio < target | N/A (watch) | load test | slower reads, correct | evictions + alert |\n\nCRITICAL GAPS remaining: 0.\n\n**TODOS.md updates** \u2014 0 proposed. Every evidenced gap is repaired in scope. Candidates rejected as expansions under HOLD SCOPE: auto-recovery from BYPASSED, cap growth, prewarming. Step evaluated, not skipped.\n\n### Diagrams\n\n\u00a71 System architecture\n```\n caller \u2500\u2500\u25b6 authn/authz \u2500\u2500\u25b6 readProfile / writeProfile (wrapper)\n \u2502 \u2502 \u2502\n \u25bc \u25bc \u25bc\n flag.readsEnabledFor LRU adapter single-flight (+invalidate)\n \u2502 \u2502\n \u2514\u2500\u2500\u25b6 repository.read/write \u2500\u2500\u25b6 DB\n metrics/logs \u25c0\u2500\u2500 wrapper + adapter (D5)\n```\n\n\u00a72a Data flow with shadow paths (read)\n```\n key \u2500\u2500\u25b6 flag? \u2500\u2500\u25b6 cache.get \u2500\u2500\u25b6 single-flight \u2500\u2500\u25b6 repository.read \u2500\u2500\u25b6 fill? \u2500\u2500\u25b6 return\n \u2502 \u2502 \u2502 \u2502 \u2502 \u2502\n nil/bad lookup undefined join or start null (absent) invalidated \u2192 skip\n key: fails: = miss; leader error \u2192 \u2192 cache null oversize \u2192 skip\n rejected reads null = ABSENT joiners get it DB error \u2192 adapter throw \u2192\n by exist- disabled hit no fill typed error BYPASSED\n ing valid.\n```\n\n\u00a72b Async ordering schedule (invariant boundary: `writeProfile` returns)\n```\n t R1 (read) W (write) R2 (read) cache result\n 1 miss, start DB read v1 \u2014\n 2 DB commits v2 \u2014\n 3 finally: delete, \u2014\n invalidate(R1 flight); returns\n 4a resume: invalidated \u2192 \u2014 R1 gets v1 (allowed)\n skip fill, return v1\n 5a miss \u2192 fresh v2 R2 gets v2 \u2713\n flight \u2192 v2, fill\n 4b miss \u2192 NOT joined \u2014\n (flight detached)\n [without D1: 4a fills v1 \u2192 R2 hits v1 \u2717 up to 30 s; 4b joins R1 \u2192 R2 gets v1 \u2717]\n Alt order: R1 resumes at t2.5 before W's finally \u2192 fills v1 \u2192 t3 delete removes it \u2713\n```\n\n\u00a73 State machine (adapter state, per process)\n```\n DISABLED \u2500\u2500flag on\u2500\u2500\u25b6 ACTIVE \u2500\u2500adapter throws\u2500\u2500\u25b6 BYPASSED\n \u25b2 \u2502 \u2502\n \u2514\u2500\u2500\u2500\u2500flag off\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500flag off\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n Invalid: BYPASSED \u2192 ACTIVE without passing DISABLED (prevented: only\n flag-off drops the instance; flag-on constructs a new empty one).\n Entry states: MISS \u2192 FILLED(v, 30 s) | ABSENT(10 s) \u2192 deleted by write / TTL / eviction.\n```\n\n\u00a74 Error flow\n```\n repository error \u2500\u2500\u25b6 single-flight releases \u2500\u2500\u25b6 no fill \u2500\u2500\u25b6 typed API error (unchanged)\n adapter throw \u2500\u2500\u25b6 BYPASSED + log + gauge=2 \u2500\u2500\u25b6 read continues to DB \u2500\u2500\u25b6 alert at 5 min\n write error \u2500\u2500\u25b6 finally: delete + invalidate \u2500\u2500\u25b6 rethrow typed error\n```\n\n\u00a75 Deployment sequence\n```\n deploy (flag off) \u2192 verify gauge=0, error rate baseline\n \u2192 10% keys, 1 healthy hour \u2192 50%, 1 healthy hour \u2192 100%\n each stage: confirm hit ratio rising, evictions, bytes < 16 MiB, no alerts,\n DB CPU and p95 trending toward targets; record against acceptance targets\n```\n\n\u00a76 Rollback flowchart\n```\n alert or regression? \u2500\u2500yes\u2500\u2500\u25b6 flag off (seconds) \u2500\u2500\u25b6 gauge=0? \u2500\u2500\u25b6 error rate/p95 recover?\n \u2502 no \u2502 no\n \u25bc \u25bc\n restart process unrelated: escalate\n BYPASSED without regression \u2500\u2500\u25b6 runbook: flag off \u2192 on \u2192 gauge=1, hit ratio recovers in 2 min\n```\n\n\u00a77 Test diagram (new things introduced)\n```\n NEW UX FLOWS: none\n NEW DATA FLOWS: read-through fill; write invalidation; absent negative cache\n NEW CODEPATHS: flag gate; hit; miss+start; miss+join; invalidated skip;\n oversize skip; BYPASSED; finally invalidation\n NEW ASYNC WORK: none (all in request path)\n NEW INTEGRATIONS: none (flag service already used)\n NEW ERROR/RESCUE PATHS: see Error & Rescue Registry (9 rows)\n Coverage: unit for every row; 1 integration (flag+wrapper+repository fake);\n pause/release ordering tests (D1); partial-rollout test (D6); metric assertions (D5)\n```\n\n**Stale diagram audit** \u2014 No existing ASCII diagrams in the plan or the files it names; D7 adds the first ones, and their maintenance is part of the change.\n\n## Implementation Tasks\nSynthesized from this review's findings. Each task derives from a specific\nfinding above. Run with Claude Code or Codex; checkbox as you ship.\n\n- [ ] **T1 (P1, human: ~4h / CC: ~15min)** \u2014 wrapper + single-flight \u2014 Add `inflight.invalidate(key)` and the fill guard\n - Surfaced by: Section 1/4 \u2014 D1 stale fill after concurrent write\n - Files: profile repository wrapper, single-flight helper, wrapper tests\n - Verify: pause/release tests for schedules 4a/4b/alt pass; `fill_skipped_stale_total` increments\n- [ ] **T2 (P1, human: ~1h / CC: ~5min)** \u2014 wrapper \u2014 Move invalidation into `finally` on writes\n - Surfaced by: Section 2 \u2014 D2 write failure after commit\n - Files: wrapper, wrapper tests (rename failed-write test)\n - Verify: rejected write \u21d2 next read misses and reads DB\n- [ ] **T3 (P1, human: ~2h / CC: ~10min)** \u2014 repository + adapter \u2014 Pin absent = null \u21c4 ABSENT sentinel\n - Surfaced by: Section 5 \u2014 D3 undefined/sentinel ambiguity\n - Files: repository read, LRU adapter get/set, tests\n - Verify: absent read hits DB once then null for 10 s; create invalidates\n- [ ] **T4 (P1, human: ~3h / CC: ~10min)** \u2014 LRU adapter \u2014 Classify oversize/full as skip; bypass only on thrown error; never throw to wrapper\n - Surfaced by: Section 2 \u2014 D4 sticky bypass on oversize\n - Files: LRU adapter, adapter tests\n - Verify: oversize set no-op, state ACTIVE, counter up; injected throw \u21d2 BYPASSED\n- [ ] **T5 (P1, human: ~4h / CC: ~20min)** \u2014 metrics/alerts/runbook \u2014 Emit named metrics, state gauge, log line; add 3 alerts, dashboard panels, runbook\n - Surfaced by: Section 8/9 \u2014 D5 no alerts or healthy definition\n - Files: wrapper, adapter, metrics config, alert rules, runbook doc\n - Verify: metric assertions in tests; alert rules load; dry-run BYPASSED alert in staging\n- [ ] **T6 (P1, human: ~2h / CC: ~10min)** \u2014 flag gate \u2014 Reads consult stable-hash flag; writes always invalidate; flag failure \u21d2 reads disabled\n - Surfaced by: Section 9/4 \u2014 D6 stale revival across stages\n - Files: wrapper, flag helper, integration test\n - Verify: 50\u219210\u219250 test misses; flag lookup failure test reads DB\n- [ ] **T7 (P2, human: ~1h / CC: ~5min)** \u2014 docs \u2014 Code-comment ordering diagram and README section\n - Surfaced by: Section 10 \u2014 D7 knowledge concentration\n - Files: wrapper, single-flight helper, repository README\n - Verify: PR reviewer confirms comment matches code\n- [ ] **T8 (P2, human: ~1h / CC: ~5min)** \u2014 tests \u2014 Injected clock for TTL and sentinel-TTL tests\n - Surfaced by: Section 6 \u2014 flakiness risk (time dependence)\n - Files: wrapper tests, adapter tests\n - Verify: tests pass with no wall-clock sleeps\n- _No new tasks from Section 3 (Security), Section 7 (Performance), Section 11 (skipped)._\n\nJSONL task artifact for `/autoplan`: **not written**. No shell tool is available in this run, so the `jq` writer could not execute. Re-run `/plan-ceo-review` interactively or write the eight rows above by hand to `~/.gstack/projects//tasks-ceo-review-.jsonl`.\n\n### Completion Summary\n```\n +====================================================================+\n | MEGA PLAN REVIEW \u2014 COMPLETION SUMMARY |\n +====================================================================+\n | Mode selected | HOLD SCOPE (preselected by user) |\n | System Audit | skipped per run rules (no shell, no repo scan)|\n | Step 0 | premise real; approach B (D0); HOLD; 0E done |\n | Section 1 (Arch) | 1 issue found (D1) |\n | Section 2 (Errors) | 9 error paths mapped, 2 GAPS (D2, D4) closed |\n | Section 3 (Security)| 0 issues found, 0 High severity |\n | Section 4 (Data/UX) | 3 edge cases mapped, 0 unhandled after D1/D6 |\n | Section 5 (Quality) | 1 issue found (D3) |\n | Section 6 (Tests) | Diagram produced, 0 open gaps (T1\u2013T6, T8) |\n | Section 7 (Perf) | 0 issues found (1 watch item) |\n | Section 8 (Observ) | 1 gap found (D5) |\n | Section 9 (Deploy) | 1 risk flagged (D6) |\n | Section 10 (Future) | Reversibility: 5/5, debt items: 1 (D7) |\n | Section 11 (Design) | SKIPPED (no UI scope) |\n +--------------------------------------------------------------------+\n | NOT in scope | written (4 items) |\n | What already exists | written |\n | Dream state delta | written |\n | Error/rescue registry| 9 rows, 0 CRITICAL GAPS |\n | Failure modes | 10 total, 0 CRITICAL GAPS |\n | TODOS.md updates | 0 items proposed |\n | Scope proposals | 0 proposed, 0 accepted (HOLD) |\n | CEO plan | skipped (HOLD) |\n | Outside voice | skipped (codex_reviews disabled) |\n | Lake Score | 8/8 recommendations chose complete option |\n | Diagrams produced | 8 (arch, data flow, async schedule, state, |\n | | error, deploy, rollback, test) |\n | Stale diagrams found | 0 |\n | Unresolved decisions | 0 |\n +====================================================================+\n```\n\nAuto-chosen decisions (automated run, no human present): D0\u2013D7 each took the\nrecommended option; none was destructive. Next-step recommendation: run\n`/plan-eng-review` (required shipping gate); no design review needed.\nNot executed in this run (no shell tool): `gstack-review-log`,\n`gstack-decision-log`, `gstack-review-read`, JSONL task write, telemetry,\nlearnings log. No durable learnings this session beyond the findings above.\n\n## GSTACK REVIEW REPORT\n\n| Review | Trigger | Why | Runs | Status | Findings |\n|--------|---------|-----|------|--------|----------|\n| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | clean | mode: HOLD_SCOPE, 0 critical gaps (7 findings D1\u2013D7, all remedied in plan) |\n| Outside Review | codex (`codex_reviews: disabled`) | Independent 2nd opinion | 0 | disabled | none; step skipped by config, no native fallback |\n| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 0 | \u2014 | \u2014 |\n| Design Review | `/plan-design-review` | UI/UX gaps | 0 | \u2014 | \u2014 |\n| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | \u2014 | \u2014 |\n\n- **OUTSIDE COVERAGE:** provider codex, phase plan-review, status disabled by `codex_reviews: disabled`, 0 findings. Review-log record not persisted (no shell in this run).\n- **VERDICT:** CEO CLEARED \u2014 eng review required\n\nNO UNRESOLVED DECISIONS\n", "table": "| ID | Section | Evidence (plan text) | Failure mode | Selected remedy | Residual risk | Verification |\n|----|---------|----------------------|--------------|-----------------|---------------|--------------|\n| D1 | 1, 4 | \"no additional version checks or coordination between a cache fill and a write\"; invariant \"every read begun after that write completes must observe the committed version\" | Read misses, awaits DB (v1); write commits v2 and returns; read resumes and fills v1; later reads hit stale v1 up to 30 s. Also a post-write miss joins the pre-write in-flight fetch and receives v1 | In-flight invalidation marker; fill only if not invalidated; post-write misses start a fresh fetch. Original reader still returns v1 to its own caller (allowed: it began before commit) | None against the invariant. Extra DB read per invalidated flight, counted | Pause/release tests for both schedules in Section 4; `fill_skipped_stale_total` increments |" }