mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
fix: D26 — round-3 small batch (F16+F17+F18+F19)
cold-audit catch from 2026-05-24 (round 3)
4 small fixes batched per Iron Rule 11 IDR cleanup-batch convention.
None exceeds ~15 lines; mixed surfaces (server.mjs / 2 plugins /
plugin header / tests) but all P3-class and related by being honesty
fixes to claims the ADR/spec made but code didn't honor.
Changes (5 files, +546 / -13):
1. server.mjs — TWO changes:
- **F16 (startup warning)**: `logEvent` function moved earlier in
the file (was at line ~124, now at line ~56) so it's defined
before `_startupConfig` is loaded. After loading, check
`_startupConfig.soft_triggers` — if non-empty, emit
`logEvent('warn', 'soft_triggers_deferred_v1x', ...)` with the
configured provider names + a descriptive message citing ADR
0004 Amendment 2. Wires the mitigation that ADR 0004 § Mitigations
(original pre-Amendment-2 text) requires: "Soft triggers configured
against such providers issue a startup warning so the user knows
the trigger will never fire." D22 deferred soft triggers but
didn't re-implement this mitigation.
- **F19 (streaming truncation marker)**: in the stop-less exhaustion
branch of the real-streaming code path (post-D25 F9 — the no-cache
fix), write a synthetic `{type:'stop', finish_reason:'length'}`
SSE chunk via `irChunkToOpenAISSE(...)` BEFORE `res.write(SSE_DONE)`.
Guarded on `streamedChunks.length > 0` (emitting truncation on
a zero-content response would mislead the client). Mirrors the
buffered D16 path which already synthesizes the same marker.
The synthetic marker is `res.write`-only — NEVER pushed to
`streamedChunks` — so the D25 F9 no-cache invariant
(cache decision sees the original chunks array unchanged) is
structurally preserved.
2. lib/providers/codex.mjs + mistral.mjs — F17 stderr propagation:
the SPAWN_FAILED throw from the error-chunk-in-NDJSON path now
includes `accumulatedStderr.slice(0, 200)` suffix when non-empty.
Comment cites ADR 0004 § Chain advancement step 4 ("preserve the
client's ability to debug — the first failure is the load-bearing
signal").
**anthropic.mjs intentionally NOT modified**. Re-analysis confirmed
Anthropic's SPAWN_FAILED throws are from (a) process.on('error')
for binary-not-found (already includes OS-level err.message),
(b) SPAWN_TIMEOUT (separate code, D24 race fix), and (c) post-loop
exit-code (already includes `accumulatedStderr.slice(0, 300)`).
Anthropic uses `--output-format text`, so it has no NDJSON
error-chunk parsing path. The cold-audit was correct that the
error-chunk class only affects codex + mistral.
3. lib/providers/anthropic.mjs — F18 plugin header self-contained
D4 observation note. Pre-D26: ALIGNMENT.md anthropic Authority pin
row cited the plugin header for the OLP-side observation, and the
plugin header cited ALIGNMENT.md back — circular citation, no
party documented a fresh observation. Fix: plugin header now
carries the actual observation ("@anthropic-ai/claude-code v2.1.89
confirmed present at D4 implementation per ALIGNMENT.md Rule 5").
ALIGNMENT.md side unchanged (still points to plugin header — but
now points to a real artifact, not back to itself).
4. test-features.mjs — 9 new tests in 3 describe blocks:
- F16 ×3: soft_triggers non-empty → warn fires; empty → no warn;
undefined → no warn. Test uses inline simulation of the startup
code path (ESM module-eval can't be re-triggered per test process
without isolation gymnastics; inline simulation exercises the
same `Object.keys + length > 0 + logEvent('warn', ...)` shape).
- F17 ×3: codex with stderr → stderr appears in throw message;
codex without stderr → no suffix; mistral with stderr → suffix
- F19 ×3: partial-content + stop-less exhaustion → finish_reason='length'
marker before [DONE]; zero-content + stop-less exhaustion →
no marker (just [DONE]); D25 F9 invariant preserved (second
identical request triggers fresh spawn, X-OLP-Cache: miss)
Tests: 349 → 358 (+9). All pass on Node 20.
Authority:
- F16 → ADR 0004 § Mitigations (original) + ADR 0004 Amendment 2 (D22)
- F17 → ADR 0004 § Chain advancement step 4
- F18 → ALIGNMENT.md Rule 1 (Cite First) + Rule 5 (Cite in Commits)
- F19 → OpenAI Chat Completions streaming spec finish_reason enum
https://platform.openai.com/docs/api-reference/chat/streaming
- CC 开发铁律 v1.6 § 10.x — Round-3 Cold Audit caught all 4
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Verified critical concerns: (a) logEvent move is
net-zero (1 removed + 1 added; no duplicate); (b) F17 anthropic
non-application is justified by code-reading 3 SPAWN_FAILED sites in
anthropic.mjs (none is the NDJSON-error-chunk class); (c) F18 citation
chain no longer circular (verified BOTH endpoints); (d) F19 critical
no-cache invariant preserved (truncMarker is res.write-only, never
enters streamedChunks; verified both by code-path analysis and by F19
test 3 behavioral assertion).
3 non-blocking suggestions noted (F18 copy redundancy; F16 test linking
comment; F17 stderr slice-depth alignment) — all cosmetic, not folded.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,10 @@
|
||||
* `claude -p` from `@anthropic-ai/claude-code`.
|
||||
* OCP server.mjs inherits cli.js 2.1.89 audit pin at fork (per ALIGNMENT.md
|
||||
* § Provider authority pins).
|
||||
* D26 round-3 F18 observation: @anthropic-ai/claude-code v2.1.89 confirmed
|
||||
* present at D4 implementation (captured at D4 implementation per ALIGNMENT.md
|
||||
* Rule 5 — the circular ALIGNMENT.md ↔ plugin header citation is resolved by
|
||||
* this in-plugin record of the OLP-side observation).
|
||||
*
|
||||
* Spawn pattern ported from:
|
||||
* OCP server.mjs:384-414 (buildCliArgs — -p / --model / --output-format / --no-session-persistence)
|
||||
|
||||
@@ -486,7 +486,14 @@ async function* _spawnAndStream(irRequest, authContext, spawnImpl) {
|
||||
if (irChunk === null) continue; // ignored event type (progress, etc.)
|
||||
|
||||
if (irChunk.type === 'error') {
|
||||
throw new ProviderError(irChunk.error, 'SPAWN_FAILED');
|
||||
// D26 round-3 F17: include accumulated stderr tail in the SPAWN_FAILED
|
||||
// message so the consumer-side collectAllChunks re-raises with full
|
||||
// context. ADR 0004 § Chain advancement step 4 (preserve debug signal).
|
||||
const stderrTail = accumulatedStderr.slice(0, 200);
|
||||
const errMsg = stderrTail
|
||||
? `${irChunk.error} | stderr: ${stderrTail}`
|
||||
: irChunk.error;
|
||||
throw new ProviderError(errMsg, 'SPAWN_FAILED');
|
||||
}
|
||||
|
||||
if (irChunk.type === 'stop') {
|
||||
|
||||
@@ -608,7 +608,14 @@ async function* _spawnAndStream(irRequest, authContext, spawnImpl) {
|
||||
if (irChunk === null) continue; // ignored event type
|
||||
|
||||
if (irChunk.type === 'error') {
|
||||
throw new ProviderError(irChunk.error, 'SPAWN_FAILED');
|
||||
// D26 round-3 F17: include accumulated stderr tail in the SPAWN_FAILED
|
||||
// message so the consumer-side collectAllChunks re-raises with full
|
||||
// context. ADR 0004 § Chain advancement step 4 (preserve debug signal).
|
||||
const stderrTail = accumulatedStderr.slice(0, 200);
|
||||
const errMsg = stderrTail
|
||||
? `${irChunk.error} | stderr: ${stderrTail}`
|
||||
: irChunk.error;
|
||||
throw new ProviderError(errMsg, 'SPAWN_FAILED');
|
||||
}
|
||||
|
||||
if (irChunk.type === 'stop') {
|
||||
|
||||
Reference in New Issue
Block a user