mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
cold-audit catch from 2026-05-24 (round 5)
Round-5 cold-audit cleanup batch. 8 items + 1 release-discipline
reconciliation. Largest batch by line count (582+/39-) but every item
is small-and-focused. 3 P2 items (F1/F3/F5 of which F3 + F1 are real
correctness/observability fixes; F5 backfills /health to spec).
Changes (10 files, +583/-39):
**P2 fixes**
1. **F1 — ALIGNMENT.md mistral authority pin self-contradicted plugin**
(ALIGNMENT.md): row cited `vibe --prompt --output json` but mistral.mjs
uses `--output streaming` (the plugin header at lines 360-369 even
justifies WHY: `--output json` emits single blob, breaks NDJSON
line-buffered parser). Constitution self-contradicting itself —
missed across 4 prior rounds. Pin updated to `--output streaming`
with DOCS-1 reference.
2. **F3 — Deterministic function_call synth ID**
(lib/ir/openai-to-ir.mjs): deprecated `function_call` translation
produced `id: \`fc-${Date.now()}\`` → ID flows into normalized
tool_calls → cache key SHA-256. Two identical requests separated
by ≥1ms → different cache keys → cache always misses for
`function_call` request shape. Violates ADR 0005 invariant
"same inputs → same key, no random, no timestamp."
Fixed: id is now `fc-<16-hex>` from SHA-256 of `${name}\0${arguments}`.
NUL separator prevents the (name='ab',args='c') vs (name='a',args='bc')
collision. 2^64 collision resistance is more than sufficient for
tool_call ID disambiguation (per-request semantic key, not crypto
primitive).
**P3 fixes**
3. **F5 — /health invokes per-plugin healthCheck()** (server.mjs +
docs/openai-spec-pin.md): ADR 0002 says "healthCheck — startup AND
/health endpoint use this." Pre-D33 /health returned only
{enabled, available} counts. Now async, iterates loadedProviders,
awaits each plugin's healthCheck() in try/catch. Returns
`providers: {enabled, available, status: {<name>: {ok, latencyMs?, error?}}}`.
4. **F8 — X-OLP-Cache reports fallback-hop cache hits** (server.mjs):
pre-D33 cacheStatus computed from `preCheckHit && fallbackHops === 0`
— only counted primary-hop cache hits. When fallback fires + the
fallback hop's getOrCompute returns from cache, header reported
`miss` despite no spawn happening.
Fixed: peek BEFORE getOrCompute inside executeHopFn, set
`lastHopWasCached` closure variable on every hop (last-write-wins
= serving hop's state). cacheStatus combines
`lastHopWasCached || (preCheckHit && fallbackHops === 0)`.
F8 chose option (b) peek-then-getOrCompute over option (a)
getOrCompute API change because option (a) would break ~15 test
callsites for marginal benefit. Accepted race window same as
existing preCheckHit pattern.
5. **F9 — validateProvider hints error message updated** (lib/providers/
base.mjs): pre-D33 message listed cacheable as missing and
maxSpawnTimeMs as required. Now: `'hints must be an object with
{ requiresTTY, concurrentSpawnSafe, maxConcurrent } + optional
{ maxSpawnTimeMs, cacheable }'`.
6. **F10 — Dead cache-write branch removed** (server.mjs): the
`if (hasStopChunk)` check in the streaming stop-less exhaustion
branch was unreachable (the stop-chunk completion path returns
earlier inside the for-await loop). Removed the dead code + added
a comment documenting the invariant.
**Governance/policy**
7. **F11 — Phase rolling mode policy formalized** (CLAUDE.md +
CHANGELOG.md): 22+ D-day commits accumulated under "Unreleased"
without per-D version bumps — Iron Rule 5 (release-kit bump-before-
push) appeared to be silently violated. Reality: per-D bumps would
produce 30+ noise tags during Phase 1. F11 formalizes the policy:
intra-Phase D-day commits accumulate under Unreleased; bump+tag
fires explicitly at Phase close (maintainer-triggered, not
automated). CLAUDE.md release_kit overlay gains `phase_rolling_mode`
block documenting the exception with self-pointer ("if Rule 5
appears silently violated, check this section first"). CHANGELOG
"Unreleased" gets a notice at top.
**No version bump, no git tag in D33** — policy formalization only.
8. **F12 — /v1/models created is stable per-model timestamp**
(models-registry.json + lib/providers/index.mjs + server.mjs +
docs/openai-spec-pin.md): pre-D33 used Math.floor(Date.now()/1000)
per request — violates OpenAI spec which treats `created` as
per-model attribute. Clients caching models by created would see
spurious updates on every poll.
Fixed: models-registry.json gains `bootstrapCreated: 1778630400`
top-level constant + per-model `created` fields where known
(anthropic claude-{opus,sonnet,haiku} with estimated release dates;
devstral models from "25-12" suffix; codex models pinned to
bootstrap pending verified release dates). handleModels uses
`getModelCreated(modelId)` helper from lib/providers/index.mjs.
Aliases share canonical's timestamp.
**Tests** (test-features.mjs): 401 → 414 (+13):
- F3 ×3 (same input → same id → same cache key; different name → different)
- F5 ×4 (empty/single/multi/throwing-plugin /health shapes)
- F8 ×1 (2-hop primary-fail + secondary-cache-hit → X-OLP-Cache: hit)
- F12 ×5 (stability/fallback/alias-equals-canonical)
Pre-commit fold-in (per evidence-first checkpoint #4):
- **D33 reviewer flagged F3 empty-args asymmetry** (Concern #1): hash
input used `?? ''` (empty stays) but emitted IR field used
`|| '{}'` (empty becomes '{}'). Consequence: `arguments: ''` and
`arguments: '{}'` emit identical IR but compute different ids →
different cache keys for semantically-identical requests. The exact
cache-stability bug F3 was supposed to fix.
Folded in: canonicalize empty-args to '{}' BEFORE hashing. Hash
input now matches IR emission exactly. Same line change resolves
the asymmetry.
Authority:
- ALIGNMENT.md self-amendment (F1 pin correction)
- ADR 0005 invariant "same inputs → same key, no random, no timestamp"
(F3 restoration)
- ADR 0002 § Provider contract "/health uses healthCheck" (F5)
- ADR 0004 § Observability headers (F8 X-OLP-Cache correctness)
- ADR 0005 § Cache write conditions item 1 (F10 truncation-not-cached
invariant explicit)
- Iron Rule 5 (F11 release-kit reconciliation)
- OpenAI /v1/models spec — `created` per-model stable (F12)
- CC 开发铁律 v1.6 § 10.x — Round-5 Cold Audit caught all 8
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE_WITH_MINOR. Verified:
- F1 plugin cross-reference (mistral.mjs:360-369) accurately documents
the rationale
- F3 collision resistance + NUL separator + restored cache invariant
- F5 all 4 cases (empty/single/multi/throwing) work
- F8 closure semantics across multi-hop chains (verified hop-fail +
fallback-hit case)
- F10 dead code removal preserves the stop-chunk completion path
- F11 phase_rolling_mode policy honest about what happened and what
the going-forward rule is
- F12 stability across consecutive /v1/models calls; alias-canonical
parity
- 414/414 tests pass
3 remaining non-blocking suggestions (F3-vs-modern-tool_calls path
canonicalization symmetry; F12 codex models explicit-vs-fallback
writeup mismatch; F8 servingHopWasCached naming) tracked as future
polish; not folded.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
199 lines
7.7 KiB
Markdown
199 lines
7.7 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).
|
|
|
|
**`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).
|