mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 09:55:29 +02:00
* feat: add a restricted and supervised Claude Code runner Preserve configured authentication and models while enforcing tool access, strict completion JSON, bounded output and process cleanup. Cover argv, failure handling, session metadata and Windows process containment. * feat: route outside reviews by harness and migrate wrapper installs Use Claude Code from Codex and Codex from other supported hosts, with shared invocation rendering, positive gate validation and per-phase provenance. Rename /claude to /claude-code, repair managed shared and copied installations safely, and generate native Kiro skills. Add installed-workflow, failure-injection and live cross-harness regression coverage. * test: recognize CEO mode labels without terminal spacing The paid workflow rendered SCOPEEXPANSION at option 4, but its driver required a literal space. Match the leading mode title without cursor-spacing artifacts and ignore adjacent preview text. Preserve missing-target failures and downstream posture assertions. * test: isolate plan-count fixtures before starting review workflows Seed the complete test plan in a private git repository before launching Claude, so a bare slash command cannot review the live workspace while a delayed fixture message remains queued. Preserve count thresholds, parsers and budgets. Add initial-context and installed-discovery tests, and retain startup/terminal diagnostics on failed evaluations. * test: stabilize review fixtures and Claude eval startup Preserve source boundaries in workflow judge inputs, isolate CEO mode plans, and wait for interactive trust input readiness. Keep startup failure evidence and retain existing models, budgets, and assertions. Co-Authored-By: OpenAI Codex <noreply@openai.com> * test: classify collapsed review modes and isolate seeded findings Keep review questions out of the setup count when terminal cursor positioning removes spaces. State existing webhook safeguards so the five-finding control measures its seeded defects without accidental extra security and concurrency gaps. Preserve question bands and the paired control. Co-Authored-By: OpenAI Codex <noreply@openai.com> * test: isolate browser daemon state across free shards Co-Authored-By: OpenAI Codex <noreply@openai.com> * test: stabilize native review counting and interactive navigation Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore: prepare v1.82.0.0 release Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix: eliminate browser and process-cleanup test flakes Pin every CI surface to Bun 1.4.0 to avoid extra-stdio finalizers closing reused live sockets. Add an isolated GC/listener regression that fails on Bun 1.3.13, and prevent coordinated rollback to an affected CI runtime. Check renderer cleanup against the render's own staging directory so concurrent renders cannot invalidate the assertion. Make the no-pgrep process-tree walk tolerate disappearing /proc entries, and synchronize its test fixture through child readiness and pipe EOF instead of sleeps. Validation: 9,157 passed, 31 skipped, zero failures across 556 files with retries disabled. Build, all-host generation freshness, and skill checks passed. All three races have failing-before/passing-after regressions. * fix: count completed native review questions in evals * fix: drive review navigation from confirmed native choices * fix: require complete section-loading eval reports * test: isolate telemetry HTTP transport from local assertions * fix: keep review input on the active native question * test: let tunnel revocation daemon choose an available port * test: allocate available ports for pairing and watchdog fixtures * fix: stabilize planning eval navigation and phase reporting * test: isolate installed runtime paths in planning evals * test: stabilize review evidence and concurrent refresh fixtures * fix: resolve design findings before editing the plan * fix: honor and persist disabled outside plan reviews * fix: preserve planning decisions and terminal evidence Load installed host reviews at autoplan phase entry and wait for completed reviewers and saved artifacts. Reuse approved remedies while preserving individual finding decisions. Drive interactive evals from the current terminal viewport, bind native questions across scrolling, and require complete native report evidence. Cover captured stale menus, permission lifecycles, setup classification, and disabled-review tool availability with deterministic regressions. Advance release metadata and the upgrade migration to the unclaimed 1.83.0.0 slot. * fix: drive native review questions and preserve current plans Use the native single-choice keyboard protocol and current terminal viewport, with per-question navigation inside packets and completed-call coverage. Keep permissions, multi-select menus, and Submit controls distinct. Send Autoplan reviewers the amended implementation plan, keep its review record separate, and supply retained application contracts in the chain fixture. Clarify individual DevEx decisions and complete CEO fix options; use one active plan destination for the section-loading report. * fix: preserve complete plan-review decisions * fix: recognize native plan dialogs and reviewer controls * fix: preserve review decisions and phase completion * fix: recognize completed reviews without losing findings * fix: preserve review continuity and native eval completion * test: fix native review completion and eval retry isolation * test: handle native review menus and complete eval fixtures * test: fix native review setup, completion, and isolation failures * test: limit native skill discovery to runtime assets * fix: bind Autoplan reviews to full ordered phase inputs * test: fix planning eval routing, counting, and timeout handling * chore: advance queued release to v1.84.0.0 * fix: preserve complete review inputs and planning decisions * fix: reconcile review approvals and preserve phase obligations * fix: preserve review obligations and unblock eval permissions Carry recorded Autoplan requirements into blind phase inputs, require Eng review approvals before exit, and exercise combined asynchronous flows in CEO reviews. Correct native finding and handoff classification and unblock repeated report edits using scoped request identities. * fix: retain plan requirements and complete native review dialogs * fix: complete native review prompts and retain plan references * fix: preserve review inputs and classify native eval evidence * fix: check competing completion orders in CEO reviews * fix: recognize review decisions and require phase methodology Require the current phase methodology before Autoplan snapshots. Correct substantive decision, closed handoff, and cache-finding classification, and honor the recommended implementation approach in native review dialogs. Add captured-transcript regressions without changing review thresholds, provider models, retries, or deadlines. * test: bind native review decisions and close completed handoffs * fix: complete review dialogs and verify methodology delivery * fix: preserve review evidence and unblock native eval prompts * fix: handle native review question completions * fix: recognize native review narration and controls * fix: count native review decisions and isolate eval fixtures * test: verify seeded review coverage and current artifact permissions * test: isolate model and brain-aware skill renders * fix: repair native workflow evaluation and clarify review steps * fix: stabilize workflow eval evidence and review guidance * test: repair native workflow observation and fixture isolation * fix: recognize completed workflow evidence and owned skill reads * test: repair seeded workflow delivery and completion evidence * test: recognize current review evidence across native forms * test: handle native review variants and permission redraws * fix: honor review preferences and recognize native eval evidence * test: recognize completed review decisions and queued permissions * test: match current review contracts and partial-line edits * test: recognize completed workflow evidence and bounded human waits * fix: preserve review entry gates and native eval interactions * fix: recognize native workflow evidence and preserve review gates * test: recognize current review evidence and preconfigure workflow fixtures * test: recognize completed review findings and scoped artifact permissions * fix: stabilize native workflow review and permission evidence * fix: recognize current review evidence and scoped edit confirmations Clarify Design and engineering review entry instructions and Design scoring. Recognize required legacy coverage and public Autoplan completion recaps. Bind the pending Edit confirmation to its exact file, ordered digest, and one-request approval when a preceding command display remains visible. Keep reviews within their existing size limits and preserve scope gates when extracting workflow fixtures from either supported preamble header. Keep failure outcomes, review thresholds, provider choices, and eval budgets. * fix: recover review workflow progress and eval evidence * fix: recognize valid review evidence and scope selection * test: fix review evidence parsing and repeated artifact prompts * test: recognize valid review decisions and pending native cards * fix(plan-eng-review): keep final navigation consistent with approved tasks * test: recognize valid review evidence and bind legacy diff requests * fix: stabilize review eval evidence and harness repair guidance * docs: update project documentation for v1.85.0.0 Co-Authored-By: OpenAI Codex <noreply@openai.com> * test: fix Windows CI fixtures and credential scan Rebase captured JSON values and filesystem evidence using the appropriate path convention. Compile native fake CLIs on Windows and synchronize pipe holder readiness, with cleanup retained when assertions fail. Assemble synthetic credential fixtures at runtime so the added-line scan keeps enforcing the same gate without flagging its own rejection controls. Discover generated skills directly for the empty-find regression check, avoiding a recursive scan through saved evaluation artifacts and dependencies. * fix: preserve source renders on Windows Compare canonical generator paths using native separators so an output sidecar pointing at the source cannot overwrite its skill or metadata. Keep the regression fixture isolated from the real checkout and expose freshness diagnostics before asserting subprocess status. Detach Windows drain-test pipe holders from the fake provider's automatic child cleanup while preserving the enclosing runner job and its assertions. * fix: clarify outside review fallback and CEO decisions Render one applicable own-harness fallback path and retain native review, disabled policy, and missing-coverage semantics. Align report field names and mode labels, and make the existing per-cut scope approval explicit. Regenerate skill outputs and keep the workflow judge's model, thresholds, and retry policy unchanged. * chore: move release to free version slot (v1.86.0.0) PR #2852 now claims v1.85.0.0. Align the release metadata and rename migration so upgrades from that version still receive it. Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix: include engineering review prerequisites and restore branch context * fix: recognize coverage diagrams and clarify design review instructions * fix: preserve file identities and join Windows test processes --------- Co-authored-by: OpenAI Codex <noreply@openai.com>
701 lines
37 KiB
Markdown
701 lines
37 KiB
Markdown
# Plan: cache profile summaries in one process
|
||
|
||
## Measured problem and accepted scope
|
||
The existing profile-summary service has one active process. A one-week trace
|
||
shows repeated reads of about 900 hot keys: DB CPU is 70%, with read p95 120 ms.
|
||
Add a process-local LRU wrapper to the existing repository. Acceptance targets
|
||
are at least 60% cache hits, DB CPU below 50%, and read p95 below 60 ms, with the
|
||
existing error-rate and correctness SLOs unchanged. This is an internal backend
|
||
change with no UI, API, schema, pricing, or developer onboarding change.
|
||
|
||
## Existing contracts retained
|
||
- All reads and writes use this repository in the same process; there are no
|
||
external DB writers. Multi-process operation remains unsupported and startup
|
||
rejects that configuration while caching is enabled.
|
||
- Authentication and authorization run before repository access. Keys encode
|
||
the authenticated tenant ID and validated profile ID without ambiguity.
|
||
Values are immutable profile-summary DTOs; secrets and cache keys are never
|
||
logged. Cached results cannot bypass authorization.
|
||
- The existing LRU adapter supports 1000 entries, a 16 MiB byte cap, and a
|
||
30-second TTL. Recorded hot data fits those limits. Absent records use a
|
||
distinct sentinel with a 10-second TTL; undefined means a cache miss.
|
||
- Cache operations are synchronous and atomic in the single JS event loop.
|
||
On any cache failure the existing adapter bypasses the cache until an empty
|
||
cache is reinitialized; repository errors keep the current typed API error
|
||
mapping. The existing per-key
|
||
single-flight wrapper coalesces simultaneous misses and releases on failure.
|
||
- A read already in progress when a write commits may return its earlier DB
|
||
snapshot to that caller. Every read begun after that write completes must
|
||
observe the committed version. TTL expiry is not a substitute for this rule.
|
||
|
||
## Proposed wrapper integration
|
||
Keep the current read-through repository interface and shared adapters. These
|
||
are the complete new read/write ordering rules; no additional version checks or
|
||
coordination between a cache fill and a write are proposed:
|
||
|
||
```javascript
|
||
async function readProfile(key) {
|
||
const cached = cache.get(key);
|
||
if (cached !== undefined) return cached;
|
||
const value = await repository.read(key);
|
||
cache.set(key, value);
|
||
return value;
|
||
}
|
||
|
||
async function writeProfile(key, update) {
|
||
const saved = await repository.write(key, update);
|
||
cache.delete(key);
|
||
return saved;
|
||
}
|
||
```
|
||
|
||
## Verification and rollout
|
||
Existing repository contract tests cover tenant isolation, key validation,
|
||
absence, DB failures, and authorization. New wrapper tests cover hit/miss,
|
||
eviction and byte limits, TTL, adapter-failure fallback, successful-write
|
||
invalidation, failed-write preservation, and concurrent-miss coalescing.
|
||
The rollout uses the existing runtime feature flag: enable for 10% of keys,
|
||
then 50%, then all keys after one healthy hour at each stage. Monitor hit/miss,
|
||
eviction, cache bytes, fallback errors, DB CPU, and read p95 without raw IDs.
|
||
On error-rate or latency regression, disable the flag immediately; both reads
|
||
and writes bypass the cache while disabled, and enabling creates an empty cache.
|
||
Cold starts remain within the existing DB capacity. The service owner monitors
|
||
the rollout and records the results against the acceptance targets.
|
||
|
||
## Out of scope
|
||
Distributed caching, cross-process coherence, prewarming, changing consistency
|
||
semantics, or adding new product surfaces. The repository interface preserves a
|
||
future replacement path without introducing a general cache framework now.
|
||
|
||
---
|
||
|
||
## CEO Review (HOLD SCOPE)
|
||
|
||
Mode: **HOLD SCOPE** — the plan's scope is accepted as stated. This review makes it bulletproof: catches failure modes, tests every edge case, ensures observability, maps every error path. No expansion surfaced.
|
||
|
||
Approach confirmed: thin wrapper around the existing repository, reusing the existing LRU adapter, single-flight coalescing, and feature-flag rollout. Minimal viable and architecturally sound — the right call.
|
||
|
||
---
|
||
|
||
## Step 0: Nuclear Scope Challenge
|
||
|
||
**0A. Premise Challenge**
|
||
|
||
1. Right problem? Yes. DB CPU at 70% and p95 at 120ms with 900 stable hot keys is a textbook read-cache opportunity. A one-week trace backing the hot-key profile makes this measurement-driven, not speculative.
|
||
2. Direct path? Yes. A process-local LRU is the most direct intervention: zero network hops, zero consistency overhead, zero schema change.
|
||
3. What if nothing done? DB CPU continues climbing as traffic grows; latency degrades further. Real pain point, evidenced by the trace.
|
||
|
||
**0B. Existing Code Leverage**
|
||
|
||
- Existing LRU adapter: fully reused, no new cache library
|
||
- Existing single-flight wrapper: reused for concurrent-miss coalescing
|
||
- Existing feature flag: reused for staged rollout
|
||
- Existing repository contract tests: extended, not replaced
|
||
- Existing tenant isolation and key validation: unchanged and inherited
|
||
|
||
No parallel implementation; no rebuild of existing functionality.
|
||
|
||
**0C. Dream State Mapping**
|
||
|
||
```
|
||
CURRENT STATE THIS PLAN 12-MONTH IDEAL
|
||
DB CPU 70% ---> LRU wraps repository ---> DB CPU <50%, p95 <60ms
|
||
read p95 120ms 60%+ cache hits stable under 10x hot traffic
|
||
all reads hit DB 900 hot keys served future: multi-process if
|
||
single process OK from process memory service scales (separate plan)
|
||
```
|
||
|
||
**0C-bis. Implementation Alternatives**
|
||
|
||
```
|
||
APPROACH A: Thin wrapper (this plan)
|
||
Summary: Add readProfile/writeProfile wrapper around existing repository, reuse LRU adapter.
|
||
Effort: S
|
||
Risk: Low
|
||
Pros: Minimal diff; zero new dependencies; fully reversible via flag
|
||
Inherits existing adapter's eviction, TTL, byte-cap, and bypass logic
|
||
Cons: Process-local only; no warm restart; cross-process unsupported by design
|
||
Reuses: existing LRU adapter, single-flight wrapper, feature flag, contract tests
|
||
|
||
APPROACH B: Decorator/Interceptor pattern
|
||
Summary: Wrap the repository interface with a cache decorator injected at construction.
|
||
Effort: M
|
||
Risk: Low
|
||
Pros: Cleaner separation; easier to swap caching strategies later
|
||
Cons: More indirection; no material runtime difference for this scope
|
||
Reuses: same LRU adapter and flag
|
||
|
||
APPROACH C: Redis sidecar
|
||
Summary: Replace process-local LRU with a Redis-backed cache for future multi-process use.
|
||
Effort: XL
|
||
Risk: High
|
||
Pros: Solves cross-process coherence; supports horizontal scale
|
||
Cons: New infrastructure dependency; network latency per read; out of stated scope
|
||
Reuses: nothing from current adapter
|
||
```
|
||
|
||
**RECOMMENDATION:** Approach A. The problem is single-process, the data fits in memory, and approach A is fully reversible with zero new dependencies. Approach B adds abstraction without runtime benefit. Approach C is explicitly out of scope and introduces infrastructure risk.
|
||
|
||
Auto-decided: Approach A selected (recommended, no human present).
|
||
|
||
**0D. HOLD SCOPE Analysis**
|
||
|
||
1. Complexity check: The wrapper touches 1 module (repository), adds 2 functions, and extends 1 test file. Well within the 8-file threshold. No smell.
|
||
2. Minimum set: The plan is already minimal. The wrapper, tests, and feature flag are all required for correctness and safe rollout.
|
||
3. Stated invariants and acceptance criteria are preserved as the baseline.
|
||
|
||
**0E. Temporal Interrogation**
|
||
|
||
```
|
||
HOUR 1 (foundations): Implementer needs: exact LRU adapter API (get/set/delete
|
||
return contracts, synchronous guarantees, bypass semantics)
|
||
HOUR 2-3 (core logic): Ambiguity: post-await guard needed before cache.set (see
|
||
Section 1 finding). Without it the stale-re-insertion race
|
||
silently violates the stated ordering invariant.
|
||
HOUR 4-5 (integration): Surprise: feature flag applies per-key to BOTH read and write
|
||
paths consistently — confirm flag evaluation is idempotent per key.
|
||
HOUR 6+ (polish/tests): Need explicit test for write-during-in-flight-read scenario.
|
||
```
|
||
|
||
(CC+gstack compresses these 6 human hours to ~20-30 minutes of wall time.)
|
||
|
||
---
|
||
|
||
## Section 1: Architecture Review
|
||
|
||
### System Architecture
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ profile-summary service (single process) │
|
||
│ │
|
||
│ ┌──────────────┐ readProfile/writeProfile ┌───────────┐ │
|
||
│ │ Callers │ ─────────────────────────► │ Wrapper │ │
|
||
│ └──────────────┘ │ (NEW) │ │
|
||
│ └─────┬─────┘ │
|
||
│ ┌───────────────┤ │
|
||
│ │ │ │
|
||
│ ┌────▼────┐ ┌────▼────┐ │
|
||
│ │ LRU │ │ Repos- │ │
|
||
│ │ Cache │ │ itory │ │
|
||
│ │(adapter)│ │(existing│ │
|
||
│ └─────────┘ └────┬────┘ │
|
||
│ │ │
|
||
│ ┌─────▼─────┐ │
|
||
│ │ Database │ │
|
||
│ └───────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
Coupling: The wrapper couples callers to the LRU adapter. Before: callers → repository → DB. After: callers → wrapper → (cache OR repository) → DB. The added coupling is justified: the adapter is an internal implementation detail, not a public contract.
|
||
|
||
Scaling: The LRU is bounded at 1000 entries / 16 MiB. Under 10x load, cache hit rate stays constant (hot keys are stable); DB load drops proportionally. Under 100x load, the 1000-entry limit may not hold all hot keys — but that is explicitly out of scope for this plan and properly left to a future distributed-cache plan.
|
||
|
||
Single points of failure: The cache is one. It fails open (bypass mode) per the plan.
|
||
|
||
Rollback posture: Disable the runtime feature flag. Instant bypass — no code change or deploy required.
|
||
|
||
### Data Flow — All Four Paths
|
||
|
||
```
|
||
READ FLOW:
|
||
KEY ──► cache.get(key)
|
||
│
|
||
┌──────┴──────────────────────────────┐
|
||
│ hit (cached !== undefined) │ miss (undefined)
|
||
▼ ▼
|
||
return cached single-flight.acquire(key)
|
||
│
|
||
await repository.read(key)
|
||
│
|
||
┌─────────────┼────────────────┐
|
||
│ nil/absent │ value │ error
|
||
▼ ▼ ▼
|
||
sentinel cache.set(key,v) typed API error
|
||
(10s TTL) return value bypass cache
|
||
|
||
WRITE FLOW:
|
||
KEY, UPDATE ──► await repository.write(key, update)
|
||
│
|
||
┌───────────┼───────────┐
|
||
│ success │ │ error
|
||
▼ │ ▼
|
||
cache.delete(key) │ typed API error (cache NOT deleted — preserved)
|
||
return saved │
|
||
│
|
||
```
|
||
|
||
### Critical Finding: Stale Re-insertion After Write Invalidation
|
||
|
||
**CRITICAL GAP** — The plan states: "Every read begun after that write completes must observe the committed version. TTL expiry is not a substitute for this rule." The proposed wrapper violates this invariant.
|
||
|
||
Race sequence (possible in the JS event loop at await boundaries):
|
||
|
||
```
|
||
t1: readProfile(key) → cache miss → single-flight → await DB read (suspends)
|
||
t2: writeProfile(key) → await DB write (suspends)
|
||
t3: DB write completes → cache.delete(key) → writeProfile returns
|
||
t4: DB read (from t1) completes → returns OLD snapshot
|
||
t5: cache.set(key, OLD_VALUE) ← stale value re-inserted after invalidation!
|
||
t6: next readProfile(key) → cache HIT → returns stale value ← INVARIANT VIOLATED
|
||
```
|
||
|
||
The plan says "no additional version checks or coordination between a cache fill and a write are proposed," but the stated ordering invariant requires exactly that guard to prevent t5.
|
||
|
||
Fix: add a synchronous post-await check before cache.set. Because cache.get and cache.set are synchronous (no await), the check+set pair is atomic in the JS event loop — the write's cache.delete cannot interleave between them:
|
||
|
||
```javascript
|
||
async function readProfile(key) {
|
||
const cached = cache.get(key);
|
||
if (cached !== undefined) return cached;
|
||
const value = await repository.read(key);
|
||
// Guard: only cache if no write invalidated this key while we were reading DB.
|
||
// Safe because cache.get and cache.set are synchronous — write's cache.delete
|
||
// cannot interleave between these two lines.
|
||
if (cache.get(key) === undefined) {
|
||
cache.set(key, value);
|
||
}
|
||
return value;
|
||
}
|
||
```
|
||
|
||
**Auto-decided (D1):** Add post-await guard to readProfile. Recommended option accepted.
|
||
|
||
### State Machine: Cache Entry
|
||
|
||
```
|
||
[DISABLED] ──(flag enable)──► [EMPTY]
|
||
▲ │
|
||
│ (flag disable) (first read for key)
|
||
│ │
|
||
│ ▼
|
||
│ [MISS/BYPASS]
|
||
│ │
|
||
│ (DB read completes)
|
||
│ │
|
||
│ ▼
|
||
│ [CACHED] ◄──── (subsequent reads)
|
||
│ │
|
||
│ (write or eviction or TTL)
|
||
│ │
|
||
│ ▼
|
||
│ [INVALIDATED]
|
||
│ │
|
||
│ (next read: miss → DB)
|
||
│ ▼
|
||
│ [CACHED again]
|
||
│
|
||
└────── (cache failure → BYPASS until reinitialized)
|
||
```
|
||
|
||
Invalid transitions prevented: write to absent key cannot create cache entry (write only deletes); sentinel entries expire on 10s TTL, not promoted to normal entries.
|
||
|
||
### Security Architecture
|
||
|
||
Auth boundary: authentication and authorization happen before repository access. The wrapper sits inside that boundary. Keys encode tenant ID and profile ID — the wrapper cannot be used to bypass authorization. No new attack surface.
|
||
|
||
---
|
||
|
||
## Section 2: Error & Rescue Map
|
||
|
||
| METHOD/CODEPATH | WHAT CAN GO WRONG | EXCEPTION CLASS |
|
||
|---|---|---|
|
||
| readProfile | cache.get throws | CacheAdapterError |
|
||
| readProfile | repository.read rejects | typed DBError |
|
||
| readProfile | cache.set throws | CacheAdapterError |
|
||
| writeProfile | repository.write rejects | typed DBError |
|
||
| writeProfile | cache.delete throws | CacheAdapterError |
|
||
|
||
| EXCEPTION CLASS | RESCUED? | RESCUE ACTION | USER SEES |
|
||
|---|---|---|---|
|
||
| CacheAdapterError (any op) | Y | Adapter enters bypass mode; all subsequent ops go to DB until cache is reinitialized | Transparent — no degraded response |
|
||
| typed DBError (read) | Y | Existing typed error mapping; bypass unchanged | Existing error response |
|
||
| typed DBError (write) | Y | Existing typed error mapping; cache NOT deleted (failed-write preservation) | Existing error response |
|
||
|
||
No catch-all handlers. No silent swallows. All cache failures are non-blocking by design (bypass mode). Repository error handling is unchanged.
|
||
|
||
**Section 2 result: 5 paths mapped, 0 CRITICAL GAPS.**
|
||
|
||
---
|
||
|
||
## Section 3: Security & Threat Model
|
||
|
||
| THREAT | LIKELIHOOD | IMPACT | MITIGATED? |
|
||
|---|---|---|---|
|
||
| Cross-tenant cache access | Low | High | YES — keys encode authenticated tenant ID; no ambiguity |
|
||
| Cache poisoning via key manipulation | Low | High | YES — keys derived from validated inputs; wrapper doesn't accept raw user input |
|
||
| PII leakage in logs | Low | High | YES — plan explicitly excludes raw IDs from monitoring |
|
||
| Stale data serving sensitive fields | Med | Med | PARTIAL — stale re-insertion gap (Section 1) closes with T1 fix |
|
||
| New attack surface (endpoints) | N/A | N/A | NO new endpoints introduced |
|
||
|
||
No new secrets, no new dependencies, no injection vectors. The stale-data risk from the re-insertion gap is mitigated by T1.
|
||
|
||
**Section 3 result: 0 new attack vectors. 1 stale-data risk closed by T1.**
|
||
|
||
---
|
||
|
||
## Section 4: Data Flow & Interaction Edge Cases
|
||
|
||
```
|
||
READ DATA FLOW:
|
||
KEY ──► VALIDATE (auth layer, pre-wrapper) ──► CACHE LOOKUP
|
||
│ │
|
||
▼ ┌──────┴──────┐
|
||
[nil?] undefined = cache miss HIT MISS
|
||
[empty?] key cannot be empty (validated) │ │
|
||
[wrong?] upstream validated return await DB
|
||
cached read
|
||
│
|
||
┌────┴────┐
|
||
value absent
|
||
│ │
|
||
post-await sentinel
|
||
guard check (10s TTL)
|
||
│
|
||
cache.set
|
||
(if key still absent)
|
||
```
|
||
|
||
**Async ordering edge cases:**
|
||
|
||
| SCENARIO | HANDLED? | HOW |
|
||
|---|---|---|
|
||
| Concurrent reads for same key (miss) | YES | Single-flight coalesces |
|
||
| Write during in-flight read (same key) | GAP → FIXED by T1 | Post-await guard prevents stale re-insertion |
|
||
| Write after read completes (normal ordering) | YES | cache.delete removes stale value before next read |
|
||
| Multiple writes to same key in sequence | YES | Each write deletes; final state is post-last-write TTL miss |
|
||
| Adapter failure during read | YES | Bypass mode; read goes to DB |
|
||
| Adapter failure during write delete | YES | Bypass mode; subsequent reads miss to DB |
|
||
| Feature flag disabled mid-request | YES | Both read and write bypass; no partial state |
|
||
|
||
**No UI edge cases** — backend-only change.
|
||
|
||
**Section 4 result: 1 edge case mapped (stale re-insertion), covered by T1 fix.**
|
||
|
||
---
|
||
|
||
## Section 5: Code Quality Review
|
||
|
||
The proposed code is 10 lines across 2 functions. Review:
|
||
|
||
- Organization: wrapper is a thin shell around existing repository — correct pattern; fits existing structure.
|
||
- DRY: no repetition. Single-flight and LRU are reused, not rebuilt.
|
||
- Naming: `readProfile`, `writeProfile`, `key`, `value`, `saved`, `cached` — all intention-revealing.
|
||
- Error handling: relies on adapter bypass semantics; no catch-all.
|
||
- Cyclomatic complexity: each function has 1-2 branches. Well within limit.
|
||
- Over-engineering: none. No unnecessary abstractions.
|
||
- Under-engineering: the missing post-await guard (addressed in T1) is the sole fragility.
|
||
|
||
**Section 5 result: 0 issues beyond the T1 finding from Section 1.**
|
||
|
||
---
|
||
|
||
## Section 6: Test Review
|
||
|
||
### Test Diagram
|
||
|
||
```
|
||
NEW DATA FLOWS:
|
||
- Read path: cache hit (all TTL states)
|
||
- Read path: cache miss → DB → cache.set
|
||
- Read path: cache miss → DB → post-await guard blocks stale re-insertion (T2: MISSING)
|
||
- Write path: success → cache.delete
|
||
- Write path: failure → cache NOT deleted (preserved)
|
||
- Concurrent reads: single-flight coalescing
|
||
- Adapter failure: bypass mode on any op
|
||
|
||
NEW CODEPATHS:
|
||
- cache.get → hit branch
|
||
- cache.get → miss branch → DB → cache.set
|
||
- cache.get → miss branch → DB → post-await guard (key no longer absent → skip set)
|
||
- cache.delete on write success
|
||
- cache.delete skipped on write failure
|
||
- adapter bypass on cache error
|
||
|
||
NEW BACKGROUND JOBS / ASYNC WORK:
|
||
- None
|
||
|
||
NEW INTEGRATIONS / EXTERNAL CALLS:
|
||
- None (existing DB calls, no new integrations)
|
||
|
||
NEW ERROR/RESCUE PATHS:
|
||
- CacheAdapterError → bypass mode (Section 2)
|
||
```
|
||
|
||
### Coverage Gap
|
||
|
||
The plan lists these test cases: hit/miss, eviction/byte limits, TTL, adapter-failure fallback, successful-write invalidation, failed-write preservation, concurrent-miss coalescing.
|
||
|
||
**Missing:** write-during-in-flight-read. This is the specific interleaving that produces the stale re-insertion (Section 1). Without it, the post-await guard added by T1 is untested.
|
||
|
||
Test spec header:
|
||
```javascript
|
||
describe('readProfile — write-during-in-flight-read', () => {
|
||
it('does not cache the stale value when a write invalidates the key during a DB read', async () => {
|
||
// Arrange: pause the DB read mid-flight using a controlled promise
|
||
// Act: start readProfile → suspend at DB read → run writeProfile → complete DB read
|
||
// Assert: cache.get(key) returns undefined after both settle
|
||
// next readProfile goes to DB (not stale cache hit)
|
||
});
|
||
});
|
||
```
|
||
|
||
**Auto-decided (D2):** Add test spec for write-during-in-flight-read. Recommended option accepted.
|
||
|
||
### Test Ambition Check
|
||
|
||
- "Ship at 2am on a Friday" test: the write-during-in-flight-read test (T2) is that test — it catches the silent stale-read failure.
|
||
- "Hostile QA" test: force adapter failure mid-sequence; verify bypass, then re-enable and verify empty cache.
|
||
- Chaos test: cycle the feature flag rapidly under concurrent read/write load; assert no stale reads escape.
|
||
|
||
**Section 6 result: 1 gap (write-during-in-flight-read test). Addressed by T2.**
|
||
|
||
---
|
||
|
||
## Section 7: Performance Review
|
||
|
||
| DIMENSION | ASSESSMENT |
|
||
|---|---|
|
||
| N+1 queries | None introduced. Cache reduces DB calls, not adds. |
|
||
| Memory | 16 MiB cap is explicit; 900 hot key DTOs are small; bounded. |
|
||
| DB indexes | No new queries; existing indexes unchanged. |
|
||
| Caching opportunities | This plan IS the caching. All hot reads covered. |
|
||
| Slow paths | Cold start: 900 cache misses on restart. Spread over time by single-flight coalescing and rolling traffic. Plan claims within existing DB capacity — reasonable given 900 reads over 30s TTL window. |
|
||
| Connection pool | No new connections. Cache eliminates DB round-trips. |
|
||
|
||
Cold-start analysis: 900 hot keys × average read latency ≈ 900 DB reads over the first ~30 seconds of traffic. Current DB handles ~N reads/s at 70% CPU. If N >> 30, cold start is within capacity. Plan states this explicitly — the service owner should verify against the current read rate during rollout monitoring.
|
||
|
||
**Section 7 result: 0 issues. Cold start assertion is a monitoring checkpoint, not a plan gap.**
|
||
|
||
---
|
||
|
||
## Section 8: Observability & Debuggability Review
|
||
|
||
**Metrics specified in plan:** hit/miss rate, eviction rate, cache bytes, fallback errors, DB CPU, read p95. This is complete for the acceptance criteria.
|
||
|
||
**Gap: alert thresholds not specified.**
|
||
|
||
The plan says "monitor" but doesn't define when the service owner should be paged vs. just watching. For a staged rollout with an active owner, manual monitoring is acceptable. But for ongoing operation post-100% rollout, the missing thresholds become an operational gap.
|
||
|
||
Suggested thresholds (P3 TODO, not blocking ship):
|
||
- Page if: hit rate < 40% sustained for 5 minutes (cache not working)
|
||
- Page if: fallback error rate > 1% (adapter instability)
|
||
- Warn if: cache bytes > 14 MiB (near 16 MiB cap, possible eviction pressure)
|
||
|
||
**Logging:** secrets and raw IDs excluded per plan. Structured log lines at bypass entry/exit and eviction spikes would aid post-incident debugging. Not required for ship.
|
||
|
||
**Debuggability at 3 weeks post-ship:** With hit/miss, eviction, and fallback error metrics, you can reconstruct most incident scenarios. The stale-read scenario (before T1 fix) would have been invisible — this is why T1 is P1.
|
||
|
||
**Runbook for fallback errors:** Disable the feature flag → service reverts to direct DB → investigate adapter state → re-enable creates empty cache.
|
||
|
||
**Auto-decided (D3):** Alert thresholds as P3 TODO. Non-blocking.
|
||
|
||
**Section 8 result: 1 minor gap (alert thresholds). Addressed as P3 TODO (T3).**
|
||
|
||
---
|
||
|
||
## Section 9: Deployment & Rollout Review
|
||
|
||
| CHECK | STATUS |
|
||
|---|---|
|
||
| DB migration | None required. |
|
||
| Feature flag | YES — existing runtime flag; staged rollout 10% → 50% → 100% |
|
||
| Rollout order | Flag enable is the deploy; no schema changes, no migration sequencing needed. |
|
||
| Rollback plan | Disable flag → instant bypass → empty cache on re-enable. Full rollback < 1 minute. |
|
||
| Deploy-time risk window | Flag-gated; old and new behavior coexist cleanly. 10% of keys test the wrapper while 90% go direct. |
|
||
| Environment parity | Staging test assumed; plan says "existing DB capacity" — verify staging has representative load. |
|
||
| Post-deploy verification | Hit rate, fallback errors, DB CPU at each stage; one healthy hour before advancing. |
|
||
| Smoke tests | Existing contract tests; new wrapper tests (T1+T2) must pass before flag enable. |
|
||
|
||
**"10% of keys" flag semantics:** The rollout applies consistently to both read and write paths for a given key. This is the only correct interpretation — partial application would create inconsistency (reads cache, writes don't invalidate). Recommend confirming flag implementation applies to both paths per key in the same call.
|
||
|
||
**Section 9 result: 0 risks. Flag semantics worth verifying in implementation.**
|
||
|
||
---
|
||
|
||
## Section 10: Long-Term Trajectory Review
|
||
|
||
| DIMENSION | ASSESSMENT |
|
||
|---|---|
|
||
| Technical debt | Minimal. Thin wrapper, no new abstraction layer. |
|
||
| Path dependency | None. Plan explicitly preserves future replacement path ("without introducing a general cache framework now"). |
|
||
| Knowledge concentration | Plan is self-contained and clear. A new engineer reading it in 12 months will understand the problem, the invariant, and the fix. |
|
||
| Reversibility | **5/5** — feature flag disable reverts instantly to baseline. One-hour rollback, zero data risk. |
|
||
| Ecosystem fit | Standard read-through LRU pattern in JS. No unusual choices. |
|
||
| The 1-year question | Reads cleanly. Acceptance targets, invariants, and out-of-scope items are all explicit. |
|
||
|
||
Future trajectory: if the service scales to multiple processes, this plan's design makes that upgrade path clear — the "Out of scope" section names distributed caching and cross-process coherence as the next tier. The current interface can be replaced without changing callers.
|
||
|
||
**Section 10 result: Reversibility 5/5, 0 debt items.**
|
||
|
||
---
|
||
|
||
## Section 11: Design & UX Review
|
||
|
||
**SKIPPED** — no UI scope detected. This is a backend-only change with no user-visible interactions, screens, or state changes.
|
||
|
||
---
|
||
|
||
## Outside Voice
|
||
|
||
Codex review skipped (codex_reviews disabled). Re-enable: `gstack-config set codex_reviews enabled`.
|
||
|
||
---
|
||
|
||
## Required Outputs
|
||
|
||
### NOT in scope (considered and deferred)
|
||
|
||
| Item | Rationale |
|
||
|---|---|
|
||
| Distributed caching | Out of stated scope; single-process design is the constraint |
|
||
| Cross-process coherence | Explicitly excluded; multi-process startup is rejected by design |
|
||
| Cache prewarming | Not needed; hot keys warm within first TTL window via normal traffic |
|
||
| Changing consistency semantics | No requirement; existing read-through model is sufficient |
|
||
| New product surfaces (API, UI, schema) | Backend-only change; no new surfaces planned |
|
||
| General cache framework | Would over-engineer; specific adapter is the right size for this problem |
|
||
| Alert threshold automation | P3 TODO; manual monitoring sufficient during rollout and initial operation |
|
||
|
||
### What already exists
|
||
|
||
| Component | Status |
|
||
|---|---|
|
||
| LRU adapter (1000 entries, 16 MiB, 30s TTL) | Reused unchanged |
|
||
| Single-flight miss coalescing | Reused unchanged |
|
||
| Runtime feature flag | Reused for staged rollout |
|
||
| Repository contract tests (tenant isolation, key validation, absence, DB failures, authorization) | Extended with wrapper tests |
|
||
| Tenant ID + profile ID key encoding | Unchanged; wrapper inherits this property |
|
||
| Typed API error mapping | Unchanged; wrapper preserves these error responses |
|
||
|
||
### Dream state delta
|
||
|
||
```
|
||
CURRENT STATE THIS PLAN DELTA 12-MONTH IDEAL
|
||
DB CPU 70% ---> DB CPU < 50% ---> Stable at < 50% under 10x traffic growth
|
||
read p95 120ms read p95 < 60ms read p95 < 60ms sustained
|
||
all 900 hot reads 60%+ hot reads served full hot-key coverage; optional
|
||
hit DB directly from process memory distributed cache if multi-process
|
||
needed (separate plan)
|
||
```
|
||
|
||
This plan closes the gap from current state to 12-month ideal for the single-process constraint. The distributed-cache path is preserved, not foreclosed.
|
||
|
||
### Error & Rescue Registry
|
||
|
||
| METHOD | FAILURE | EXCEPTION | RESCUED? | RESCUE ACTION | USER SEES |
|
||
|---|---|---|---|---|---|
|
||
| readProfile | cache.get throws | CacheAdapterError | YES | Adapter enters bypass; DB read proceeds | Transparent |
|
||
| readProfile | DB read rejects | typed DBError | YES | Existing typed error mapping | Existing error response |
|
||
| readProfile | cache.set throws | CacheAdapterError | YES | Adapter enters bypass; read returns DB value | Transparent |
|
||
| writeProfile | DB write rejects | typed DBError | YES | Existing typed error mapping; cache NOT deleted | Existing error response |
|
||
| writeProfile | cache.delete throws | CacheAdapterError | YES | Adapter enters bypass; subsequent reads go to DB | Transparent |
|
||
|
||
### Failure Modes Registry
|
||
|
||
| CODEPATH | FAILURE MODE | RESCUED? | TEST? | USER SEES | LOGGED? |
|
||
|---|---|---|---|---|---|
|
||
| readProfile | Cache adapter failure | YES | YES (adapter-failure fallback) | Nothing (transparent) | YES (bypass entry) |
|
||
| readProfile | DB read error | YES | YES (existing contract tests) | Existing error response | YES (existing) |
|
||
| readProfile | Stale re-insertion after write | NO (gap) → FIXED T1 | NO (gap) → ADDED T2 | Silent stale read | Not without T1+T2 |
|
||
| writeProfile | DB write error | YES | YES (failed-write preservation) | Existing error response | YES (existing) |
|
||
| writeProfile | Cache delete failure | YES | YES (adapter-failure fallback) | Nothing (transparent) | YES (bypass entry) |
|
||
| Feature flag disable | All cache ops bypassed | N/A | YES (existing flag tests) | Nothing (transparent) | YES (flag events) |
|
||
|
||
**CRITICAL GAP before T1+T2:** Stale re-insertion after write is RESCUED=N, TEST=N, USER SEES=Silent. T1 and T2 close this gap.
|
||
|
||
### Diagrams Summary
|
||
|
||
Architecture, data flow, state machine, and error flow produced in sections above. Deployment sequence and rollback are feature-flag based and described in Section 9 prose (no sequence diagram needed — disable flag = rollback, one step).
|
||
|
||
### Stale Diagram Audit
|
||
|
||
No existing ASCII diagrams found in the plan or referenced files. This plan introduces 4 new diagrams (system architecture, read flow, state machine, cross-reference in Section 4). All current and consistent with the proposed implementation.
|
||
|
||
---
|
||
|
||
## Implementation Tasks
|
||
|
||
Synthesized from this review's findings. Each task derives from a specific finding above. Run with Claude Code; checkbox as you ship.
|
||
|
||
- [ ] **T1 (P1, human: ~30min / CC: ~5min)** — readProfile wrapper — Add post-await guard to prevent stale re-insertion after write invalidation
|
||
- Surfaced by: Section 1 (Architecture) — stale re-insertion race; write's cache.delete can execute between the DB read's await and cache.set, allowing an old snapshot back into cache
|
||
- Files: repository wrapper module (readProfile function)
|
||
- Verify: T2 test passes; run existing contract tests; confirm cache.get returns undefined for a key that was written while its read was in-flight
|
||
- Change: after `await repository.read(key)`, replace unconditional `cache.set(key, value)` with `if (cache.get(key) === undefined) { cache.set(key, value); }`
|
||
|
||
- [ ] **T2 (P2, human: ~1h / CC: ~10min)** — wrapper tests — Add test for write-during-in-flight-read scenario
|
||
- Surfaced by: Section 6 (Tests) — the post-await guard (T1) is untested without a controlled interleaving test
|
||
- Files: wrapper test file
|
||
- Verify: test fails without T1 fix; passes with it; is deterministic (no timing dependency — use controlled promise resolution)
|
||
- Test: pause readProfile at the DB read using a deferred promise; run writeProfile to completion; resolve DB read; assert cache.get(key) === undefined after both settle
|
||
|
||
- [ ] **T3 (P3, human: ~30min / CC: ~5min)** — observability — Define alert thresholds for ongoing cache operation post-rollout
|
||
- Surfaced by: Section 8 (Observability) — monitoring metrics are specified but alert thresholds are not; fine during active-owner rollout, operational gap post-stabilization
|
||
- Files: alerting config / runbook
|
||
- Thresholds: hit rate < 40% sustained 5 min → page; fallback error rate > 1% → page; cache bytes > 14 MiB → warn
|
||
|
||
_No new tasks from Sections 3, 5, 7, 9, 10, 11._
|
||
|
||
---
|
||
|
||
## Completion Summary
|
||
|
||
```
|
||
+====================================================================+
|
||
| MEGA PLAN REVIEW — COMPLETION SUMMARY |
|
||
+====================================================================+
|
||
| Mode selected | HOLD SCOPE |
|
||
| System Audit | SKIPPED (per run configuration) |
|
||
| Step 0 | HOLD SCOPE confirmed; Approach A selected |
|
||
| Section 1 (Arch) | 1 CRITICAL GAP (stale re-insertion) → T1 |
|
||
| Section 2 (Errors) | 5 error paths mapped, 0 GAPS |
|
||
| Section 3 (Security)| 0 issues found, 0 High severity |
|
||
| Section 4 (Data/UX) | 1 edge case mapped (stale re-insertion) |
|
||
| Section 5 (Quality) | 0 issues found |
|
||
| Section 6 (Tests) | Diagram produced, 1 gap (T2) |
|
||
| Section 7 (Perf) | 0 issues found |
|
||
| Section 8 (Observ) | 1 minor gap (alert thresholds → T3) |
|
||
| Section 9 (Deploy) | 0 risks flagged |
|
||
| Section 10 (Future) | Reversibility: 5/5, debt items: 0 |
|
||
| Section 11 (Design) | SKIPPED (no UI scope) |
|
||
+--------------------------------------------------------------------+
|
||
| NOT in scope | written (7 items) |
|
||
| What already exists | written (6 components) |
|
||
| Dream state delta | written |
|
||
| Error/rescue registry| 5 methods, 0 CRITICAL GAPS (post-T1) |
|
||
| Failure modes | 6 total, 1 CRITICAL GAP (closed by T1+T2) |
|
||
| TODOS.md updates | 1 item (T3, P3, alert thresholds) |
|
||
| Scope proposals | 0 proposed, 0 accepted (HOLD SCOPE) |
|
||
| CEO plan | skipped (HOLD SCOPE) |
|
||
| Outside voice | DISABLED (codex_reviews=disabled) |
|
||
| Lake Score | 3/3 recommendations chose complete option |
|
||
| Diagrams produced | 4 (architecture, read flow, state machine, |
|
||
| | data flow with shadow paths) |
|
||
| Stale diagrams found | 0 |
|
||
| Unresolved decisions | 0 (D1, D2, D3 all auto-decided) |
|
||
+====================================================================+
|
||
```
|
||
|
||
**Auto-decided decisions (no human present):**
|
||
- D1: Add post-await guard (stale re-insertion fix) → recommended option accepted → T1
|
||
- D2: Add write-during-in-flight-read test → recommended option accepted → T2
|
||
- D3: Alert thresholds as P3 TODO → recommended option accepted → T3
|
||
|
||
---
|
||
|
||
## GSTACK REVIEW REPORT
|
||
|
||
| Review | Trigger | Why | Runs | Status | Findings |
|
||
|--------|---------|-----|------|--------|----------|
|
||
| CEO Review | `/plan-ceo-review` | Scope & strategy | 1 | issues_open | mode: HOLD_SCOPE, 1 critical gap (stale re-insertion → T1+T2) |
|
||
| Outside Review | disabled | Independent 2nd opinion | 0 | DISABLED | codex_reviews=disabled per config |
|
||
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 0 | — | — |
|
||
| Design Review | `/plan-design-review` | UI/UX gaps | 0 | — | — |
|
||
| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | — | — |
|
||
|
||
**OUTSIDE COVERAGE:** codex_reviews=disabled; outside review step skipped per isolated config. Re-enable: `gstack-config set codex_reviews enabled`.
|
||
|
||
**VERDICT:** CEO Review complete — 1 critical gap found and addressed (T1: post-await guard; T2: interleaving test). Eng review required before shipping.
|
||
|
||
NO UNRESOLVED DECISIONS
|