mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-19 09:45:07 +00:00
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:
@@ -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 ────────
|
||||
|
||||
Reference in New Issue
Block a user