mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
fix+docs: D34 — FINAL batch (F1+F4+F7+F8); audit cadence stops
cold-audit catch from 2026-05-24 (round 6 — FINAL)
This is the closing D-day of a 24-day round-1→round-6 audit cycle.
After this commit + the 9 round-6 follow-up issue filings, no more
audit rounds. Trajectory R1=17 → R2=13 → R3=13 → R4=10 → R5=12 →
R6=14 — the method did not converge; owner chose Option A (focused
batch of most consequential items, then STOP).
Changes (6 files, +138 / -47):
**Code changes**
1. lib/cache/keys.mjs (+14/-?) — F4 P2 cache key array-field
normalization:
- New `normalizeArrayField` helper: `(Array.isArray(v) && v.length === 0) ? null : (v ?? null)`
- Applied to `tools` and `stop` in computeCacheKey
- Now `tools: []` and `tools` omitted produce IDENTICAL cache
keys (and same for `stop: []` vs omitted). ADR 0005 Amendment 2's
own claim that "[] and undefined share a cache entry" was
empirically FALSE pre-D34; round-6 reviewer verified hashes
differ. The fix makes the claim literally true at the
key-composition layer.
2. lib/providers/base.mjs (+16/-?) — F7 P2 dead error code removal:
- `QUOTA_EXHAUSTED` removed from PROVIDER_ERROR_CODES
- `RATE_LIMITED` removed from PROVIDER_ERROR_CODES
- v0.1 live codes: SPAWN_FAILED, CLI_NOT_FOUND, AUTH_MISSING,
SPAWN_TIMEOUT
- Comment block documents removal + cites ADR 0004 Amendment 3
3. lib/fallback/engine.mjs (+29/-?) — F7 P2 sibling:
- HARD_TRIGGER_CODES: QUOTA_EXHAUSTED + RATE_LIMITED removed
- SPAWN_FAILED, CLI_NOT_FOUND, AUTH_MISSING(false), SPAWN_TIMEOUT
remain
- evaluateHardTriggers HTTP-status branches KEPT (option (b)) with
forward-compat comment: "v0.1 plugins never attach statusCode;
branches reserved for v1.x when plugin gains HTTP-status parsing"
4. test-features.mjs (+93) — F4 + F7 test work:
- 4 new F4 regression tests (tools:[] vs undefined, stop:[] vs
undefined, tools:[] vs null, tools:non-empty vs undefined sanity)
- ~14 integration test code-swap edits (QUOTA_EXHAUSTED →
SPAWN_FAILED, RATE_LIMITED → SPAWN_FAILED) preserving original
hard-trigger semantic
- 2 dead unit tests for QUOTA_EXHAUSTED/RATE_LIMITED removed
(tombstone comment retained for audit trail)
**ADR amendments (docs-only, no code change)**
5. docs/adr/0004-fallback-engine.md (+12) — F7 Amendment 3:
- Documents the v0.1 hard-trigger code narrowing
- 4 live codes listed explicitly
- Captures evaluateHardTriggers HTTP-status branch retention rationale
- v1.x re-activation path: plugin gains HTTP-status parsing →
re-add codes → branches activate naturally
6. docs/adr/0005-cache-cross-provider.md (+21) — TWO amendments + 1
prior-amendment update:
- **Amendment 6 (F1 P1)**: Formal v1.x deferral of D4 streaming
singleflight. Buffered path (executeHopFn) uses cacheStore.getOrCompute
and participates in D4 fully. Streaming cache-miss path
(server.mjs:609-741) bypasses singleflight — N concurrent identical
streamers each spawn fresh. v0.1 trade-off accepted for
personal/family scale; v1.x design ADR needed for tee-streaming +
per-key inflight Map. Cross-references CLAUDE.md release_kit.
phase_rolling_mode as the deferral pattern precedent.
- **Amendment 7 (F8 P2)**: Documents the v0.1 conservative cache-key
posture: includes all IR fields including those plugins discard
(anthropic/codex/mistral drop temperature/max_tokens/top_p/stop/
tools/tool_choice at spawn). Consequence: 2 requests with different
temperature produce identical CLI output (CLI ignores) but
different cache keys → spurious miss. Trade-off justified:
spurious miss > spurious hit. v1.x forward path:
per-plugin cacheKeyFields contract extension (ADR 0002 amendment
needed). 3 implementation subtasks enumerated for the v1.x PR.
- **Amendment 2 update (F4)**: heading renamed to "Note on
null-coalescing AND array normalization"; body documents the
new normalizeArrayField helper; quotes the regression test name.
Tests: 414 → 416 (+4 F4 regression, -2 F7 dead, +0 net from F7
integration rewrites).
Pre-commit fold-in: NONE — D34 reviewer APPROVE with all 4 suggestions
non-blocking/cosmetic.
Authority:
- ADR 0005 Amendment 2 invariant restored at code level (F4)
- ADR 0005 Amendment 6 formalizes F1 streaming-singleflight deferral
per the same pattern as D22 ADR 0004 Amendment 2 soft-trigger
deferral
- ADR 0005 Amendment 7 documents F8 conservative posture as v0.1
intentional design (not an accident)
- ADR 0004 Amendment 3 narrows v0.1 trigger taxonomy (F7)
- Round-6 cold audit findings F2 / F3 / F6 / F9 / F10 / F11 / F12 /
F13 / F14 filed as GitHub issues after this commit (NOT in scope)
- CC 开发铁律 v1.6 § 10.x — final round of the audit cadence
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Critical depth checks:
- B5 over-normalization: verified `normalizeArrayField` only applies
to `tools` and `stop`; `response_format: {}` and `tool_choice: ''`
unaffected (Array.isArray guard)
- C10 test cleanup: 21 references reconciled (3 tombstone, 18
rewrites/removals); integration test rewrites preserve hard-trigger
semantics (QUOTA_EXHAUSTED → SPAWN_FAILED is also a hard trigger,
so fallback advancement behavior unchanged)
---
**End of audit cycle.** 24 D-days shipped from D10 (P1 hardening) through
D34 (final batch). 6 cold-audit rounds executed; 78+ findings raised;
~50 closed via implementation; ~28 deferred to GitHub issues / v1.x
ADR amendments. v0.1 tag remains explicit-maintainer-action per
phase_rolling_mode policy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Vendored
+12
-2
@@ -235,11 +235,21 @@ export function computeCacheKey(provider, model, ir, _options = {}) {
|
||||
// path in server.mjs side-channels through the raw body to compensate. Do
|
||||
// NOT remove the slot — once IR carries cache_control, this key composition
|
||||
// is already correct.
|
||||
// F4 (ADR 0005 Amendment 2 clarification, D34): normalize array-typed fields so
|
||||
// that [] (explicit empty) and undefined/null (absent) produce the same key.
|
||||
// `tools: []` and `tools` omitted both mean "no tools available" — they produce
|
||||
// identical model output and must collide on the same cache entry.
|
||||
// `stop: []` similarly means "no stop sequences" — equivalent to absent.
|
||||
// Per Amendment 2: "The normalization step at computeCacheKey collapses [] to null
|
||||
// for array-typed fields (tools, stop) so the claim holds at the key-composition layer."
|
||||
const normalizeArrayField = (v) =>
|
||||
(Array.isArray(v) && v.length === 0) ? null : (v ?? null);
|
||||
|
||||
const keyObj = {
|
||||
provider,
|
||||
model,
|
||||
messages: normalized,
|
||||
tools: ir.tools ?? null,
|
||||
tools: normalizeArrayField(ir.tools),
|
||||
temperature: ir.temperature ?? null,
|
||||
response_format: ir.response_format ?? null,
|
||||
cache_control: cacheControlMarkers.length > 0 ? cacheControlMarkers : null,
|
||||
@@ -247,7 +257,7 @@ export function computeCacheKey(provider, model, ir, _options = {}) {
|
||||
// Appended after the existing seven fields to avoid invalidating ordering-dependent keys.
|
||||
max_tokens: ir.max_tokens ?? null,
|
||||
top_p: ir.top_p ?? null,
|
||||
stop: ir.stop ?? null,
|
||||
stop: normalizeArrayField(ir.stop),
|
||||
tool_choice: ir.tool_choice ?? null,
|
||||
};
|
||||
|
||||
|
||||
+19
-10
@@ -26,20 +26,22 @@ import { computeIRRequestHash } from '../cache/keys.mjs';
|
||||
|
||||
/**
|
||||
* Maps ProviderError codes to hard-trigger decisions.
|
||||
* Per ADR 0004 § Decision § Trigger taxonomy — Hard triggers:
|
||||
* - QUOTA_EXHAUSTED → hard trigger
|
||||
* - RATE_LIMITED → hard trigger
|
||||
* - SPAWN_FAILED → hard trigger (provider CLI failed)
|
||||
* - CLI_NOT_FOUND → hard trigger (binary missing)
|
||||
* - AUTH_MISSING → NOT a hard trigger (user-config failure; user must fix)
|
||||
* - OUTPUT_PARSE_ERROR → removed (D32 F4): no plugin emits it; dead code.
|
||||
* Re-add via ADR 0004 amendment if needed.
|
||||
*
|
||||
* v0.1 live codes (per ADR 0004 Amendment 3, D34 F7):
|
||||
* - SPAWN_FAILED → hard trigger (provider CLI failed)
|
||||
* - CLI_NOT_FOUND → hard trigger (binary missing)
|
||||
* - AUTH_MISSING → NOT a hard trigger (user-config failure; user must fix)
|
||||
* - SPAWN_TIMEOUT → hard trigger (per ADR 0004 § Trigger taxonomy bullet 4)
|
||||
*
|
||||
* QUOTA_EXHAUSTED and RATE_LIMITED removed (D34 F7 / ADR 0004 Amendment 3):
|
||||
* no v0.1 plugin parses underlying-API HTTP status codes, so these codes
|
||||
* are never emitted at v0.1. Re-add via ADR 0004 amendment when a plugin
|
||||
* gains HTTP-status parsing capability.
|
||||
* OUTPUT_PARSE_ERROR removed earlier (D32 F4): no plugin emits it; dead code.
|
||||
*
|
||||
* @type {Record<string, boolean>}
|
||||
*/
|
||||
const HARD_TRIGGER_CODES = {
|
||||
QUOTA_EXHAUSTED: true,
|
||||
RATE_LIMITED: true,
|
||||
SPAWN_FAILED: true,
|
||||
CLI_NOT_FOUND: true,
|
||||
AUTH_MISSING: false, // user config problem — never fall over (ADR 0004 § Decision)
|
||||
@@ -120,6 +122,13 @@ export function evaluateHardTriggers(error, _providerHints = {}) {
|
||||
}
|
||||
|
||||
// HTTP status code present on the error object.
|
||||
// NOTE (ADR 0004 Amendment 3, D34 F7): these HTTP-status branches are
|
||||
// forward-compat reserved code. v0.1 plugins never attach statusCode to
|
||||
// thrown errors — they throw ProviderError with codes (SPAWN_FAILED,
|
||||
// SPAWN_TIMEOUT, CLI_NOT_FOUND, AUTH_MISSING) and never surface underlying-API
|
||||
// HTTP status. When a future plugin gains HTTP-status parsing, it will attach
|
||||
// statusCode to the thrown error and these branches will activate. Until then,
|
||||
// this path is unreachable in production. Left in place as intent signal.
|
||||
const status = error.statusCode ?? error.status ?? null;
|
||||
if (status !== null && typeof status === 'number') {
|
||||
// Client-side errors: never fall over (ADR 0004 § No fallback for client-side errors)
|
||||
|
||||
+11
-5
@@ -138,15 +138,21 @@ export function validateProvider(p) {
|
||||
|
||||
// ── Error class ───────────────────────────────────────────────────────────
|
||||
|
||||
/** Error codes surfaced by provider plugins */
|
||||
/**
|
||||
* Error codes surfaced by provider plugins.
|
||||
*
|
||||
* v0.1 live codes (per ADR 0004 Amendment 3, D34 F7):
|
||||
* SPAWN_FAILED, CLI_NOT_FOUND, AUTH_MISSING, SPAWN_TIMEOUT
|
||||
*
|
||||
* QUOTA_EXHAUSTED and RATE_LIMITED were removed (D34 F7): no plugin parses
|
||||
* underlying-API HTTP status codes at v0.1, so these codes are never emitted.
|
||||
* Re-add via ADR 0004 amendment when a plugin gains HTTP-status parsing.
|
||||
* (Previously removed: OUTPUT_PARSE_ERROR, D32 F4.)
|
||||
*/
|
||||
export const PROVIDER_ERROR_CODES = /** @type {const} */ ([
|
||||
'AUTH_MISSING',
|
||||
'QUOTA_EXHAUSTED',
|
||||
'RATE_LIMITED',
|
||||
'CLI_NOT_FOUND',
|
||||
'SPAWN_FAILED',
|
||||
// OUTPUT_PARSE_ERROR removed (D32 F4): no plugin emits it; dead code.
|
||||
// Re-add via ADR 0004 amendment if a future plugin surfaces parse failures.
|
||||
'SPAWN_TIMEOUT', // ADR 0004 § Trigger taxonomy bullet 4: spawn timeout is a hard trigger
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user