Second batch of pre-Phase-2 cleanup. 6 GitHub issues closed in one cohesive docs/governance commit with 1 small code addition (#2 debug log line in server.mjs) and 1 transcript artifact (#15 new file). Changes (7 files modified, 1 file created, +470 / -13): **Code changes** 1. **#2 — cache_control partial-noop debug log** (server.mjs) ADR 0005 § D2 says: "for non-Anthropic targets, the bypass markers are noop'd (logged once per request at debug level so users can see they were ignored)". Pre-D36 logEvent fired only when an Anthropic hop actually bypassed; non-Anthropic hops with markers present were silently noop'd. New 12-line block in handleChatCompletions right after hasCacheControlMarkers is computed. Fires logEvent('debug', 'cache_control_partial_noop', { chain: [<provider names>], marker_count: <count> }) when: - hasCacheControlMarkers === true AND - chain.some(hop => hop.provider !== 'anthropic') Fires at most once per request (top-level if, not in a loop). No log when no markers, or when every chain hop is Anthropic. The existing cache_bypass debug log inside shouldBypassCacheForHop is untouched. marker_count sums body-side and IR-side extractCacheControlMarkers results. At v0.1 the IR term is structurally 0 (openAIToIR strips cache_control); inline comment marks this as a revisit point for the future ADR 0003 amendment that activates cache_control in the IR whitelist. **Documentation amendments** 2. **#5 — ADR 0002 vibe.mjs → mistral.mjs** (docs/adr/0002-plugin-architecture.md) § Decision filesystem layout: `vibe.mjs` corrected to `mistral.mjs` (file named after provider key per the convention anthropic.mjs/codex.mjs). The vibe.mjs entry was an early-draft naming choice that never landed. Amendment 5 prepended above Amendment 4 documenting the filename correction + explicit convention statement (file named after provider key, not CLI binary) for future contributors. 3. **#6 — mistral.mjs A5 flip + ALIGNMENT.md table update** (lib/providers/mistral.mjs + ALIGNMENT.md) Pre-D36: header A5 (model flag) status was UNPINNED-D-later-verifies but function body lines 376-380 said CONFIRMED-NOT-APPLICABLE (DeepWiki enumeration already confirmed `--model` does not exist on vibe CLI). Header now reflects CONFIRMED-NOT-APPLICABLE with DeepWiki citation (DOCS-4). ALIGNMENT.md Speculative-Candidate table mistral row: A5 removed from UNPINNED list; A4, A6, A7, A8 preserved with parenthetical descriptions intact. No code change to function body (already correct). 4. **#13 — /v1/models alias entries — ALIGNMENT.md + spec-pin governance** (ALIGNMENT.md + docs/openai-spec-pin.md) Round-6 F10 flagged D27 F15's alias entries on /v1/models as borderline Rule 2(b) violation (OpenAI spec does not enumerate aliases as separate entries). Option C selected: keep current behavior, document the controlled deviation. - ALIGNMENT.md: new "Controlled deviations (entry-surface scope)" subsection under "Class-specific Exceptions". Entry 1 documents the /v1/models alias deviation with rationale (D27 F15 onboarding), formal contract reference, field constraints (owned_by matches canonical, created matches canonical, no invented fields), SPOT reference (getAliasMap()), and re- evaluation trigger. - docs/openai-spec-pin.md: new alias-surfacing subsection under GET /v1/models with full 4-field contract table (id/object/ created/owned_by), rationale, sourcing explanation, forward path. - server.mjs handleModels: NO CHANGE — behavior preserved. 5. **#15 — Anthropic v2.1.89 transcript artifact** (docs/provider-audits/anthropic.md NEW; ALIGNMENT.md + lib/providers/anthropic.mjs cross-references) Round-6 F12: ALIGNMENT.md anthropic row pin (v2.1.89, observed at D4) cited the plugin header; plugin header cited the observation date but no transcript. Circular per Rule 1 ("observed behaviour, transcript attached"). New file docs/provider-audits/anthropic.md (single living artifact, not version-specific): - Date of capture: 2026-05-24 - Observed `claude --version`: 2.1.132 (Claude Code) — captured today on the project maintainer's primary workstation - Plugin-pinned version: @anthropic-ai/claude-code v2.1.89 (from D4 implementation pin in ALIGNMENT.md Provider Authority Pins) - Version drift: honestly documented — pin is v2.1.89, live is v2.1.132, drift within tolerance, re-audit triggers named - Sample invocation: `claude -p --output-format text --no-session- persistence --model <model> [--debug]` - Flag-surface table: 5 OLP-consumed flags verbatim from `claude -p --help` (-p / --output-format / --no-session- persistence / --model / --debug) - Citation cross-references back to ALIGNMENT.md + plugin header ALIGNMENT.md anthropic row: appended "transcript artifact: docs/ provider-audits/anthropic.md (captured 2026-05-24)" — closes the circular citation. lib/providers/anthropic.mjs header: 5-line pointer to the artifact with version numbers stated explicitly. **Tests** (test-features.mjs): 424 → 431 (+7): - #2 partial-noop log ×3: - Suite 9f case 1: markers + mixed chain → fires once at level=debug - Suite 9f case 2: no markers → suppressed - Suite 9f case 3: anthropic-only chain → suppressed - #14 cache_control slot determinism ×4: - #14a: markers-present IR produces different key from no-markers IR - #14b: same IR computed twice yields identical key - #14c: two independently-constructed IRs with identical payloads yield same key - #14d: both top-level and content-array-nested markers affect the key All 4 tests call computeCacheKey directly on hand-built IRs (bypassing openAIToIR which strips markers at v0.1). Per ALIGNMENT.md Rule 2 (No Invention), no sortMarkers helper added — the slot is dead-code at v0.1 and shipping a helper without a caller authority would be invention. Test comment documents the forward-activation contract. Pre-commit fold-in (per evidence-first checkpoint #4): - **D36 reviewer flagged marker_count latent double-count risk** (Suggestion #1, non-blocking). The sum at server.mjs:435-437 is safe at v0.1 (IR term structurally 0) but will 2× when a future ADR 0003 amendment activates cache_control in the IR whitelist. Folded in a 4-line comment marking the revisit point. Two other non-blocking reviewer suggestions not folded: - Test count brief-vs-deliverable discrepancy (4 not 3 for #14) is informational — the 4-test variant is strictly better (covers content-array nesting which is a real extractCacheControlMarkers contract path). - Recapture-procedure git-add reminder in anthropic.md is low-priority procedure documentation. Authority: - ADR 0005 § D2 — cache_control partial-noop debug log requirement (#2) - ADR 0002 § Decision filesystem layout (Amendment 5) — plugin file naming convention (#5) - DeepWiki vibe CLI flag enumeration — A5 not applicable (#6) - ALIGNMENT.md Rule 2(b) + docs/openai-spec-pin.md GET /v1/models — alias controlled deviation (#13) - ADR 0005 cache key stability invariant + ADR 0003 forward-compat — cache_control slot determinism contract (#14) - ALIGNMENT.md Rule 5 (observed behaviour, transcript attached) + Provider Authority Pins anthropic row — transcript artifact (#15) - CC 开发铁律 v1.6 § 10.x — independent fresh-context reviewer - CLAUDE.md release_kit_overlay phase_rolling_mode — D36 under "Unreleased" against Phase 2; no version bump Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent of drafter): APPROVE. Critical depth checks: - #2: gate condition fires only on (markers AND non-anthropic-hop); pre-existing cache_bypass log inside shouldBypassCacheForHop untouched - #5: lib/providers/ directory verified — mistral.mjs exists, vibe.mjs does not exist - #6: mistral.mjs spawn-site body comment (lines 376-380) already CONFIRMED-NOT-APPLICABLE pre-D36 and unchanged in this diff - #13: server.mjs handleModels unchanged (verified via grep) - #14: all 4 tests pass against current code; no sortMarkers helper shipped (Rule 2 No Invention honored) - #15: live `claude --version` independently run by reviewer → matches artifact (2.1.132); all 5 OLP-consumed flags independently verified present in `claude -p --help` - Hygiene: 0 hits for personal markers, home paths, OAuth tokens, internal IPs across all 8 files - 431/431 tests pass in reviewer's independent npm test run Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9.4 KiB
OpenAI Spec Pin (v0.1 baseline)
- Date pinned: 2026-05-24 (D30)
- Status: v0.1 baseline — annual audit per ALIGNMENT.md § Annual Alignment Audit
- Authority: OpenAI Chat Completions API + Models API
This document is the spec-diff baseline against which OLP's annual audit will compare
future OpenAI spec changes. It enumerates the specific spec sections OLP currently
implements as the entry surface, verified against lib/ir/openai-to-ir.mjs and
lib/ir/ir-to-openai.mjs at the time of pinning.
Endpoints implemented
POST /v1/chat/completions
- Spec section: https://platform.openai.com/docs/api-reference/chat/create
- Retrieval timestamp: 2026-05-24
Request body fields supported (translated into IR by openAIToIR in
lib/ir/openai-to-ir.mjs):
| Field | Type | Required | Notes |
|---|---|---|---|
model |
string | required | Passed through to IR; validated non-empty |
messages |
array | required | Non-empty; each element translated via translateMessage |
messages[i].role |
string | required | system / user / assistant / tool; deprecated function normalized to tool |
messages[i].content |
string|null | required | null normalized to '' |
messages[i].name |
string | optional | Passed through to IR |
messages[i].tool_call_id |
string | optional | Passed through to IR |
messages[i].tool_calls |
array | optional | {id, type:'function', function:{name, arguments}} |
messages[i].function_call |
object | optional | Deprecated field; mapped to a single tool_calls entry |
stream |
boolean | optional | Default false; true triggers SSE path |
temperature |
number | optional | Range [0, 2]; passed to IR |
max_tokens |
integer | optional | Must be a positive integer; passed to IR |
top_p |
number | optional | Range [0, 1]; passed to IR |
stop |
string | array | optional | Passed to IR as-is |
tools |
array | optional | Only type:'function' tools supported; translated via translateTools |
tools[i].function.name |
string | required (in tool) | Passed through |
tools[i].function.description |
string | optional | Passed through if present |
tools[i].function.parameters |
object | optional | Passed through if present |
tool_choice |
'auto'|'none'|'required'|{type:'function',function:{name}} |
optional | Passed to IR verbatim |
response_format |
object | optional | Passed to IR verbatim |
Request body fields NOT yet supported (silently dropped by entry surface — not
read in openAIToIR):
n— multiple completions; OLP is single-completion onlyseed— deterministic samplingfrequency_penalty,presence_penaltylogit_biaslogprobs,top_logprobsuserservice_tierparallel_tool_callsstream_options
Response shape (non-streaming) — object: 'chat.completion'
(assembled by irResponseToOpenAINonStream in lib/ir/ir-to-openai.mjs):
{
"id": "chatcmpl-<base64url>",
"object": "chat.completion",
"created": <unix-epoch-seconds>,
"model": "<model-string-from-request>",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "<string-or-null>",
"tool_calls": [ ... ]
},
"finish_reason": "<stop|length|tool_calls|content_filter|function_call|null>"
}],
"usage": { ... }
}
usageis included only when the provider surfaces token counts on the final chunk.message.tool_callsis included only when tool calls are present.message.contentisnullwhen there is no text content and tool calls are present.
Response shape (streaming) — object: 'chat.completion.chunk'
(emitted by irChunkToOpenAISSE in lib/ir/ir-to-openai.mjs):
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":<ts>,"model":"<m>","choices":[{"index":0,"delta":{"role":"assistant","content":"..."},"finish_reason":null}]}
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":<ts>,"model":"<m>","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
deltacarriesrole(first chunk only),content(text chunks), ortool_calls(tool call chunks).- Final stop chunk has
finish_reasonset and emptydelta. usageis included in the stop chunk only when the provider surfaces token counts.- Stream terminator:
data: [DONE]\n\n.
finish_reason enum honored (normalized by normalizeFinishReason; non-spec values
normalized to 'stop'):
stop— natural completionlength— truncated atmax_tokensor synthesized on truncation (D19/D16)tool_calls— model stopped to emit a tool callcontent_filter— provider-side content filterfunction_call— deprecated; preserved for backwards compatibilitynull— in-progress (streaming delta chunks)
Error response shape:
HTTP 4xx/5xx with body:
{ "error": { "message": "<string>", "type": "<string>" } }
No invented top-level error field on chat.completion objects (per ALIGNMENT.md
Rule 2 — D12 finding). Errors surface exclusively via HTTP status codes with the above
body shape.
GET /v1/models
- Spec section: https://platform.openai.com/docs/api-reference/models/list
- Retrieval timestamp: 2026-05-24
Response shape (handleModels in server.mjs):
{ "object": "list", "data": [ ... ] }
Each entry: { "id": "<model-id>", "object": "model", "created": <ts>, "owned_by": "<provider-key>" } —
no invented fields (per D27 F15). Alias entries are also surfaced as separate list members
(per D27 F15 alias surfacing).
Alias surfacing — controlled deviation (D36 #13). OpenAI's /v1/models spec
enumerates one entry per canonical model ID; OLP additionally surfaces alias entries
(e.g. claude, sonnet, opus, haiku alongside their canonical Anthropic targets).
This is a documented deviation from strict spec parity. It is governed by
ALIGNMENT.md § Class-specific Exceptions → Controlled deviations (entry-surface scope),
which references this section as the formal contract.
The alias-entry contract:
| Field | Value for alias entry |
|---|---|
id |
the alias string (e.g. 'sonnet') — same shape as canonical entries |
object |
'model' — same as canonical entries |
created |
identical to the canonical target's created timestamp (per F12) |
owned_by |
identical to the canonical target's owned_by (i.e. the provider key) |
The alias list is sourced from models-registry.json via getAliasMap() in
lib/providers/index.mjs — the SPOT for alias-aware routing. server.mjs handleModels
appends alias entries to the canonical list only when the alias's canonical target's
provider is currently in loadedProviders. No fields beyond the four OpenAI-spec fields
are added on alias entries.
Rationale (D27 F15): Onboarding gap. Clients configured with model: 'sonnet' (a
common alias used by Anthropic's own CLI and OpenClaw-class tools) previously received
an empty /v1/models response that did not surface the alias as a callable model id.
Surfacing the alias makes the discovery loop usable for OpenAI-compatible clients with
alias-aware UX.
Forward path: Annual audit (14 May) re-checks whether OpenAI has shipped a formal
alias-listing extension to /v1/models. If so, OLP migrates the alias surface to that
shape. If not, the deviation continues unchanged.
created field stability (F12 round-5 cold-audit): OpenAI spec treats created as a
stable per-model attribute, not a request-time value. server.mjs handleModels uses
getModelCreated(modelId) (from lib/providers/index.mjs) which reads the per-entry
created field from models-registry.json. If a model entry has no created field,
the fallback is models-registry.json top-level bootstrapCreated
(currently 1778630400 = 2026-05-13). The per-model timestamps are the closest
approximation to the real model announcement dates per provider docs. Alias entries
use the same created timestamp as their canonical model target.
GET /health
OLP-specific endpoint (not in OpenAI spec). Returns:
{
"ok": true,
"version": "<semver>",
"providers": {
"enabled": <n>,
"available": <n>,
"status": {
"<provider-name>": { "ok": true, "latencyMs": <ms> }
}
}
}
Per-provider status entries are the result of each loaded provider's healthCheck() call
(ADR 0002 § Provider contract). If healthCheck() throws, the entry is
{ "ok": false, "error": "<message>" }. (F5 round-5 cold-audit.)
Streaming SSE semantics
- MIME:
text/event-stream - Per-chunk framing:
data: <json>\n\n - Terminator:
data: [DONE]\n\n - Truncation marker:
finish_reason: 'length'synthesized when the provider generator exhausts without a natural stop chunk (D26 F19 — mirrors D16 buffered-path semantics) - Response headers on stream:
Content-Type: text/event-stream,Cache-Control: no-cache,Connection: keep-alive,X-Accel-Buffering: noplus OLP diagnostic headers
Audit method
Annual audit (target 2027-05-14): re-fetch each cited URL above, diff against the field
lists above, file an ADR amendment for any newly-shipped OpenAI field or changed semantic
that OLP should implement. Consult lib/ir/openai-to-ir.mjs and lib/ir/ir-to-openai.mjs
as the implementation source of truth.
Scope note: v0.1 baseline
This is a minimal baseline. v1.0+ should expand coverage to include the "NOT yet
supported" fields above where OLP intends to support them (via openAIToIR amendments +
ADR 0003 updates).