Files
olp/docs/openai-spec-pin.md
taodengandClaude Opus 4.7 e96752a528 docs+fix+test: D36 — pre-Phase-2 batch #2 (issues #2 #5 #6 #13 #14 #15)
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>
2026-05-24 20:38:16 +10:00

231 lines
9.4 KiB
Markdown

# 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 only
- `seed` — deterministic sampling
- `frequency_penalty`, `presence_penalty`
- `logit_bias`
- `logprobs`, `top_logprobs`
- `user`
- `service_tier`
- `parallel_tool_calls`
- `stream_options`
**Response shape (non-streaming)**`object: 'chat.completion'`
(assembled by `irResponseToOpenAINonStream` in `lib/ir/ir-to-openai.mjs`):
```json
{
"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": { ... }
}
```
- `usage` is included only when the provider surfaces token counts on the final chunk.
- `message.tool_calls` is included only when tool calls are present.
- `message.content` is `null` when 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`):
```json
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]
```
- `delta` carries `role` (first chunk only), `content` (text chunks), or `tool_calls` (tool call chunks).
- Final stop chunk has `finish_reason` set and empty `delta`.
- `usage` is 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 completion
- `length` — truncated at `max_tokens` or synthesized on truncation (D19/D16)
- `tool_calls` — model stopped to emit a tool call
- `content_filter` — provider-side content filter
- `function_call` — deprecated; preserved for backwards compatibility
- `null` — in-progress (streaming delta chunks)
**Error response shape:**
HTTP 4xx/5xx with body:
```json
{ "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`):
```json
{ "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:
```json
{
"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: no` plus 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).