Commit Graph
10 Commits
Author SHA1 Message Date
68851fe3d7 docs: D43-A — Phase 2 doc alignment (no code change) (#18)
* docs: D43-A — Phase 2 doc alignment (no code change)

Phase 1 was closed at v0.1.1 (multi-provider proxy core + pre-Phase-2
cleanup, D35-D42). This commit aligns documentation surfaces to the
Phase 2 reality before D43-B (ADR 0007 multi-key auth design draft) lands.

Pure doc cleanup — no .mjs / no tests / 4 files touched.

- CLAUDE.md release_kit.phase_rolling_mode:
  * current_phase: Phase 1 → Phase 2
  * current_pre_release_identifier: "0.1.0-bootstrap" → "0.2.0-phase2"
- README.md:
  * Status header now reads "v0.1.1 shipped (2026-05-25); Phase 2 in progress"
  * Implementation Status dated 2026-05-25; intro paragraph reflects Phase 1
    close + Phase 2 active
  * lib/keys.mjs row: "📋 Planned (Phase 2)" → "📋 Phase 2 active per ADR 0007
    (drafting at D43-B)"
  * Known limitations "Multi-key auth not yet implemented" note updated
  * Phase plan rewritten end-to-end: the original v0.1 spec planned one
    plugin per phase, but actual execution bundled the three Tier-D plugins
    + cache + fallback into a single Phase 1 milestone (v0.1.0+v0.1.1).
    New plan: Phase 0  / Phase 1  / Phase 2 multi-key auth (current) /
    Phase 3 dashboard / Phase 4+ v1.x roadmap / Phase N tier-2 opt-in.
- AGENTS.md § Key files to know:
  * lib/keys.mjs marker updated
  * Implementation-status-note dated 2026-05-25; reflects v0.1.1 close +
    Phase 2 active scope
- CHANGELOG.md Unreleased: D43-A entry recording the alignment per
  CLAUDE.md release_kit overlay phase_rolling_mode discipline.

Authority: CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased; Phase 2 kickoff handoff at
~/.cc-rules/memory/handoffs/2026-05-25-phase-2-kickoff.md (committed in
cc-rules d9da966); ADR 0007 forthcoming at D43-B.

Test count: 468 → 468 (npm test verified locally before commit).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: D43-A fold-in — ALIGNMENT.md phase terminology note (reviewer P2)

Fresh-context sonnet reviewer (PR #18) flagged P2: ALIGNMENT.md uses
"Phase 2"/"Phase 3" at lines ~58/~143-144/~179 with the original
per-plugin enablement meaning (Phase 2 = Codex enable, Phase 3 = Mistral
enable), conflicting with the new README phase plan rewritten by D43-A
where Phase 2 means multi-key auth.

Reviewer's recommended minimal fix (B1): add a clarifying note in
ALIGNMENT.md § Provider Inventory header explaining the dual usage,
rather than amend the tables or audit trigger wording. This keeps D43-A
within "pure doc cleanup" scope.

- ALIGNMENT.md § Provider Inventory: one-paragraph "Note on phase
  terminology" inserted between the v0.1 zero-Enabled-Providers
  rationale and the Enabled Providers table. No Speculative-Candidate
  table change, no audit-trigger wording change, no governance-text
  change.
- CHANGELOG.md Unreleased D43-A entry: ALIGNMENT.md added to the file
  list with a one-line explanation referencing the reviewer-P2 fold-in.

Test count: 468 → 468 (npm test verified locally after fold-in; no test
file touched).

Authority: PR #18 fresh-context reviewer finding P2; CLAUDE.md release_kit
overlay phase_rolling_mode — under Unreleased.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 09:29:05 +10:00
taodengandClaude Opus 4.7 b0c080db13 docs: D42 — streaming singleflight design ADR + v1.x safeguards (issue #16)
ADR 0005 Amendment 6 (D34) deferred streaming-path D4 singleflight to
v1.x with the note "the design alone warrants a dedicated ADR." Round-6
cold-audit F13 (issue #16) raised the sibling TOCTOU window between
server.mjs's preCheckHit peek and the streaming-branch spawn. D42
fulfils Amendment 6's deferral note by ratifying the v1.x design as
ADR 0005 Amendment 8 — design only, no implementation.

**Design ratified (ADR 0005 Amendment 8, 14 sections):**

1. New `cacheStore.getOrComputeStreaming(keyId, cacheKey, sourceFactory)`
   API with `{ stream, isFirst }` return shape. Mirrors the buffered
   path's `getOrCompute` to keep the cache API surface coherent.
2. `StreamingInflightEntry` shape — source iterator + AbortController +
   accumulated-chunks replay buffer + attached-clients Set + state
   flags + D38 spawn-slot tracker.
3. `AttachedClient` shape — per-client tee buffer + byte-size meter +
   late-joiner replay flag + done/resolveNext/rejectNext for the
   single-reader-multi-writer tee.
4. Tee fan-out loop (one reader drains source, fans chunks to all
   attached clients).
5. Late-joiner replay policy — accumulated chunks burst-drained on
   attach.
6. Cache TTL race during inflight — late joiners see the inflight entry
   and join; expired cache slot is overwritten by inflight completion.
7. D38 maxConcurrent coordination — only first caller acquires; release
   fires once on source-complete/error/abort.
8. New `STREAM_BACKPRESSURE` error code. NOT a hard trigger. Affected
   client gets synthetic `{ type: 'stop', finish_reason: 'length' }` +
   `[DONE]` (matches D35 #10 truncation marker).
9. Mid-stream disconnect — remove from attached set; if 0 remaining,
   abort source via AbortController.
10. Replay buffer cap (10 MB, matches D23 cache-entry cap) — over cap
    marks entry not cacheable, late joiners get backpressure error.
11. Observability — 4 new log events
    (streaming_inflight_join / source_done / abort,
    stream_backpressure_disconnect) + new
    `X-OLP-Streaming-Inflight: source | attached | solo` header.
12. Server.mjs wiring — replaces the current peek+spawn pattern;
    TOCTOU window closes because Map check+insert is synchronous.
13. Test surface (when implementation lands) — 10 scenarios covering
    single-client / 2-concurrent / 3-concurrent / mid-stream join /
    first-disconnect / all-disconnect / source-error / backpressure /
    D38 coordination / TTL race / replay cap / X-OLP-* header values.
14. Defaults — PER_CLIENT_QUEUE_CAP=1MB, ACCUMULATED_REPLAY_CAP=10MB,
    STREAM_BACKPRESSURE not in HARD_TRIGGER_CODES.

**Multi-layer safeguards (the maintainer asked: "保证后面这一块会被处理而不会被忽略"):**

1. **`docs/v1x-roadmap.md` (NEW)** — single living landing page for
   every Phase-1 deferral. 7 items at D42:
   - #1 Streaming SF (this amendment)
   - #2 Multi-key auth (lib/keys.mjs)
   - #3 Soft trigger reactivation (ADR 0004 Amendment 2)
   - #4 /health activeSpawns integration (D38)
   - #5 Provider-level cacheKeyFields mask (ADR 0005 Amendment 7)
   - #6 Streaming-path SPAWN_FAILED salvage
   - #7 AUTH_MISSING tuple test coverage (D40 follow-up)
   Each entry names the ratifying ADR, load-bearing code anchor
   (file:line), GitHub issue (if any), concrete start trigger, and
   estimated effort. Maintainer's session-startup discipline grep
   this file at sprint kickoff.

2. **Issue #16 STAYS OPEN** — not closed in D42. Body updated post-
   commit to reference Amendment 8 with status "design ratified;
   implementation pending." Do not close until §13 test surface is
   green on actual implementation.

3. **`lib/cache/store.mjs#getOrCompute` JSDoc** — TODO comment for the
   sibling streaming API pointing at Amendment 8 + v1x-roadmap.md #1.

4. **`server.mjs` streaming-branch entry (~line 810)** — TODO comment
   block citing Amendment 8 + issue #16 + roadmap.md #1, naming the
   exact code lines the v1.x impl will replace.

5. **`README.md § Known limitations` section** — new subsection
   surfaces 4 limitations to users (streaming SF / soft triggers /
   multi-key auth / cacheKeyFields mask), each linking to the
   v1x-roadmap.md entry.

6. **Amendment 8 § "Cross-references and safeguards"** — explicit
   cross-link block enumerating the above 4 anchors so a future
   ADR-only reader knows every breadcrumb.

**Maintainer decision recorded:** Option 1 (design ADR only) chosen
over Option 2 (design + implementation now). Rationale: 200-400 lines
of concurrency primitives + 15-20 tests is not "pre-Phase-2 cleanup"
in shape — it is real v1.x feature work. Shipping streaming SF in a
v0.1.1 patch release would muddy the Phase 1 / Phase 2 contract that
v0.1.0 ratified. Personal/family-scale load makes the deferral safe
at v0.1.

Changes (6 files, +165 / -0):

- `docs/adr/0005-cache-cross-provider.md` — Amendment 8 prepended
  (133 lines).
- `docs/v1x-roadmap.md` — NEW file (148 lines).
- `lib/cache/store.mjs` — getOrCompute JSDoc gains TODO block (8 lines).
- `server.mjs` — streaming-branch entry gains TODO block (6 lines).
- `README.md` — Known limitations section (9 lines).
- `CHANGELOG.md` — D42 sub-entry under Unreleased (9 lines).

No code-behavior change. No new tests. No package.json bump
(phase_rolling_mode).

Authority:
- ADR 0005 Amendment 8 (this commit) — design ratification
- ADR 0005 Amendment 6 (D34) — original deferral with "design ADR
  needed" note that this commit fulfils
- GitHub issue #16 (round-6 F13) — sibling TOCTOU; STAYS OPEN
- ADR 0002 Amendment 6 (D38) — tryAcquireSpawn semantics
- ADR 0004 Amendment 5 (D40) — observability pattern extension
- CC 开发铁律 v1.6 § 10.x — design-only amendment; fresh-context
  reviewer not required per Iron Rule 10 implementation-phase scope
- CLAUDE.md release_kit_overlay phase_rolling_mode — under Unreleased

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 06:58:45 +10:00
taodengandClaude Opus 4.7 b43b07afbf docs: D41 — X-OLP-Provider-Used chain-origin semantics (issue #8)
Round-4 cold-audit Finding 10 (filed as issue #8): on a chain-exhausted
response, X-OLP-Provider-Used returns chain[0].provider — but if hop 0
were soft-skipped (quota threshold exceeded), the header would attribute
a provider whose plugin's spawn() was never called. README's "which
provider's plugin served the request" is technically false in that
edge case.

At v0.1 the scenario is unreachable because soft triggers are deferred
(ADR 0004 Amendment 2) — evaluateSoftTriggers always returns false.
The ambiguity is latent and only activates when soft triggers
reactivate in v1.x.

**Option B chosen — document chain-origin semantics, no code change.**

Option A would track firstAttemptedProvider separately in
executeWithFallback and return that on chain exhaustion. Adding state
for an unreachable v0.1 code path would violate ALIGNMENT.md Rule 2
(No Invention). The D40 X-OLP-Fallback-Detail header (Amendment 5)
already carries per-hop spawn history including soft-skip records
(trigger_type: 'soft'), providing the disambiguation channel on the
wire without needing providerUsed to handle it.

Changes (4 files, +35 / -1):

1. **docs/adr/0004-fallback-engine.md** — Amendment 6 added above
   Amendment 5 in the amendments stack. Documents the chain-origin
   contract, names Option A as the likely v1.x preference, cites
   the Rule 2 rationale + the X-OLP-Fallback-Detail disambiguation
   channel.

2. **README.md** — Observability header description updated:
   "which provider's plugin served the request" gains a clarifying
   sentence about chain-origin semantics on exhausted responses,
   with a pointer to ADR 0004 Amendment 6.

3. **lib/fallback/engine.mjs** — Inline comment block at the
   chain-exhausted return site explicitly cites the amendment and
   captures the v0.1-vs-v1.x semantic. No behavior change.

4. **CHANGELOG.md** — D41 sub-entry under existing D38/D39/D40
   entries in Unreleased section.

No code-behavior change. No new tests — the relevant scenario is
dead-by-config at v0.1. v1.x soft-trigger reactivation work should
add a test exercising soft-skip + chain-exhausted that pins
whichever option (A or B) the v1.x maintainer chooses, and
coordinate the README + ADR Amendment 6 update if Option A is
adopted.

Authority:
- ADR 0004 Amendment 6 (this commit) — chain-origin semantics
- ADR 0004 § Decision § Chain advancement step 4 — original promise
- ADR 0004 Amendment 2 — soft triggers deferred (precondition)
- ADR 0004 Amendment 5 (D40) — per-hop attribution channel
- ALIGNMENT.md Rule 2 — No Invention rationale
- GitHub issue #8 — closed by this commit
- D32 round-4 cold-audit F10 — original filing
- CC 开发铁律 v1.6 § 10.x — Iron Rule 10's implementation-phase scope
  is unmet here (doc-only amendment); no fresh-context reviewer
  dispatched per the documented exception in this amendment's
  procedural mechanism

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 21:47:33 +10:00
taodengandClaude Opus 4.7 30de965e8e fix+docs: D32 — round-4 cleanup batch (F2/F3/F4/F5/F7/F8/F9)
cold-audit catch from 2026-05-24 (round 4)

Round-4 cold-audit cleanup batch. 7 items grouped per IDR cleanup-batch
convention (D19/D20/D25/D26/D30/D31 precedent). 2 P2 + 3 ADR amendments
+ 2 small docs/code cleanups. F10 (P3 semantic edge case) filed
separately as GitHub issue #8.

Changes (8 files, +200 / -38):

**P2 fixes**

1. **F3 — README missing 6 provider auth env vars** (README.md):
   D30 fixed the `OLP_*` table but missed the auth-bearing env vars
   actually read by plugin code:
   - `CLAUDE_CODE_OAUTH_TOKEN` (anthropic.mjs:84) — highest-precedence
     override; bypasses keychain + .credentials.json file lookup
   - `OPENAI_CODEX_AUTH_PATH` (codex.mjs:163) — overrides full auth
     file path; when set, no other path tried
   - `CODEX_HOME` (codex.mjs:176) — overrides base dir; default auth
     path becomes `$CODEX_HOME/auth.json`
   - `MISTRAL_API_KEY` (mistral.mjs:260) — directly supplies API key;
     highest precedence per Mistral DOCS-2
   - `MISTRAL_VIBE_AUTH_PATH` (mistral.mjs:265) — overrides full .env
     path; evaluated only when MISTRAL_API_KEY absent
   - `VIBE_HOME` (mistral.mjs:277) — overrides Vibe base dir; default
     auth path becomes `$VIBE_HOME/.env`
   Real onboarding-blocker fix: new users couldn't run OLP without
   knowing about these env vars; README now documents them in a
   "Per-provider auth env vars" subsection.

2. **F8 — Early-return error paths missing X-OLP-* headers** (server.mjs):
   ADR 0004 § Observability + README claim "every response carries the
   5 X-OLP-* headers" but 503 (no-enabled-providers), 415 (wrong
   Content-Type), and early 400s (bad JSON, IR validation) emitted only
   Content-Type + Content-Length + X-OLP-Latency-Ms (D18 only wired the
   chain-exhausted path). Operators debugging these paths got less info
   than the ADR promised.
   New helper `olpErrorHeaders({ startMs, model })` emits the canonical
   "no provider attempted" defaults:
     X-OLP-Provider-Used: 'none'
     X-OLP-Model-Used: model ?? 'unknown'
     X-OLP-Fallback-Hops: '0'
     X-OLP-Cache: 'bypass'
     X-OLP-Latency-Ms: <delta>
   6 sendError call sites updated: 415 / 400-bad-JSON / 400-IR-parse
   (model: undefined → 'unknown') + 503-no-providers / 503-provider-
   disappeared / 500-engine-programming (model: ir.model). The 502
   streaming-error-before-first-chunk path correctly stays on
   `olpHeaders(...)` since a provider WAS attempted.

**ADR amendments**

3. **F2 — ADR 0003 model-mapping example correction** (docs/adr/0003,
   Amendment 2): the § Required fields example claimed `claude-sonnet-4-6`
   → `claude-sonnet-4-6-20260301` mapping happens in the provider plugin.
   This was wrong: per D17 SPOT decision (commit cb86807), `irRequest.model`
   is passed verbatim to the CLI; each provider CLI accepts its own
   aliases natively. Both inline correction (in § Decision) AND new
   Amendment 2 (in § Amendments) added.

4. **F4 — OUTPUT_PARSE_ERROR dead code removal** (base.mjs, engine.mjs):
   `PROVIDER_ERROR_CODES.OUTPUT_PARSE_ERROR` and
   `HARD_TRIGGER_CODES.OUTPUT_PARSE_ERROR = true` were registered but
   ZERO plugins emit OUTPUT_PARSE_ERROR. ADR 0004 § Trigger taxonomy
   enumerates 4 hard-trigger categories (5xx, quota 4xx, exit-code,
   spawn-timeout) — OUTPUT_PARSE_ERROR was a 5th never authorized by
   ADR. Removed from both enums with removal-reason comments for
   auditability. Re-add via ADR 0004 amendment if a future plugin
   surfaces parse failures (cheaper than authoring an amendment for
   dead code now).

5. **F5 — ADR 0002 Amendment 4 ratifying contractVersion** (docs/adr/0002):
   `lib/providers/base.mjs:76-81` enforces `p.contractVersion === '1.0'`
   and all 3 plugins declare it, but ADR 0002 § Provider contract field
   list never named it. Same governance-violation class as D11's
   `maxSpawnTimeMs` retroactive sync (Amendment 1). Amendment 4 ratifies
   contractVersion as a required v1.0 contract field; § Provider contract
   field list updated to 10 fields (was 9).

**Small cleanups**

6. **F7 — README API Endpoints table 📋 markers** (README.md): added
   Status column with  Shipped (3 rows: /v1/chat/completions, /v1/models,
   /health) and 📋 Planned (3 rows: /cache/stats Phase 5, /v0/management/
   quota Phase 6, /dashboard Phase 6). Matches D20's Implementation
   Status table convention.

7. **F9 — Codex parser inline assumption labels** (codex.mjs): added 5
   inline `// A4:` comments on each defensive branch in `codexChunkToIR`.
   Pre-D32 these branches had only the top-of-function block comment;
   ALIGNMENT.md Speculative-Candidate condition 5 promises future
   implementers can grep assumption labels to find every speculative
   branch — D32 honors that promise for codex.mjs (mistral.mjs already
   had this pattern from D8).

**Tests** (test-features.mjs):
- 17g retitled + assertion expanded to full 5-header set (was partial)
- 17h new: 415 wrong Content-Type → 5 X-OLP-* headers with 'unknown' model
- 17i new: 503 no-enabled-providers → 5 X-OLP-* headers with ir.model
- OUTPUT_PARSE_ERROR test removed (replaced with comment for audit trail)

Test count: 400 → 401 (-1 OUTPUT_PARSE_ERROR + 2 new F8 + 1 retitled
existing = net +1).

**F10 filed as issue, NOT in D32**:
https://github.com/dtzp555-max/olp/issues/8 — "Soft-skip + chain-
exhausted: X-OLP-Provider-Used semantics ambiguity". Semantic edge
case requiring soft trigger reactivation (deferred to v1.x per D22
Amendment 2); not a v0.1 user-affecting issue.

Pre-commit fold-in (per evidence-first checkpoint #4):
- **D32 reviewer caught a doc-accuracy bug**: README's
  CLAUDE_CODE_OAUTH_TOKEN row described the no-env fallback chain as
  "Searches keychain first, then `~/.claude/.credentials.json`" — but
  the actual code in anthropic.mjs:82-112 checks file FIRST, then
  keychain (darwin-only). Order was reversed. Folded in: corrected
  to "Searches `~/.claude/.credentials.json` first, then macOS
  keychain (darwin only)". Same class of doc-accuracy mistake as the
  D25 → D31 / D27 → D31 pattern: D-batch reviewer catches docs that
  contradict source.

Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE_WITH_MINOR. Independently verified:
- All 6 F3 env vars: precedence chains traced through plugin source
  (caught the anthropic reversed-order — folded in)
- F8 olpErrorHeaders helper correctly distinguishes "no provider"
  defaults from olpHeaders' "provider attempted" defaults
- F8: all 6 sendError call sites use the right model arg (undefined
  pre-IR-parse → 'unknown'; ir.model post-IR-parse)
- F8: 502 streaming-error stays on olpHeaders (provider was attempted)
- F4: OUTPUT_PARSE_ERROR removed from both enums, zero plugin emit
  references remain, test cleanup is auditable
- F5: ADR 0002 Amendment 4 matches Amendment 1's retroactive-sync
  structure; contract field list now 10 items
- F2: D17 commit cb86807 verified to match the SPOT decision claim
- F7/F9: docs/code cleanups verified
- F10 issue #8 verified OPEN with correct title
- 401/401 tests pass

3 non-blocking suggestions (anthropic precedence column folded; F2
inline mistral `--model` caveat could be added; F4 wording asymmetry
between base.mjs and engine.mjs comments) — minor polish, not folded.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 17:28:42 +10:00
taodengandClaude Opus 4.7 5119b427fd docs: D30 — README env vars correctness (F7) + openai-spec-pin.md v0.1 baseline (F20)
cold-audit catch from 2026-05-24 (round 3)

Round-3 P3 docs batch. Two unrelated docs items grouped per IDR cleanup
convention (D19/D20/D25 precedent).

**F7** — README Environment Variables table drift.

Pre-D30 table documented `OLP_HOME` and `OLP_LOG_LEVEL` — neither is
read by any code in the repo. The table missed `OLP_CLAUDE_BIN`,
`OLP_CODEX_BIN`, `OLP_VIBE_BIN` which the 3 provider plugins DO read
to override the path to each provider's CLI binary.

`grep -rn "process\.env\.OLP_" server.mjs lib/` returns exactly 4 reads:
- `OLP_PORT` (server.mjs:49)
- `OLP_CLAUDE_BIN` (anthropic.mjs:69)
- `OLP_CODEX_BIN` (codex.mjs:143)
- `OLP_VIBE_BIN` (mistral.mjs:240)

Fix: README env-vars table now lists exactly these 4 active vars with
their actual defaults. `OLP_HOME` + `OLP_LOG_LEVEL` moved to a
"📋 Planned (Phase 2)" callout block beneath the table, with honest
explanations linking to the actual code state (`loadFallbackConfigSync`
hardcodes the config path; `logEvent` writes unconditionally).

**F20** — Author docs/openai-spec-pin.md v0.1 baseline.

ALIGNMENT.md Authority 2 + Annual Alignment Audit § Scope both
referenced `docs/openai-spec-pin.md` as the artifact the annual audit
diffs against. Pre-D30 the file didn't exist (D20 marked it 📋 Planned).
This left the entry-surface audit without a diff baseline — v0.1 ships
with no provable "the OpenAI spec was THIS on the day OLP implemented
its entry surface" anchor.

Fix: author a 175-line minimal v0.1 baseline. Every field claim is
verified against source (openai-to-ir.mjs + ir-to-openai.mjs + server.mjs).
Structure:
- POST /v1/chat/completions: 14 supported request fields (model,
  messages + 6 message-level subfields, stream, temperature, max_tokens,
  top_p, stop, tools, tool_choice, response_format) + 11 NOT-yet-supported
  fields (n, seed, frequency_penalty, presence_penalty, logit_bias,
  logprobs, top_logprobs, user, service_tier, parallel_tool_calls,
  stream_options)
- Response shapes: chat.completion (non-stream), chat.completion.chunk
  (streaming) — verified against irResponseToOpenAINonStream and
  irChunkToOpenAISSE
- `finish_reason` enum: verified against OPENAI_FINISH_REASON_ENUM
  constant in ir-to-openai.mjs (post-D19 + D26)
- Error response shape: HTTP 4xx/5xx + `{error: {message, type}}` —
  verified against `sendError` in server.mjs
- GET /v1/models: verified against handleModels (post-D18 + D27 F15)
- Streaming SSE semantics: framing, terminator, post-D26 F19
  truncation marker

The pin also documents the v0.1 → v1.0 forward-looking expansion plan:
the "NOT yet supported" fields are explicit candidates for v1.0+
implementation via openai-to-ir.mjs amendments + ADR 0003 updates.

ALIGNMENT.md + README Implementation status table both flip the marker
from 📋 Planned to  Shipped (D30) with the 2026-05-24 timestamp.

Changes (3 files, +180 / -8):
- ALIGNMENT.md +2/-2 (2 markers updated: Authority 2 + Annual Audit)
- README.md +11/-6 (env-vars table delta + status table marker flip +
  Planned callout for OLP_HOME/OLP_LOG_LEVEL)
- docs/openai-spec-pin.md (new, 175 lines)

Tests: 400/400 unchanged — pure docs change.

Authority:
- F7 → process.env reads verified by direct grep
- F20 → OpenAI Chat Completions spec
  https://platform.openai.com/docs/api-reference/chat/create
  https://platform.openai.com/docs/api-reference/chat/streaming
  https://platform.openai.com/docs/api-reference/chat/object
  https://platform.openai.com/docs/api-reference/models/list
- F20 internal source-of-truth: openai-to-ir.mjs + ir-to-openai.mjs +
  server.mjs (all field claims traced)
- ALIGNMENT.md § Authority 2 + § Annual Alignment Audit
- CC 开发铁律 v1.6 § 10.x — Round-3 Cold Audit caught both items

Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Independent verification:
- Grep confirmed exactly 4 process.env.OLP_* reads — matches new env
  vars table
- Each new var's default value verified against the plugin code
  (anthropic.mjs:69 → 'claude'; codex.mjs:143 → 'codex'; mistral.mjs:240
  → 'vibe')
- All 14 spec-pin supported request fields traced to openAIToIR line
  references (model L129, messages L45-89, stream L141, temperature
  L160, max_tokens L152, top_p L168, stop L176, tools L183, tool_choice
  L187, response_format L191)
- All 11 NOT-supported fields confirmed absent via grep
- Response shape claims (chat.completion + chat.completion.chunk +
  /v1/models) all match source code line-by-line
- ALIGNMENT.md markers — pure markup flip, no rule changes
- 400/400 tests pass

3 non-blocking suggestions noted (function_call finish_reason caveat;
README slug rendering; ADR 0003 cross-link in spec-pin) — all cosmetic,
not folded.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 16:56:01 +10:00
taodengandClaude Opus 4.7 cd391b13ba 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>
2026-05-24 14:56:05 +10:00
taodengandClaude Opus 4.7 e10b7d7cb9 docs(adr-0004)+chore: D22 — defer soft triggers to v1.x (round-2 F2)
cold-audit catch from 2026-05-24

Round-2 cold-audit Finding 2 (P2 feature-surface vs data-ingestion drift).
ADR 0004 § Trigger taxonomy documented soft triggers (credit_pool_percent,
daily_request_count, five_hour_window_percent) as a live, configurable
feature category. But `evaluateSoftTriggers` in lib/fallback/engine.mjs
is functionally inert at v0.1:
- buildDefaultChain hardcodes quotaSnapshot: null on every hop
- No call site for provider.quotaStatus() in server.mjs or engine.mjs
  (only definitions in the 3 provider plugins, all currently stubs)
- evaluateSoftTriggers correctly short-circuits to false on null snapshot

A user populating routing.soft_triggers in ~/.olp/config.json gets zero
runtime behavior. Round-1 cold audit + D5/D9 diff-review reviewers all
focused on evaluation correctness; nobody traced the production data
path end-to-end.

Owner decision (after considering wire-vs-defer): defer to v1.x. Wiring
quotaStatus() polling requires per-hop async I/O before each spawn
decision, error handling for providers without quota endpoints (all 3
current providers fall in this bucket: claude -p, codex exec --json,
vibe --prompt — none expose quota), a caching layer to avoid re-polling,
and a latency budget for a pre-spawn network call. Implementation cost
high; v0.1 value zero.

Strategy: defer the FEATURE (data ingestion path), not the CODE (evaluation
logic). evaluateSoftTriggers is small, well-tested via unit tests that
inject snapshots directly (test-features.mjs:3617-3665), and
architecturally correct. v1.x reactivation requires only wiring the
data path — evaluation stays untouched.

Changes (3 files, +25 / -1):

1. docs/adr/0004-fallback-engine.md +15 — new Amendment 2 block at top
   of doc (after Amendment 1, before § Context, matching D11/D15/D16/
   Amendment 1 placement convention):
   - Finding (cold-audit round-2 F2 + 3 concrete code-state facts)
   - Decision (defer; keep evaluation code inert-but-correct)
   - Rationale (3-point cost/value analysis)
   - Effect on § Trigger taxonomy (inline deferral note appended; design
     prose preserved)
   - What v1.x reactivation looks like (3 concrete steps; no rewrite needed)
   - Procedural mechanism (CC 开发铁律 v1.6 § 10.x — round-2 caught it)

   Plus an inline "📋 Deferred to v1.x (Amendment 2)" sub-bullet in
   § Trigger taxonomy → Soft triggers entry. The 3 threshold descriptions
   are preserved verbatim — the architectural design remains the v1.x
   intent.

2. lib/fallback/engine.mjs +8 / -1 — comment-only updates on the 2
   `quotaSnapshot: null` lines in buildDefaultChain. Each now reads:
   ```
   // quotaSnapshot stays null at v0.1 — soft triggers deferred to v1.x per
   // ADR 0004 Amendment 2. evaluateSoftTriggers correctly short-circuits to
   // false when quotaSnapshot is null. The polling path is not wired in v0.1.
   ```
   No code logic changed. The previous misleading comment "populated at
   runtime if provider.quotaStatus() is called" (which falsely implied a
   live wiring) is replaced.

3. README.md +3 — two additions:
   - Deferral callout immediately after the routing.soft_triggers config
     example block, naming Amendment 2
   - Implementation status table row: "Soft trigger data path
     (quotaStatus() polling) | 📋 Planned (v1.x) | Evaluation logic
     shipped + tested; data ingestion deferred per ADR 0004 Amendment 2"

Tests: 335/335 unchanged — pure docs + comment deferral, no behavior
change. Existing unit tests for evaluateSoftTriggers (which inject
snapshots directly) continue to validate the evaluation correctness
independent of the production ingestion path.

Authority:
- ADR 0004 self-amendment (Amendment 2 in-place)
- ALIGNMENT.md Rule 1 (Cite First) + CLAUDE.md § "Hard requirements"
  item 1 — contract status changes require ADR amendment
- CC 开发铁律 v1.6 § 10.x — Round-2 Cold Audit caught this

Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Verified all 3 framing claims independently:
(a) buildDefaultChain hardcodes null on both branches — confirmed at
engine.mjs:421 and :444; (b) zero quotaStatus() call sites in production
— `grep -rn "quotaStatus("` found only 3 definitions in provider plugins
+ JSDoc mentions; (c) evaluateSoftTriggers correctly short-circuits at
engine.mjs:139. Reactivation realism check: all 3 v1.x steps map to
existing surfaces (insertion points, hop shape, test fixtures).

Follow-up items (reviewer's non-blocking notes, NOT in this PR):
- README line 154 still reads "328-test suite"; current is 335 — pre-
  existing doc-sync drift to pick up in D25 P3 batch
- v1.x ADR 0002 amendment may need to formally define authContext shape
  (currently informal in the contract); pre-note as a dependency

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 14:28:49 +10:00
taodengandClaude Opus 4.7 d85a2dcf71 docs: D20 — honest forward-reference annotations across README/ADRs (Finding 9)
cold-audit catch from 2026-05-23

Cold-audit Finding 9 (P3 drift): multiple docs referenced files / paths
/ directories that don't exist at the current implementation state. A
reader following AGENTS.md § "Key files to know" to read lib/keys.mjs
would find no such file; following README architecture to dashboard.html
same; ADR 0005 § D1 described a `~/.olp/cache/<key>/<prefix>/<hash>.json`
layout that doesn't exist (current impl is in-memory Map per
lib/cache/store.mjs). D20 doesn't gut the designs — it adds honest
status callouts so the gap is obvious within seconds.

Files changed (8, docs only, +46 / -14):

1. README.md (+32 / -3):
   - New H2 section "Implementation status (as of 2026-05-24)" with a
     10-row table distinguishing  Shipped vs 📋 Planned, including phase
     numbers (Phase 2 / 6 / 7) matching the existing §"Phase plan"
   - Inline status annotation on the multi-key auth bullet
     (lib/keys.mjs marked planned for Phase 2)
   - Inline status on the cache layer bullet (file-backed storage marked
     Phase 2 — current is in-memory Map)
   - Migration section's Phase 7 placeholder annotated explicitly

2. AGENTS.md (+8 / -3):
   - Inline `📋 Planned (Phase N) — not yet authored` markers on
     lib/keys.mjs and dashboard.html in the "Key files" bullet list
   - Inline marker on setup.mjs reference in the
     "Project-specific constraints" section
   - New "Implementation status note" paragraph at the end of the Key
     files block pointing to README's status table for the full picture

3. ALIGNMENT.md (+6 / -3):
   - docs/openai-spec-pin.md references (Authority 2 + audits section)
     tightened from "deferred" to "deferred, not yet authored; must be
     created before first annual audit (target: v1.0)"
   - docs/alignment-audits/ directory annotated as "directory does not
     exist yet; it is created when the first audit is conducted"

4. CLAUDE.md (+2):
   - release_kit.bootstrap_quirk_policy YAML retains the
     scripts/migrate-from-ocp.mjs reference (forward-looking spec
     compliance) and adds an inline YAML comment explicitly noting
     "is planned (Phase 7), not yet authored. The scripts/ directory
     does not currently exist. References here are forward-looking;
     do not attempt to run this script."

5-8. ADR amendments (status notes only — no Amendment blocks, since
     these are clarifications about implementation state, NOT decision
     changes per se):
   - ADR 0001 (Consequences/Negative): scripts/migrate-from-ocp.mjs
     annotated as Phase 7 planned
   - ADR 0003 § Decision (lossy-translation paragraph): inline note that
     docs/provider-caveats.md is planned; until it exists, lossy edges
     are recorded only in plugin headers. Plus a status mention in
     Consequences/Positive
   - ADR 0004 Mitigations: docs/provider-caveats.md annotated as planned
   - ADR 0005 § Decision (D1 paragraph): the file-backed layout block
     reframed from "Cache directory structure:" to "Designed file-backed
     layout (target for Phase 2 storage adapter):". Added a status
     blockquote explicitly noting "v0.1 implementation in lib/cache/store.mjs
     uses an in-memory Map; no files written to ~/.olp/cache/. The
     file-backed layout described above is the designed shape; it
     transitions in via a Phase 2 storage adapter. Per-key isolation and
     singleflight (D4) are live; file persistence is not."

Note on ADR Decision-section edits (corrected from initial writeup):
two ADR amendments DO touch Decision-section text (ADR 0003 lossy-
translation paragraph, ADR 0005 D1 cache layout). Both edits are
conservative — status caveats that preserve the original prose verbatim
(0003) or reframe section headings without changing the path-shape spec
(0005). No design content is gutted. No Amendment block was added
because the decisions themselves aren't changing; these are clarifications
about what's live today vs designed. The discipline boundary here:
inline-status-note ≠ decision-amendment.

Tests: 328/328 unchanged (sanity check; docs-only changes).

7/7 Finding 9 forward references annotated (confirmed by reviewer
running independent `ls` on each path):
- lib/keys.mjs  doesn't exist → annotated
- dashboard.html  doesn't exist → annotated
- docs/provider-caveats.md  → annotated (2 ADR sites + Consequences)
- docs/openai-spec-pin.md  → annotated (ALIGNMENT.md Authority 2)
- docs/alignment-audits/  → annotated
- scripts/migrate-from-ocp.mjs  → annotated (README + ADR 0001 + CLAUDE.md release_kit)
- setup.mjs  → annotated (AGENTS.md)

Authority:
- README/AGENTS.md/ADR/ALIGNMENT.md/CLAUDE.md self — the doc set is
  its own authority for what it documents; D20 brings each statement
  into honest agreement with the current implementation
- CC 开发铁律 v1.6 § 10.x — Cold Audit Finding 9

Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE_WITH_MINOR. Verified each Finding 9 path doesn't
exist via independent `ls`; cross-checked phase numbers match the
existing §"Phase plan" listing; ran `npm test` to confirm 328/328
unchanged. Two non-blocking minors:

1. Implementer's initial writeup overstated "ADR decision text NOT
   edited" — reality is two Decision-section paragraphs got inline
   status caveats. Commit message above is corrected.

2. Reviewer found an additional drift D20 didn't address: ADR 0002 §
   Decision filesystem layout lists `vibe.mjs` for the Mistral plugin,
   but the shipped file is `mistral.mjs` (the binary is `vibe`, the
   plugin file is `mistral`). Different drift class from Finding 9
   (file exists, just named differently in the ADR). Filed as
   follow-up issue.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 12:26:02 +10:00
taodengandClaude Opus 4.7 91223ee9ab docs(governance): fold in 6 codex review findings
External Codex CLI review surfaced 6 substantive findings beyond what
the internal opus reviewer caught at D1. All folded in this commit.
Files changed: ALIGNMENT.md, README.md, ADR 0001, ADR 0006, CHANGELOG.

1. Provider Inventory split: Candidate vs Enabled
   - Bootstrap previously listed anthropic/openai/mistral as Tier D
     default-enabled with Authority pins still "TBD at Phase N spawn".
     This violated Rule 1 (Cite First) and Rule 3 (Match Implementation).
   - v0.1 founding now ships 0 Enabled Providers. All 8 are Candidate.
     Enablement requires: authority pin filled + plugin landed + Phase
     audit passed.

2. Antigravity Tier A downgraded to "evidence-backed pending pin"
   - Secondary reports disagree on blast radius (piunikaweb 03-02 says
     AI-tier only; piunikaweb 02-23 + OpenClaw issue + VentureBeat say
     broader). Google FAQ language naming OpenClaw/OpenCode/Claude Code
     is cited from secondary sources only — primary URL not pinned.
   - Exclusion remains active by default; constitutional weight matches
     evidence. Primary-source pinning tracked as one-shot audit task
     with 90-day Tier-reconsideration trigger.

3. ADR 0001 supersession scope narrowed
   - Previous draft claimed OLP is "the structural shape ADR 0005
     endorsed," but ADR 0005's separate-repo recommendation came with
     "BYOK from day one" + "no cli.js spawn" qualifiers OLP rejects.
   - Supersession now narrowly scoped to "single-provider-sufficiency
     premise only"; BYOK + no-spawn parts of ADR 0005 explicitly NOT
     inherited.

4. Anthropic post-2026-06-15 one-shot audit scheduled
   - Annual 14 May audit would leave the Anthropic Tier re-eval almost
     a year late after the 2026-06-15 split.
   - Added one-shot audit for 2026-06-16 (or first billing-cycle close)
     verifying observed behaviour matches spec §2 assumptions.

5. Tier A "permanent" wording unified
   - ALIGNMENT.md and ADR 0006 disagreed (permanent vs amendable).
     Unified as "Excluded by default. Cannot be re-included unless
     ADR 0006 is superseded/amended with new primary-source evidence."

6. OpenAI Tier D wording softened
   - Discussion #8338 was framed as "maintainer confirmed permissive";
     actual quote is a maintainer posture statement with explicit "I'm
     an engineer, not a lawyer" caveat.
   - Now: "maintainer signal indicates low risk; formal ToS pin pending."

Reviewer: OpenAI Codex CLI (external, fresh-context). Iron Rule 10
satisfied — internal opus reviewer was not the source of these
findings; reviewer and maintainer are distinct.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 16:21:37 +10:00
taodengandClaude Opus 4.7 0041fb1017 chore: bootstrap OLP v0.1 — multi-provider LLM proxy
Initial release. OLP (Open LLM Proxy) is a personal- and family-scale
multi-provider LLM proxy that supersedes OCP (Open Claude Proxy).

Trigger: Anthropic's 2026-05-14 announcement (effective 2026-06-15)
moves `claude -p` / Agent SDK / third-party agent traffic out of the
Pro/Max subscription pool into a separate fixed monthly Agent SDK
Credit pool. OCP's foundational assumption ("subscription = unlimited
within rate limits") breaks for Anthropic on that date. Spreading
risk across multiple providers is the structural response.

Phase 0 lands:
- ALIGNMENT.md (constitution: 5 Rules, 3 Authorities, 4-tier Risk
  Framework, 8-provider inventory)
- AGENTS.md (multi-tool agent guidelines; inherits cc-rules)
- CLAUDE.md (Claude-Code session instructions + release_kit overlay)
- README.md (phase-aware skeleton)
- docs/adr/0001-0006 (Founding ADRs: project founding / plugin
  architecture / IR design / fallback engine / cross-provider cache /
  provider inclusion + risk-tier framework)
- .github/PULL_REQUEST_TEMPLATE.md (8-radio Change Type + per-type
  Authority Evidence + Iron Rule 10 reviewer checklist)
- .github/workflows/alignment.yml (blacklist + Antigravity exclusion
  enforcement + models-registry validator + commit-citation soft check)
- .github/workflows/release.yml (auto-release on tag with version
  match check per Iron Rule 5)
- .github/workflows/test.yml (Node 20/24 matrix, bootstrap-tolerant)
- package.json, .gitignore, LICENSE (MIT), CHANGELOG.md

Provider inventory at bootstrap:
  Tier D (default-enabled):       anthropic, openai, mistral
  Tier C (opt-in):                grok, kimi
  Tier B (opt-in + consent):      minimax, glm, qwen
  Tier A (permanently excluded):  google-antigravity

Supersedes OCP ADR 0005 (No Multi-Provider) per OLP ADR 0001. OCP
will enter maintenance mode when OLP v0.1 ships per Phase 7 plan.

Iron Rule 10 gate: fresh-context independent opus reviewer audited
all 15 governance files against OLP v0.1 spec + OCP precedent.
Verdict: APPROVE_WITH_MINOR. Two minor findings folded in:
  1. alignment.yml heredoc EOF moved to column 0 (was indented;
     bash parse failed silently on real blacklist hits, printing
     a cryptic "syntax error" instead of the structured ALIGNMENT
     GUARDRAIL FAILURE banner).
  2. AGENTS.md clarified that the SPOT discipline for
     models-registry.json will be codified by a Phase-1 ADR (OLP
     ADR 0003 is currently the IR design, not a SPOT codification;
     OCP's ADR 0003 is the precedent but OLP's registry shape
     differs and warrants its own ADR).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 15:46:56 +10:00