ADR 0005 Amendment 6 (D34) deferred streaming-path D4 singleflight to v1.x with the note "the design alone warrants a dedicated ADR." Round-6 cold-audit F13 (issue #16) raised the sibling TOCTOU window between server.mjs's preCheckHit peek and the streaming-branch spawn. D42 fulfils Amendment 6's deferral note by ratifying the v1.x design as ADR 0005 Amendment 8 — design only, no implementation. **Design ratified (ADR 0005 Amendment 8, 14 sections):** 1. New `cacheStore.getOrComputeStreaming(keyId, cacheKey, sourceFactory)` API with `{ stream, isFirst }` return shape. Mirrors the buffered path's `getOrCompute` to keep the cache API surface coherent. 2. `StreamingInflightEntry` shape — source iterator + AbortController + accumulated-chunks replay buffer + attached-clients Set + state flags + D38 spawn-slot tracker. 3. `AttachedClient` shape — per-client tee buffer + byte-size meter + late-joiner replay flag + done/resolveNext/rejectNext for the single-reader-multi-writer tee. 4. Tee fan-out loop (one reader drains source, fans chunks to all attached clients). 5. Late-joiner replay policy — accumulated chunks burst-drained on attach. 6. Cache TTL race during inflight — late joiners see the inflight entry and join; expired cache slot is overwritten by inflight completion. 7. D38 maxConcurrent coordination — only first caller acquires; release fires once on source-complete/error/abort. 8. New `STREAM_BACKPRESSURE` error code. NOT a hard trigger. Affected client gets synthetic `{ type: 'stop', finish_reason: 'length' }` + `[DONE]` (matches D35 #10 truncation marker). 9. Mid-stream disconnect — remove from attached set; if 0 remaining, abort source via AbortController. 10. Replay buffer cap (10 MB, matches D23 cache-entry cap) — over cap marks entry not cacheable, late joiners get backpressure error. 11. Observability — 4 new log events (streaming_inflight_join / source_done / abort, stream_backpressure_disconnect) + new `X-OLP-Streaming-Inflight: source | attached | solo` header. 12. Server.mjs wiring — replaces the current peek+spawn pattern; TOCTOU window closes because Map check+insert is synchronous. 13. Test surface (when implementation lands) — 10 scenarios covering single-client / 2-concurrent / 3-concurrent / mid-stream join / first-disconnect / all-disconnect / source-error / backpressure / D38 coordination / TTL race / replay cap / X-OLP-* header values. 14. Defaults — PER_CLIENT_QUEUE_CAP=1MB, ACCUMULATED_REPLAY_CAP=10MB, STREAM_BACKPRESSURE not in HARD_TRIGGER_CODES. **Multi-layer safeguards (the maintainer asked: "保证后面这一块会被处理而不会被忽略"):** 1. **`docs/v1x-roadmap.md` (NEW)** — single living landing page for every Phase-1 deferral. 7 items at D42: - #1 Streaming SF (this amendment) - #2 Multi-key auth (lib/keys.mjs) - #3 Soft trigger reactivation (ADR 0004 Amendment 2) - #4 /health activeSpawns integration (D38) - #5 Provider-level cacheKeyFields mask (ADR 0005 Amendment 7) - #6 Streaming-path SPAWN_FAILED salvage - #7 AUTH_MISSING tuple test coverage (D40 follow-up) Each entry names the ratifying ADR, load-bearing code anchor (file:line), GitHub issue (if any), concrete start trigger, and estimated effort. Maintainer's session-startup discipline grep this file at sprint kickoff. 2. **Issue #16 STAYS OPEN** — not closed in D42. Body updated post- commit to reference Amendment 8 with status "design ratified; implementation pending." Do not close until §13 test surface is green on actual implementation. 3. **`lib/cache/store.mjs#getOrCompute` JSDoc** — TODO comment for the sibling streaming API pointing at Amendment 8 + v1x-roadmap.md #1. 4. **`server.mjs` streaming-branch entry (~line 810)** — TODO comment block citing Amendment 8 + issue #16 + roadmap.md #1, naming the exact code lines the v1.x impl will replace. 5. **`README.md § Known limitations` section** — new subsection surfaces 4 limitations to users (streaming SF / soft triggers / multi-key auth / cacheKeyFields mask), each linking to the v1x-roadmap.md entry. 6. **Amendment 8 § "Cross-references and safeguards"** — explicit cross-link block enumerating the above 4 anchors so a future ADR-only reader knows every breadcrumb. **Maintainer decision recorded:** Option 1 (design ADR only) chosen over Option 2 (design + implementation now). Rationale: 200-400 lines of concurrency primitives + 15-20 tests is not "pre-Phase-2 cleanup" in shape — it is real v1.x feature work. Shipping streaming SF in a v0.1.1 patch release would muddy the Phase 1 / Phase 2 contract that v0.1.0 ratified. Personal/family-scale load makes the deferral safe at v0.1. Changes (6 files, +165 / -0): - `docs/adr/0005-cache-cross-provider.md` — Amendment 8 prepended (133 lines). - `docs/v1x-roadmap.md` — NEW file (148 lines). - `lib/cache/store.mjs` — getOrCompute JSDoc gains TODO block (8 lines). - `server.mjs` — streaming-branch entry gains TODO block (6 lines). - `README.md` — Known limitations section (9 lines). - `CHANGELOG.md` — D42 sub-entry under Unreleased (9 lines). No code-behavior change. No new tests. No package.json bump (phase_rolling_mode). Authority: - ADR 0005 Amendment 8 (this commit) — design ratification - ADR 0005 Amendment 6 (D34) — original deferral with "design ADR needed" note that this commit fulfils - GitHub issue #16 (round-6 F13) — sibling TOCTOU; STAYS OPEN - ADR 0002 Amendment 6 (D38) — tryAcquireSpawn semantics - ADR 0004 Amendment 5 (D40) — observability pattern extension - CC 开发铁律 v1.6 § 10.x — design-only amendment; fresh-context reviewer not required per Iron Rule 10 implementation-phase scope - CLAUDE.md release_kit_overlay phase_rolling_mode — under Unreleased Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
10 KiB
OLP v1.x Roadmap — Deferred Work Tracker
Purpose. Single landing page for every Phase-1 deferral that an actual v1.x sprint must pick up. Each entry cross-references its ratifying ADR, its GitHub issue (if any), and the load-bearing code anchor so a future maintainer can resume without spelunking the commit history.
Status: Living document. Add new entries at the top. Each item should answer:
- What is deferred?
- Why was it deferred (link the ratifying ADR amendment).
- Where does the work live in the tree today (file + anchor).
- When does it need to land (trigger: load profile, security event, governance amendment).
Reading order for a v1.x sprint kickoff. Items #1–#3 are the most architecturally consequential and should be designed in dependency order: #2 (multi-key auth) blocks header gating in #1 and observability ownership in #4. #1 (streaming SF) blocks #5 (soft trigger reactivation) only if soft triggers are wired on streaming requests.
#1 — Streaming-path singleflight + TOCTOU close
- What.
cacheStore.getOrComputeStreaming(keyId, cacheKey, sourceFactory)API replacing the currentpeek + spawnpattern inserver.mjs. Per-(keyId, cacheKey) inflight Map with tee fan-out, bounded per-client backpressure queues, late-joiner replay buffer, AbortController propagation on all-disconnect. - Why deferred. Personal/family-scale single-tenant load — N concurrent identical streaming requests is an edge case that has not been reported. Each concurrent caller receives the correct response; the waste is N CLI processes instead of one.
- Design ADR (ratified).
docs/adr/0005-cache-cross-provider.mdAmendment 8 — full design including the inflight Map shape, tee policy, late-joiner replay, backpressure cap, D38 semaphore coordination, abort policy, cache TTL race handling, observability event set, and X-OLP-Streaming-Inflight header. Implementation acceptance criteria are in Amendment 8 §13. - Tracking issue. GitHub issue #16 — STAYS OPEN as v1.x tracker. Sibling: the closed-but-not-implemented Amendment 6 deferral (D34 F1).
- Code anchors today.
server.mjslines ~782 (preCheckHit = await cacheStore.peek(...)) and ~811–817 (streaming branch entry) — these are the lines the new API replaces.lib/cache/store.mjsgetOrCompute— sibling API; the new one mirrors its shape on the streaming path.
- Trigger to start. Any of: (a) report of N>1 concurrent identical streaming requests in the wild, (b) v1.x sprint planning kickoff with the maintainer explicitly opening this scope, (c) downstream feature requiring tee-streaming primitive (e.g., browser-side observer attaching to an existing stream).
- Estimated effort. Design ADR ratified (Amendment 8) = 30 min done. Implementation = 200–400 lines + 15-20 tests + fresh-context reviewer pass. ~3-4 hours of subagent runtime with full Iron Rule 10 discipline.
#2 — Multi-key auth (lib/keys.mjs)
- What. Per-API-key identity, namespace scoping for the cache, ownership tier (owner vs guest) for header gating, and audit log of which key issued which request.
- Why deferred. Phase 1 ships single-tenant — the cache layer carries
keyId='__anonymous__'(D5). No real user identity is needed for personal/family use today. - Design ADR (NOT YET RATIFIED). No design ADR exists yet. v1.x sprint must produce one before implementation.
- Tracking. Not a GitHub issue (no governance event filed for it). Tracked here + in
AGENTS.md § Key files to know(lib/keys.mjsmarked 📋 Planned). - Blocks.
X-OLP-Fallback-Detailowner-only gating (D40 / ADR 0004 Amendment 5 — currently ungated)./healthper-key visibility (currently anonymous-only).
- Code anchors today.
lib/cache/store.mjsper-keyId namespace Map — the data model is already keyed bykeyId; only the keyId source is hardcoded.server.mjsrequest handlers — thekeyId='__anonymous__'constant needs to be replaced by a header/token lookup.
- Trigger to start. First multi-user deployment of OLP (e.g., maintainer + spouse + child accessing the same instance with separate identities).
#3 — Soft trigger reactivation (ADR 0004 Amendment 2)
- What. Per-provider
quotaStatuspolling,softThresholdcomparisons, soft-skip advancement when quota approaches limit. CurrentlyevaluateSoftTriggersalways returnsfalsebecausequotaSnapshotis never populated. - Why deferred. v0.1 hard triggers (SPAWN_FAILED / CLI_NOT_FOUND / SPAWN_TIMEOUT / CONCURRENCY_LIMIT) are sufficient for fallback advancement at personal/family scale. Soft triggers require persistent quota snapshots and a polling mechanism, which adds operational surface (timer drift, snapshot staleness, observability burden).
- Design ADR.
docs/adr/0004-fallback-engine.mdAmendment 2 — explicit v1.x deferral with mitigations (startup warning if user configures soft thresholds without runtime enforcement). - Tracking. Not a GitHub issue. Tracked here + via the startup warning in
server.mjs(the_softTriggersConfiguredwarn emission). - Blocks.
- Issue #8 (
X-OLP-Provider-Usedchain-origin semantics) — Option A (trackfirstAttemptedProvider) becomes preferable once soft triggers can fire. See ADR 0004 Amendment 6 § v1.x re-evaluation. X-OLP-Fallback-Detailtrigger_type: 'soft'path — currently dead code, becomes live with this work.
- Issue #8 (
- Code anchors today.
lib/fallback/engine.mjsevaluateSoftTriggers(returns false unconditionally at v0.1).lib/providers/base.mjsProvider.quotaStatuscontract (declared but unused at v0.1).
- Trigger to start. First quota-rate-limit event in the wild — at which point the operator would want pre-emptive advancement rather than spawn-then-fail.
#4 — /health activeSpawns integration
- What. Surface D38
getActiveSpawnCount(providerName)per-provider on the/healthendpoint at the pathproviders.status.<name>.activeSpawns. - Why deferred. D38 (issue #1) shipped the runtime enforcement and exported
getActiveSpawnCount;/healthintegration was scoped out as forward-looking polish. - Design ADR.
docs/adr/0002-plugin-architecture.mdAmendment 6 — names the target path explicitly: "/healthintegration deferred — when surfaced there will land atproviders.status.<name>.activeSpawns; not wired at D38." - Tracking. Not a GitHub issue. Tracked here.
- Code anchors today.
lib/providers/index.mjsexportsgetActiveSpawnCountalready.server.mjs handleHealth— extension point for the new field.
- Trigger to start. First time the maintainer wants per-provider concurrency visibility for capacity planning.
#5 — Provider-level cacheKeyFields (per-plugin mask)
- What. Per-plugin declaration of which IR fields are actually consumed by the underlying CLI invocation, used by
computeCacheKeyto skip fields that the plugin drops at spawn. Reduces spurious-miss rate from the v0.1 conservative-posture trade-off (Amendment 7). - Why deferred. At personal/family scale the extra spawn cost from spurious misses is negligible. The contract extension adds complexity (per-plugin field set + plumbing through
buildDefaultChain→executeHopFn→computeCacheKey). - Design ADR.
docs/adr/0005-cache-cross-provider.mdAmendment 7 § Forward path. - Tracking. Not a GitHub issue. Tracked here.
- Code anchors today.
- Plugin file headers — each lists its "fields dropped at spawn" table for human reference; the v1.x amendment makes that table machine-readable.
lib/cache/keys.mjs computeCacheKey— would acceptpluginCacheKeyMaskparameter.
- Trigger to start. First time spurious-miss rate becomes a measurable load factor.
#6 — Streaming-path SPAWN_FAILED salvage
- What. Currently the streaming branch does NOT participate in D16 salvage (the salvage-on-SPAWN_FAILED + chunks pattern that the buffered path uses). Streaming SPAWN_FAILED mid-stream → the truncation marker (D35 #10) fires, but no salvage logic captures partial chunks for downstream cache reuse.
- Why deferred. Less impactful than #1 — at most one client benefits per spawn event, and the buffered path already provides salvage for the bulk of requests. Streaming is the minority path.
- Design ADR. Not yet ratified. Coordinated with #1 because the tee architecture changes the salvage semantics (multiple clients may want different finish_reason interpretations on source-mid-stream-failure).
- Tracking. Not a GitHub issue. Tracked here.
- Trigger to start. Bundled with #1 implementation work (the inflight tee architecture changes the salvage semantics, so designing them together is cheaper than serializing).
#7 — AUTH_MISSING tuple path test coverage (D40 follow-up)
- What. Dedicated test in
test-features.mjsSuite D40 that asserts thefallbackDetailtuple records the AUTH_MISSING path withtrigger_type: 'auth_missing'. D40 reviewer flagged this as the last gap in the engine-path matrix; code is structurally correct, just lacks an explicit pin. - Why deferred. Low priority — the AUTH_MISSING early-return branch has the tuple push BEFORE it (verified in D40 reviewer pass), so coverage is implicit via the other engine-path tests. A 3-line dedicated test would make the pin explicit.
- Design. No ADR needed. ~5-line test addition.
- Tracking. Not a GitHub issue. Tracked here.
- Trigger to start. Next routine test-suite hardening pass, OR when AUTH_MISSING handling is changed for any reason.
Adding a new entry
When a future D-day defers work, the deferring commit should:
- Always update this file with a new entry at the top.
- Always name the ratifying ADR amendment (or note "no ADR yet — future work needs one").
- Always name the load-bearing code anchor (
file:lineform preferred over symbolic names — the symbolic name can drift). - Always name a concrete trigger to start the work — vague triggers ("when needed") let entries rot.
- If the deferral has a GitHub issue, keep it OPEN and reference it here. If it does NOT, leave a note explaining why (e.g., "tracked here only — no external governance event filed").
The maintainer's session-startup discipline should grep this file at sprint kickoff. If an entry's "trigger to start" condition is met, it leaves this page and becomes a sprint item.