chore: D25 — round-2 P3 batch (F5/F6/F7/F9/F10/F11/F13 + D22 follow-up)

cold-audit catch from 2026-05-24

Final round-2 cold-audit cleanup batch. 8 small P3 items, batched per
Iron Rule 11 IDR cleanup-batch convention (precedent: D19 / D20). No
item exceeds ~35 lines; only F9 is a code change, the rest are docs +
registry updates.

Changes (7 files, +140 / -10):

1. ALIGNMENT.md (+8 / -2)
   - **F7**: Authority pin rows for anthropic / codex / mistral updated
     from "TBD on Phase-1 spawn" to the actual citations cited in each
     plugin's header:
     · anthropic — @anthropic-ai/claude-code v2.1.89 (OCP fork audit pin)
     · openai — https://developers.openai.com/codex/cli/reference + features URL
     · mistral — https://docs.mistral.ai/mistral-vibe/terminal/quickstart + config URL
     Plugins remain Candidate (per Provider Inventory) — D25 just removes the
     `TBD` marker; Enabled transition still requires Phase audit.
   - **F6**: New 3rd entry in § One-shot Triggered Audits — "OpenAI Codex ToS
     formal pin (trigger: Phase 2 E2E enable for Codex, OR 2026-12-31)".
     Closes the cross-reference from ADR 0006 § Decision table.

2. README.md (+4 / -2)
   - **F5**: Cache-key bullet (Architecture section) replaced the stale
     7-tuple with a link to ADR 0005 § Cache key composition + the
     post-D15 11-field tuple inlined.
   - **D22 follow-up**: "328-test suite" → "Comprehensive test suite
     covering IR, cache, fallback, and integration paths" (version-less
     to prevent re-drift on every D-day).

3. docs/adr/0002-plugin-architecture.md (+4 / -2) — **F11**: Amendment 1
   wording corrected. The original Authority line + maxSpawnTimeMs
   description said "fallback engine's spawn-timeout enforcement loop"
   — but the enforcement actually lives in each provider plugin's
   `_spawnAndStream` (setTimeout + proc.kill + reject pattern). The
   fallback engine merely treats SPAWN_TIMEOUT as a hard trigger per
   ADR 0004 § Trigger taxonomy bullet 4. Both wording sites updated.

4. docs/adr/0005-cache-cross-provider.md (+1) — **F13**: Amendment 2
   gains a "Note on null-coalescing collisions" paragraph documenting
   that the `?? null` serialization treats undefined / null / [] as
   equivalent cache keys for array-typed fields. Intentional — both
   `tools: []` and `tools` omitted semantically mean "no tools." If a
   future provider distinguishes empty-array vs absent, the serialization
   needs revision.

5. models-registry.json (+35) — **F10**: 5 candidate entries added for
   the providers ALIGNMENT.md names but registry omitted. All five with
   `candidate: true`, `models: []`, tier per ALIGNMENT.md inventory:
   · grok / kimi → Tier C
   · minimax / glm / qwen → Tier B
   Closes the release_kit overlay's "Supported Providers from
   models-registry.json" claim. alignment.yml KNOWN_PROVIDERS validation
   array already includes all 8 names, so registry → workflow validation
   continues to pass.

6. server.mjs (+20 / -6) — **F9**: Streaming success path now distinguishes
   stop-terminated vs exhausted-without-stop. Pre-D25 code unconditionally
   cached after the for-await loop ended, treating any exhaustion as
   "completed." Now: only cache if `lastChunk?.type === 'stop'`. If the
   generator exhausts without emitting stop (truncation), log
   `streaming_no_stop_chunk` warn event and do NOT persist. Mirrors
   D16's buffered-path semantics ("response completed successfully (no
   truncation, no error mid-stream)") with the simpler skip-write
   pattern (streaming path doesn't use getOrCompute → no singleflight
   eviction needed). D23's cacheableForFirstHop guard preserved as the
   outer condition.

7. test-features.mjs (+78) — F9 test 15e in Suite 15:
   - Mock provider whose spawn yields delta chunks then implicit-returns
     without stop (provider-injection pattern same as 15d — the real
     plugins synthesize a stop on clean proc exit so __setSpawnImpl can't
     simulate this case)
   - Asserts response succeeds AND second identical request triggers
     fresh spawn (proves no caching happened) AND X-OLP-Cache: miss on
     both responses

Tests: 348 → 349 (+1 from 15e). All pass on Node 20.

Authority:
- F5 → ADR 0005 § Cache key composition (post-D15 Amendment 2)
- F6 → ALIGNMENT.md self + ADR 0006 self-reference
- F7 → plugin headers (verified during D25 implementation)
- F9 → ADR 0005 § Cache write conditions item 1 + D16 truncation precedent
- F10 → ALIGNMENT.md § Provider Inventory tier classification
- F11 → ADR 0004 § Trigger taxonomy bullet 4 (the actual SPAWN_TIMEOUT
  hard-trigger documentation)
- F13 → ADR 0005 Amendment 2 (extends with the null-coalescing note)
- D22 fu → no spec authority; version-less framing prevents future drift
- CC 开发铁律 v1.6 § 10.x — Round-2 Cold Audit caught all 8 items

Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Independently verified F7 citations against
plugin headers (anthropic.mjs:5-6, codex.mjs:23/31, mistral.mjs:26/32);
F10 tier classifications against ALIGNMENT.md § Provider Inventory;
F6 cross-reference now self-consistent (ADR 0006 → ALIGNMENT.md);
alignment.yml workflow validation passes; F9 truncation semantics
mirror D16 buffered-path; test 15e correctly uses provider-injection
since real plugin synthesizes stop on clean exit.

Three non-blocking suggestions noted (README cache-key bullet slightly
verbose with both link + inline list; F9 could optionally synthesize
a finish_reason: 'length' stop chunk for client-visible truncation
observability; test 15e single-delta variant could be extended to
multi-delta). None folded in — all genuine polish, not correctness
gaps.

---

**Round-2 cold-audit cleanup complete.** 5 D-days (D21-D25 minus the
already-completed D24) closed all 13 round-2 findings (P2: F1/F2/F3/F4
in D21/D22/D23/D24; P3: F5-F13 distributed across D25 + earlier issues
#2/#3). v0.1 is one cold-audit-round-3 away from being ready to tag.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 14:56:05 +10:00
co-authored by Claude Opus 4.7
parent 7ef5510837
commit cd391b13ba
7 changed files with 140 additions and 10 deletions
+78
View File
@@ -5414,6 +5414,84 @@ describe('Streaming cache-miss real-time (Suite 15)', () => {
}
});
it('15e: streaming generator exhausted without stop chunk → response delivered but NOT cached (F9)', async () => {
// Authority: ADR 0005 § "Cache write conditions" item 1 — "response completed
// successfully (no truncation, no error mid-stream)". A generator that exhausts
// without emitting a stop chunk is a truncated response and must NOT be cached.
// Compare D16's buffered-path truncation eviction.
//
// Implementation note: __setSpawnImpl cannot produce a no-stop response because
// the anthropic provider plugin synthesizes a stop chunk on clean proc exit
// (lib/providers/anthropic.mjs line ~376: `yield anthropicStopToIR('stop')`).
// Instead we inject a custom provider whose spawn() async generator yields a
// delta chunk and then RETURNS without yielding a stop chunk, bypassing the
// plugin layer entirely. Same injection pattern as 15d.
//
// Verifies: (a) client receives the chunks (200); (b) the response is NOT
// written to cache — a second identical request triggers a fresh spawn (cache
// miss), proving no caching happened after request 1.
const { loadedProviders: lp15e, __clearCache: cc15e } = await import('./server.mjs');
const savedAnthropicProvider = lp15e.get('anthropic');
let spawnCallCount = 0;
const noStopProvider = {
...savedAnthropicProvider,
spawn: async function* () {
spawnCallCount++;
// Yield one delta chunk but NO stop chunk — generator exhausts here.
yield { type: 'delta', content: 'no-stop-content', finish_reason: null };
// (implicit return — no stop chunk emitted)
},
};
lp15e.set('anthropic', noStopProvider);
cc15e();
const makeRequest = () => new Promise((resolve, reject) => {
const req = httpRequest({
hostname: '127.0.0.1',
port: port15,
method: 'POST',
path: '/v1/chat/completions',
headers: { 'Content-Type': 'application/json' },
}, res => {
let data = '';
res.on('data', d => { data += d.toString(); });
res.on('end', () => resolve({ status: res.statusCode, body: data, headers: res.headers }));
res.on('error', reject);
});
req.on('error', reject);
req.write(JSON.stringify({
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: 'no-stop-chunk-f9-test' }],
stream: true,
}));
req.end();
});
try {
// First request: generator exhausts without stop → client receives response (200)
const r1 = await makeRequest();
assert.equal(r1.status, 200, `First request failed: ${r1.status} ${r1.body.slice(0, 200)}`);
assert.equal(spawnCallCount, 1, 'Spawn called once for first request');
assert.equal(r1.headers['x-olp-cache'], 'miss', 'First request must be cache miss');
// Second identical request: must ALSO be cache miss + fresh spawn (proves no caching after r1)
const r2 = await makeRequest();
assert.equal(r2.status, 200, `Second request failed: ${r2.status} ${r2.body.slice(0, 200)}`);
assert.equal(spawnCallCount, 2,
`Expected spawn called again for r2 (no-stop response must not be cached), got spawnCallCount=${spawnCallCount}`);
assert.equal(r2.headers['x-olp-cache'], 'miss', 'Second request must also be cache miss (no caching of truncated response)');
} finally {
// Restore the original anthropic provider
if (savedAnthropicProvider !== undefined) {
lp15e.set('anthropic', savedAnthropicProvider);
} else {
lp15e.delete('anthropic');
}
}
});
});
// ── Suite 17: D18 — /v1/models population + X-OLP-* headers on errors ────────