mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
43ca4a65b0c347b054bec8a1ee48899a2d60968a
52
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ffe81f7a45 |
docs(spike): PI231 verify HOME/CODEX_HOME ephemeral redirect — Solution 1 PASS (#67)
Task #4 PI231 spike per ADR 0014 Amendment 1 § A1.2 Layer 1. Both providers PASS: claude v2.1.152: HOME redirected 100% of state writes — .claude.json (23KB), projects/, sessions/, backups/, .cache/. Real ~/.claude.json untouched. Symlinked credentials worked. codex v0.133.0: CODEX_HOME redirected ALL state — models_cache (200KB), 3 SQLite DBs (~250KB), cache, sessions, memories, skills, plugin clones. Real ~/.codex untouched. Symlinked auth.json worked. Architecture claims validated. Tasks #5-#8 unblocked. Caveats: codex refuses PATH-helper install under /tmp (warning, not blocker). codex v0.133.0 dropped --ask-for-approval; use -c approval_policy=never. Vibe not installed on PI231; spike deferred. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d67ba3d675 |
docs(adr): Phase 7 Amendment 1 — supersede PR-B with ephemeral-home + ISOLATION contract (#66)
Co-merging ADR 0014 Amendment 1 (4-layer Solution 1) + ADR 0002 Amendment 9 (Provider ISOLATION contract). Reviewed by 2 fresh-context opus subagents per Iron Rule 10. Second review verdict APPROVE after 6 citation-discipline fold-ins applied. PR-B outer-bwrap archived to branch phase-7-pr-b-outer-bwrap-snapshot. |
||
|
|
d0dcd281ef |
feat(sandbox): Phase 7 PR-B — anthropic.mjs spawn wrapped in sandbox-runtime
Wraps the claude CLI spawn in @anthropic-ai/sandbox-runtime per ADR 0014 § PR-B. Achieves multi-tenant filesystem + network isolation: the spawned claude subprocess can no longer read ~/.olp/keys.json, ~/.claude/.credentials.json, ~/.ssh/, or any other per-client OAuth material. Only api.anthropic.com and statsig.anthropic.com are reachable; only /tmp/olp-spawn/<uuid>/ is writable per spawn (ephemeral, UUID-scoped to prevent cross-request contamination). ## Authority citations - @anthropic-ai/sandbox-runtime v0.0.52 https://github.com/anthropic-experimental/sandbox-runtime dist/sandbox/sandbox-manager.js — SandboxManager.initialize(), wrapWithSandbox() dist/sandbox/sandbox-utils.js — getDefaultWritePaths() - 2026-05-28 spike report at /tmp/sandbox-spike/report.md on PI231: spike-anthropic.mjs (wrapWithSandbox call signature + NDJSON proof), spike-deny.mjs (cat ~/.olp/keys.json MUST fail) - OLP ADR 0014 § 2.1 PR-B, § 4.1 PR-B acceptance criteria - OLP ADR 0009 Amendment 1 § Caveats #3 (sandbox is cloud prerequisite) - OLP ALIGNMENT.md Rule 1 — provider plugin authority citation - cc-mem incident 2026-05-27 § 3 (multi-tenant OAuth token exposure via prompt injection) ## Files changed - lib/sandbox/manager.mjs (new): bootstrap + spawn-wrap layer. Exports: bootstrapSandbox(), isSandboxActive(), wrapSpawn(), __resetSandboxManagerForTests(). Config-at-boot model (one SandboxManager.initialize() at server start; per-request wrapWithSandbox() reads from already-initialized state). Transparent pass-through when inactive. - lib/providers/anthropic.mjs: spawn site wrapped via wrapSpawn({ bin, args, env, allowedDomains: ['api.anthropic.com','statsig.anthropic.com'] }). ADR 0009 Amendment 1 spawn args unchanged — only execution is wrapped. - server.mjs: bootstrapSandbox() called before server.listen(); startup banner logs sandbox.active state. /health.sandbox now includes active:boolean field (distinguishes "deps present" from "SandboxManager initialized and wrapping"). - test-features.mjs: Suite 43 (8 tests — manager unit: bootstrap state, idempotency, isSandboxActive, wrapSpawn passthrough, /health.active field) + Suite 44 (2 PI231-gated tests: 44a security negative test + 44b positive echo test, skipped by default, run with OLP_E2E_SANDBOX=1 npm test). - CHANGELOG.md, docs/adr/0014-sandbox-runtime-integration.md: PR-B status updated; ADR table + /health JSON example updated with active field. ## Test count 805 (pre-PR-B) → 813 (+8 Suite 43; Suite 44 skipped on macOS, runs on PI231) All 813 pass on macOS dev machine. 0 regressions. ## PI231 validation (required before merge — Suite 44) After apt-get install bubblewrap socat (ripgrep already present) + server restart: 1. curl /health → confirm sandbox.available=true AND sandbox.active=true 2. Real anthropic request → confirm stream-json still works end-to-end 3. OLP_E2E_SANDBOX=1 npm test → confirm Suite 44a (cat ~/.olp/keys.json MUST fail) and Suite 44b (echo SANDBOX_PROOF succeeds) Reviewer: must SSH PI231, run Suite 44, and confirm the ADR 0014 § 4.1 criteria. This commit is ready to push; do NOT push before Suite 44 transcript is captured. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
07d9c8a6ae |
feat(sandbox): Phase 7 PR-A — @anthropic-ai/sandbox-runtime dep + doctor + ADR 0014
Lays the foundation for multi-tenant provider spawning isolation per ADR 0014 § Decision. NO production wiring — PR-B (anthropic.mjs spawn wrap) lands separately and requires bubblewrap + socat + ripgrep installed on PI231 first. Files: - package.json: add @anthropic-ai/sandbox-runtime ^0.0.52 - lib/sandbox/doctor.mjs (new): preflight checkSandboxAvailability + describeSandboxStatus; pure module, no state, no initialize() call - server.mjs: /health response gains 'sandbox' field with availability + missing deps + install hint; result memoized via _sandboxStatusCache; __resetSandboxStatusCache() test seam exported - docs/adr/0014-sandbox-runtime-integration.md (new): 4-PR layered rollout decision per Iron Rule 11; PR-B/C/D acceptance criteria previewed; 6 spike pitfalls recorded; 282 lines - CHANGELOG.md: Unreleased entry under Phase 7 PR-A - test-features.mjs Suite 42 (+8 tests, 797 → 805, all pass) Authority: - @anthropic-ai/sandbox-runtime v0.0.52 — anthropic-experimental org https://github.com/anthropic-experimental/sandbox-runtime - 2026-05-28 PoC spike (verdict YELLOW; report at /tmp/sandbox-spike/ on PI231): clean arm64 install, dep check fail-closed behaviour confirmed, three PoC scripts parked - cc-mem incident memory § 4 (prior-art search — ecosystem hasn't solved multi-tenant fs/tool isolation) - ADR 0009 Amendment 1 § Caveats #3 — sandbox is cloud prerequisite - docs/plans/cloud-deployment-family.md § 5 Spike note (macOS): on dev Mac mini with rg via Homebrew, SandboxManager.isSupportedPlatform()=true and checkDependencies() returns no errors — macOS uses built-in sandbox-exec, not bwrap. /health.sandbox.available=true on macOS dev, false on PI231 until apt install. Operational follow-ups (NOT this PR): 1. sudo apt-get install -y bubblewrap socat ripgrep on PI231 (5-min window) 2. PR-B: lib/providers/anthropic.mjs spawn wrap + negative test (in-sandbox cat of OAuth token MUST fail) 3. PR-C: lib/providers/codex.mjs wrap with enableWeakerNestedSandbox:true 4. PR-D: cloud deployment plan update + unblock Tests: 797 → 805 (all pass, 0 fail). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
dd0c821272 |
docs(plans): cloud deployment plan (family testing phase) — draft
Plan-stage document for Oracle Cloud VM deployment with family-scale hardening. Covers architecture overview, TLS termination, iptables / OCI Security List policy, OAuth credential transfer, auth model (allow_anonymous off, per-key tier mapping), audit visibility, and the Phase 7 sandbox-runtime prerequisite. Status: Draft, pending Phase 7 (sandbox-runtime integration) completion. This commit makes the plan referenceable from ADR 0009 Amendment 1 § Caveats #3, the 2026-05-27 incident memory (cc-rules), and the forthcoming Phase 7 charter. NOT a code change — no ALIGNMENT.md authority citation required. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
97e7d16585 |
feat(anthropic): stream-json + --system-prompt (ADR 0009 Amendment 1)
Replaces claude -p --output-format text spawn with claude (no -p) --output-format stream-json --verbose --system-prompt. Authority: - claude CLI v2.1.104 § --output-format stream-json (verified 2026-05-27 empirical test on PI231: NDJSON event stream emits without -p) - claude CLI v2.1.104 § --verbose (required companion to stream-json) - claude CLI v2.1.104 § --system-prompt (full default-prompt replacement, suppresses env-block + tool descriptions) - claude CLI v2.1.104 § --no-session-persistence - OLP ADR 0009 Amendment 1 — decision lock + value re-anchoring - OLP ALIGNMENT.md Rule 1 — provider plugin authority citation Four orthogonal values delivered: 1. Hallucination fix: model no longer claims server-side cwd / OS / tools (verified bot self-check produces "I don't have local env" instead of "/home/tlab/olp") 2. ~64% per-request cost reduction: empirical Sonnet 4.6 $0.0216 → $0.0078, from ~30% input-token reduction (16,601 → 10,700) 3. NDJSON observability: rate_limit_event + usage + cache stats per request now available (future audit/dashboard work) 4. Possible 30-60 day bridge for Anthropic 2026-06-15 billing split (uncertain per P0 spike — third-party-app classification clause) Per ADR 0009 Amendment 1 § "Caveats": this is NOT a substitute for Phase 7 @anthropic-ai/sandbox-runtime work; sandbox remains required for any cloud / multi-tenant deployment. Cache key composition unchanged (ADR 0005 IR-based hash; on-the-wire format is internal to the provider plugin). Tests: 771 → 790 (+19 new in Suite 41; 9 existing mocks updated to emit valid NDJSON stream_event format instead of raw text). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
40f9453d88 |
fix(ir): accept OpenAI role=developer at entry surface, normalize to system (#65)
* fix(ir): accept OpenAI role=developer at entry surface, normalize to system Hermes Agent (v0.13+) and other modern openai-completions clients (Cline, Continue.dev) default to role=developer for the high-priority instruction slot when the model id matches OpenAI o1/o3+ reasoning family. OLP IR's validator rejected this with 400 IR validation failed: role must be one of system|user|assistant|tool, got "developer". Reproduced 2026-05-27 on PI230 Hermes v0.14.0 trying olp-codex/gpt-5.5 through PI231 OLP. olp-claude (Anthropic models) was unaffected because Hermes sends system role for Claude models (no developer-role concept on Anthropic side). Fix: extend openai-to-ir.mjs normalizeRole to map developer to system at the entry boundary. The IR canonical-four-roles invariant is preserved; every provider plugin's role-handling stays unchanged. Same pattern as the existing function-to-tool normalization that already lives in normalizeRole (function role was OpenAI-deprecated). Normalize-at- entry centralizes role-spec-evolution handling in one file rather than bloating the IR schema and forcing every provider plugin to handle each new role. Why not add developer to VALID_ROLES: it would require branches in three provider plugins (anthropic, codex, mistral) all mapping to system-style annotation anyway, plus wider IR surface for any future OpenAI role addition. Tests: two new pin tests in Suite IR translation: - developer role to system translation - mixed-role array including developer validates cleanly 768 to 770 tests, 0 fail. Verified Hermes via olp-codex no longer hits the IR rejection error after this fix (smoke run from PI230). ADR 0003 Amendment 3 documents the rationale + future-role policy (normalize-at-entry unless a role genuinely conveys provider-distinguishable semantics). Authority: OpenAI Responses API spec developer-role + Hermes Agent v0.14.0 reproduction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test+docs: PR #65 fold-in reviewer N2 + N3 N2 — Negative control test added: unknown role (e.g. "admin") still raises BadRequestError. Pins that the developer-to-system normalization is the only entry-surface escape hatch; any future widening of normalizeRole that returns role as-is for unknown inputs will be caught by this test. N3 — ADR 0003 Amendment 3 gains a "Cache-key impact" bullet documenting that a developer-form and system-form request with otherwise-identical content now share the same IR and thus the same cache key (per ADR 0005). This is by design and matches OpenAI's own backward-compat semantics; the note exists so a future debug session investigating "why does my developer request hit a cache entry from an old system request" finds the answer. Test count: 770 to 771, all pass. Skipped reviewer N1 (URL precision) — Amendment already cites multiple authorities including the Hermes/Cline tracking convention and live reproduction transcript; single-URL precision is over-spec. 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> |
||
|
|
e2f41eb60e |
docs(openclaw): switch to canonical env-var-reference apiKey + olp-claude rename + /models menu-only note (#64)
Three corrections to PR #63 surfaced via live Telegram bot bring-up on Mac mini -> PI231 OLP: 1. headers.Authorization workaround does NOT work for openai-shape model ids. PI231 audit log showed key_id=__anonymous__ for every gpt-* model sent via olp-codex despite headers.Authorization being set. Canonical OpenClaw pattern is apiKey: dollar-brace VAR brace env-var reference (per docs.openclaw.ai). Verified end-to-end: this attributes both olp-claude/* and olp-codex/* traffic to the bot owner key. Root cause: two unresolved upstream openclaw bugs: - #41157 (Gemini openai-completions Authorization not sent) - #1669 (Ollama provider ignores apiKey) 2. claude-local renamed to olp-claude for naming symmetry with olp-codex. 3. /models is menu-only in OpenClaw. Typing /models olp-codex/gpt-5.5 does NOT directly switch -- only opens the picker. Changes: - Two-modes table: client-mode Auth row now recommends env-var-ref apiKey - Mode B: NEW step walks through env var setup per OS - Provider JSON examples use VAR ref pattern (no more headers.Authorization) - Gotchas rewritten with three failure modes documented - New gotcha for /models menu-only semantics - Troubleshooting table: updated 401 row + new rows for anonymous attribution + menu-only Authority: Live reproduction on Mac mini OpenClaw v2026.5.22 + PI231 OLP v0.5.1. PI231 audit log verification. OpenClaw issues #41157, #1669, #29095. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
5d60a0599f |
docs(openclaw): expand integration guide — server-co-located vs client-mode + codex-via-OLP recipe (#63)
The previous version of docs/integrations/openclaw.md assumed
loopback OpenClaw+OLP co-located on the same host. Two real gaps:
1. **Client-mode (OpenClaw on a different machine than OLP) is
the common family-deployment shape**, and the loopback config
recipe doesn't work there. Specifically:
- apiKey field is silently overridden by OpenClaw's service-managed
env (`OPENCLAW_SERVICE_MANAGED_ENV_KEYS=DEEPSEEK_API_KEY,OPENAI_API_KEY`).
The OpenClaw gateway clobbers `process.env.OPENAI_API_KEY` with
whatever ChatGPT key it's been authed against, so when an
openai-completions provider falls back to env-var auth, the
wrong token gets sent and OLP rejects with 401.
- The escape hatch is `headers.Authorization: Bearer olp_...` in
the provider config, which beats the env fallback because
OpenClaw merges `providerConfig.headers` at request-build time
(sanitizeModelHeaders → outgoing request).
2. **codex / OpenAI models via OLP routing wasn't documented at
all.** OpenClaw's stock `openai-codex` provider talks to ChatGPT
directly (bypasses OLP, no audit, no per-key tracking). To get
gpt-5.5 / gpt-5.3-codex etc. routed through OLP for per-key
observability, you have to register a custom `olp-openai`
provider — recipe now included.
Changes to docs/integrations/openclaw.md:
- Restructured into Mode A (server-co-located) vs Mode B (client-mode)
with explicit "pick yours" table up top.
- Mode B fully written out — proxyUrl with server IP, headers.Authorization
override, claude-local provider listed with the three Claude IDs that
OLP's /v1/models actually exposes.
- New § "Using codex / OpenAI models through OLP" with olp-openai
provider recipe + agents.defaults.models alias examples + warning
about why NOT to use OpenClaw's stock openai-codex provider.
- New § Gotchas covering: OPENCLAW_SERVICE_MANAGED_ENV_KEYS clobber,
default-agent-model points-at-removed-provider, /new doesn't reset
model selection, the openclaw.extensions schema-drift (was already
there).
- New § Troubleshooting table mapping symptoms → root causes → fixes.
Discovered live on Mac mini 2026-05-27 during PI231-as-server topology
bring-up. The "apiKey shadowed by env" finding required reading
OpenClaw source (`/opt/homebrew/lib/node_modules/openclaw/dist/...`)
to confirm the `providerConfig.headers` precedence. Verified end-to-end:
Telegram bot now routes free-text through claude-local → PI231 OLP →
claude spawn → reply; and `/models olp-openai/gpt-5.5` switches to
codex via OLP with per-key audit visible on dashboard.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
ea0392f744 |
fix(olp-plugin): add openclaw.extensions field for modern OpenClaw v2026.5+ install (#62)
Discovered 2026-05-27 while bringing up Mac mini as OpenClaw client of
PI231 OLP server: `openclaw plugins install ./olp-plugin/` fails with
package.json missing openclaw.extensions; update the plugin package to
include openclaw.extensions (for example ["./dist/index.js"])
OpenClaw v2026.5.22 enforces a stricter plugin-manifest validation than
earlier revisions. The olp-plugin/package.json still used the legacy
schema (`type: "plugin"` + `pluginManifest`) without the new
`extensions: [./index.js]` field that modern OpenClaw requires for the
gateway plugin-discovery loop.
Workaround until this fix lands: manually `cp -R olp-plugin
~/.openclaw/extensions/olp` (symlink also works per docs Option B).
Changes:
- olp-plugin/package.json: added `extensions: ["./index.js"]` to the
openclaw block. Keeps legacy fields (`type`, `id`, `pluginManifest`)
for backwards compat with pre-2026.5 OpenClaw revisions; the modern
loader only reads `extensions`.
- docs/integrations/openclaw.md:
- § 3 Configure: rewrote example to show the modern `plugins.allow` +
`plugins.entries.olp.{enabled, config}` schema. The previous shape
(`plugins.olp.{proxyUrl, apiKey}` at top level) doesn't match what
OpenClaw actually picks up.
- § Known issues: added a paragraph documenting the
`openclaw.extensions` schema-drift event + recovery path (pull latest
OLP, or symlink fallback).
Verified post-fix: `openclaw plugins install ./olp-plugin/` succeeds on
Mac mini OpenClaw v2026.5.22.
Authority:
- Reproduced live on Mac mini 2026-05-27 during PI231-as-server topology
bring-up
- OpenClaw stock plugin format (sample: `/opt/homebrew/lib/node_modules/
openclaw/dist/extensions/anthropic/package.json` uses `extensions:
["./index.js"]`)
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
9dc070bc53 |
docs: refresh dashboard screenshot with live MacBook v0.5.1 data (#60)
D82 originally rendered docs/img/dashboard-v0.5.0.png from synthetic
quota_v2 data (the live API wasn't probed at the time of capture).
After v0.5.1 hotfix shipped, the screenshot is replaced with a render
from a real /v0/management/dashboard-data response captured 2026-05-27
from the MacBook OLP server on commit
|
||
|
|
fa2d1af130 |
feat+test: F4 CLI/plugin quota_v2 migration + v1.x #7 AUTH_MISSING test pin (#59)
## F4 — bin/olp.mjs + olp-plugin/index.js migrate to quota_v2 Codex post-v0.5.0 review Q4: both `olp usage` CLI and `/olp usage` plugin handler read legacy `body.quota` shape, which never has `percent_used` or meaningful `available` data → display always fell through to "no quota api" even when anthropic live quota was visible on the dashboard (D81/D82). Authority: codex review Q4 (PR #58); ADR 0008 Amendment 2 (quota_v2 shape: { provider, status, utilization, reset, representative_claim, fallback_percentage, overage, failure?, failure_kind?, backoff_until? }). ### bin/olp.mjs cmdUsage - When `body.quota_v2` present (non-empty array, server v0.5.0+): render per-provider rows with status badge (live/stale/unreachable/unavailable), 5h + 7d utilization % (color-coded: green <50% / yellow 50-80% / red ≥80%), reset countdowns, binding claim, ⚠ stale / ❌ unreachable annotations. - When `body.quota_v2` absent: fall through to existing legacy `body.quota` rendering (preserves backwards compat with pre-v0.5.0 servers). - Added `formatResetCountdown(epochSeconds)` — 5-range formatter (past / <1h / <24h / <7d / ≥7d), ported from dashboard.html D82. Exported for tests. Kept in bin/olp.mjs (not lib/) per spec guidance. - Added `formatAgo(diffMs)` — small helper for stale-row age display. ### olp-plugin/index.js fmtUsage() - Same quota_v2 migration. Plain-text output (no ANSI), one line per provider. Legacy body.quota fallback preserved. - Added `pluginFormatResetCountdown(epochSeconds)` — intentionally duplicated from bin/olp.mjs (olp-plugin ships as a separate package and must not import from bin/). Exported for tests. - Also fixed fmtUsage to read `w.request_count` (dashboard-data shape) in addition to legacy `w.requests` (OCP-era shape) for completeness. ### Backwards compat - Pre-v0.5.0 server returns body.quota only → both surfaces use legacy display (unchanged behaviour). - v0.5.0+ server returns both quota and quota_v2 → both surfaces prefer quota_v2. - Legacy code paths kept (5-10 lines each, not deleted). ## v1.x roadmap #7 — AUTH_MISSING tuple path test coverage — CLOSED The dedicated test was already shipped at D56 (test-features.mjs line 6255: 'engine: AUTH_MISSING terminates chain, fallbackDetail tuple records trigger_type:"auth_missing" (D56, v1.x roadmap #7)'). This commit closes the roadmap entry with a date stamp and PR reference. Authority: docs/v1x-roadmap.md § "#7 — AUTH_MISSING tuple path test coverage (D40 follow-up)". ## Tests Suite 40 (9 new tests — 40a through 40i): 40a — cmdUsage parses quota_v2 live rows (mock server) 40b — cmdUsage falls back to legacy body.quota when quota_v2 absent 40c — olp-plugin fmtUsage parses quota_v2 live row 40d — olp-plugin fmtUsage falls back to legacy body.quota 40e — formatResetCountdown covers all 5 time ranges 40f — pluginFormatResetCountdown covers past/<1h/<24h/<7d/≥7d 40g — cmdUsage renders quota_v2 stale row with ⚠ stale note 40h — cmdUsage renders quota_v2 unreachable row with ❌ indicator 40i — cmdUsage renders quota_v2 unavailable rows correctly 759 → 768 tests, 0 fail. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
bddf2cba1e |
release(v0.5.1): hotfix — quota probe cache/backoff/schema-drift correctness (codex review) (#58)
* release(v0.5.1): hotfix — quota probe cache/backoff/schema-drift correctness (codex review) Addresses three production-quality findings from codex's post-v0.5.0 review (codex review on PR #57, reproduced with local mocks): F1 [P1] — Doctor bypass of cache + backoff (ADR 0013 Rule 3) `anthropic.quota_probe_reachable` called `_probeOnce(auth)` directly, bypassing `quotaProbeState.backoffUntil`. Successive `olp doctor` invocations within a backoff window each hit upstream — violating ADR 0013 Rule 3 (backoff is mandatory for ALL consumers). Fix: doctor now routes through `quotaStatus()`. ADR 0013 Rule 3 clarified: "All consumers of `quotaStatus()`, including `olp doctor` checks, MUST route through `quotaStatus()` and MUST NOT call `_probeOnce()` directly." F2 [P2] — 200 with empty ratelimit headers cached as live data (ADR 0013 Rule 5) A 200 OK with zero `anthropic-ratelimit-*` headers was cached as `stale: false` (live). Minimum-viable-schema gate added to `_probeOnce`: requires 5h-utilization + 5h-reset + 7d-utilization + 7d-reset present; absence → `failureKind: 'schema_drift'`, backoff scheduled, result NOT cached. ADR 0013 Rule 5 updated with the gate specification. F3 [P2] — Dashboard-data loses failure detail (ADR 0013 Rule 6) `aggregateProviderQuota()` collapsed all failure modes into `status: 'unavailable'` ("no public quota api or probe disabled") — same as providers with no API at all. Fix: `quotaStatus()` v0.5.1 contract — `null` ONLY for opt-in-off; failures return `{ probe_status: 'unreachable', failure: { kind, message, backoff_until? } }`. New `failure_kind` enum: no_credentials | auth_failed | rate_limited | schema_drift | network | other. Dashboard renders `unreachable` with red border + failure detail. Authority: ADR 0013 Rules 3, 5, 6 (cache + backoff + schema-drift + failure transparency) ADR 0008 Amendment 2 (richer quota_v2 shape; new unreachable status) ADR 0002 Amendment 8 unchanged (constitutional permission for the probe) Codex review findings F1–F3 (codex on PR #57) Changes: - lib/providers/anthropic.mjs: quotaProbeState gains lastError + failureKind; _probeOnce: min-field gate + failureKind population; quotaStatus(): v0.5.1 contract (null=disabled only; probe_status:live/stale/unreachable); doctorChecks routes through quotaStatus(); reset functions updated - lib/audit-query.mjs: _normalizeAnthropicQuota handles probe_status field; aggregateProviderQuota emits failure/failure_kind/backoff_until; unreachable status - dashboard.html: unreachable CSS classes + render path + footer v0.5.1 - test-features.mjs: 38f/j/l updated for v0.5.1 shape; 38r refactored for F1; 38g/k gain probe_status assertions; 38u/v/w new regression tests; 756→759 tests - docs/adr/0008: Amendment 2 (richer ProviderQuotaEntry + quotaStatus contract) - docs/adr/0013: Rule 3 clarification (doctor must use quotaStatus); Rule 5 min-viable-schema gate specification - package.json: 0.5.0 → 0.5.1 - CHANGELOG.md: v0.5.1 hotfix entry promoted from Unreleased - README.md / AGENTS.md: Phase 5 closed at v0.5.1; Phase 6 next Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs+code: PR #58 fold-in — reviewer Nits #1 + #2 (ADR doc-drift + opt_in_off enum) Fresh-context reviewer (PR #58) verdict: APPROVE_WITH_MINOR, 0 blocking, 4 nits. Folding in #1 + #2 (both 1-line cosmetic-but-correct fixes). Deferring #3 (test-only edge case in module-level seam restore) and #4 (pre-existing codex F4 — bin/olp.mjs + olp-plugin still read legacy quota shape; separate PR planned). Nit #1 — ADR 0002 Amendment 8 documentation drift. Amendment 8 at v0.5.0 line 20 said "the function returns `null` rather than throwing", and line 33 said "stale-cache-on-failure (`null` is returned only when no cache entry exists; if a stale entry exists it's returned with a `stale: true` marker)". v0.5.1 refined this contract: `null` is now reserved STRICTLY for opt-in-off, and all failure modes return `{ probe_status: 'unreachable' | 'stale', failure: {...} }`. The substantive idempotent-failure constraint (no throw to caller) is unchanged. The operational description in Amendment 8 was stale — fixed to cross-reference ADR 0008 Amendment 2 + ADR 0013 Rule 6 for the v0.5.1 contract refinement. Also references Suite 38 (38u/38v/38w) as the regression coverage producing the new shape. Nit #2 — `failureKind: 'opt_in_off'` declared but never produced. The enum value was listed in both the code comment (anthropic.mjs:250) and ADR 0008 Amendment 2 (line 30) but never actually assigned — because when opt-in is off, `quotaStatus()` returns the literal `null` BEFORE any state mutation happens. The enum value was dead. Fix: removed `opt_in_off` from both enum declarations + added an inline note explaining that consumers (audit-query, doctor) distinguish opt-in-off by checking `quotaStatus() === null`, not via failureKind. Deferred: - Nit #3 (test-seam restore edge case): if a caller pre-sets `_quotaAuthReadFnForTest` AND passes a non-default `_authReadFn`, the finally block restores to null clobbering pre-set value. Test-only impact, no production risk. Pure hygiene; defer. - Nit #4 (codex F4): bin/olp.mjs cmdUsage and olp-plugin/index.js still read legacy `body.quota` field, never consume `quota_v2`. Reviewer confirmed neither crashes — both gracefully fall through to "no quota api" branch. Out of scope for this hotfix per the hotfix dispatch contract; separate PR will migrate them. Tests: 759/759 still pass post-fold-in. No test changes needed. Authority: PR #58 review thread + ADR 0013 Rule 6 (failure transparency) + ADR 0008 Amendment 2 (ProviderQuotaEntry v0.5.1 shape). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
d872330c9e |
docs: Phase 5 close-prep — README § Plan Usage + supported-provider matrix + live E2E artifact (#56)
* docs: Phase 5 close-prep — README § Plan Usage + supported-provider matrix + live E2E artifact
Pre-close documentation pass per ADR 0012 § Exit gate items 3 + 9.
v0.5.0 close PR (package.json bump + CHANGELOG promotion + tag) is
maintainer-triggered per CLAUDE.md release_kit.phase_close_trigger.
What this PR ships:
(1) README § What you get — added Plan-usage probe bullet with anchor to
the new § Plan Usage section.
(2) README § Supported Providers — table gained "Quota probe (v0.5.0+)"
column. Anthropic ✅ live (13 anthropic-ratelimit-unified-* headers,
opt-in via quota_probe_enabled). Codex ❌ no public quota API.
Mistral ❌ per D84 spike 2026-05-26 (no /v1/usage at docs.mistral.ai/api).
Others TBD (Phase 8+).
(3) README new § Plan Usage (live quota probe) between § Configuration and
§ API Endpoints. Documents: how the probe works (POST /v1/messages
with max_tokens:1 + headers-only parse), opt-in config block, OAuth
token sources (env / .credentials.json / macOS Keychain), olp doctor
integration, schema-drift protection (Path A `strings` over compiled
binary + Path B live probe diff), full ADR cross-refs. Embeds the
dashboard screenshot below.
(4) README § API Endpoints — updated /dashboard / /v0/management/dashboard-data
/ /v0/management/quota rows to reflect Phase 5 changes:
- /dashboard: Claude.ai-style Plan Usage section + 60s auto-refresh + manual button
- /v0/management/dashboard-data: new quota_v2 field alongside legacy quota
- /v0/management/quota: mirrors dashboard-data quota_v2 for scripted monitoring
(5) docs/img/dashboard-v0.5.0.png — dashboard screenshot rendered from
live MacBook server JSON (utilization_5h: 36%, utilization_7d: 34%,
representative_claim: five_hour, overage: rejected) merged into D83
dashboard.html. Identifying IPs / hostnames / Tailscale nodes redacted
from rendered output per public-repo hygiene rule (cc-rules AGENTS.md).
(6) docs/exit-gates/phase-5-e2e.json — sanitized record of the Live
MacBook E2E verification (exit-gate item 9). Confirms quota_v2 shape
produced live, anthropic probe returned 12/13 fields (overage-reset
absent per audit memory — only fires on active overage), opt-in
mechanism verified end-to-end (config flag flipped → server probed
→ dashboard renders). Post-test cleanup noted: temp owner key
revoked, config restored to baseline, test server terminated.
Remaining exit-gate items for the v0.5.0 close PR (maintainer-triggered):
- CHANGELOG.md "Unreleased" → "## v0.5.0 — <date>" promotion
- package.json 0.4.4 → 0.5.0
- CLAUDE.md release_kit.phase_rolling_mode: Phase 5 → Phase 6,
0.5.0-phase5 → 0.6.0-phase6
- Tag v0.5.0 push (triggers .github/workflows/release.yml auto-release)
Authority cited:
- ADR 0012 § Exit gate items 3 + 9
- ADR 0012 Amendment 1 (D84 NO-GO rationale in Mistral row)
- ADR 0002 Amendment 8 + ADR 0013 (the constitutional context for the
Plan Usage section's "how it works" framing)
- D80 commit
|
||
|
|
a41420d0fc |
feat: D82 — dashboard UI Claude.ai-style (Phase 5) (#54)
* feat: D82 — dashboard UI Claude.ai-style per-provider rows (Phase 5)
Implements ADR 0012 D82: restructures dashboard.html to render
quota_v2 data (produced by D81 / PR #53) in a Claude.ai-style
per-provider row layout. Closes v1.x roadmap #8.
## What changed
### dashboard.html (A–G)
A. New "Plan Usage" section at top (full-width, above the 2-col grid):
- Per-provider rows rendered from `data.quota_v2`
- Each row: provider badge (colored chip), status dot + chip
(live/stale/unavailable), schema version tag
- Two utilization bars (5h + 7d) with:
- Rounded gradient bar (green <50% / amber 50-80% / red >80%)
- Label "Current 5-hour session: 49%" / "Weekly all-models: 31%"
- Right-side reset countdown (see B)
- Bottom chips: representative-claim badge (purple), overage chip
(amber/green), fallback-percentage chip, last-fresh "Updated N
min ago" tag; stale rows show amber ⚠ stale data chip with tooltip
- Unavailable rows: provider badge + reason text only; no bars
B. formatResetCountdown(epochSeconds):
- < 1 hour: "Resets in 23 min"
- 1–24 hours: "Resets in 12hr 30min"
- < 7 days: "Resets Sun 9:00 PM" (weekday + 12h time)
- >= 7 days: "Resets May 31 9:00 PM" (month day + time)
- past: "Resetting now…"
Uses toLocaleString('en-US', { hour12: true }).
C. 60s auto-refresh (quota_v2 only) with visibilityState guard:
- Separate timer (quotaRefreshTimer); does NOT replace the 30s poll
- Pauses on 'hidden'; resumes + immediate re-fetch on 'visible'
- Other 3 panels (24h, 30d, top fallback) keep 30s cadence unchanged
D. Manual refresh button (↻ Refresh) in Plan Usage header:
- 2-second spam guard (button disables post-click)
- Spinning ⟳ icon during fetch
- Re-enables after fetch completes (success or error)
E. Graceful quota_v2 / legacy quota fallback:
- If data.quota_v2 is present and non-empty → render Plan Usage rows;
hide legacy "Quota (per provider)" panel
- If data.quota_v2 is absent/empty → show note in Plan Usage area;
surface legacy data.quota in the original table panel
- Guards operator running an older OLP build (pre-D81)
F. Visual polish: rounded bars, gradient fills, airy whitespace, mobile-
responsive (bars reflow on narrow viewports via flex-wrap). Color
palette: #10b981 (green), #f59e0b (amber), #ef4444 (red) matching
Tailwind emerald/amber/red-500 per spec.
G. Other 3 panels (24h, 30d, top fallback) and their 30s poll cadence
are IDENTICAL to D51. Only the Quota panel restructures.
### docs/v1x-roadmap.md (H)
Marks entry #8 as "✅ CLOSED (D82, v0.5.0)". Adds closure status,
PR ref, and a brief note inside the entry body. Updates reading-order
header paragraph to include #8 in the closed list.
## Authority + citations
- ADR 0012 D82 — Claude.ai-style restructure D-day spec
(docs/adr/0012-phase-5-charter-quota-probes-dashboard.md § D-day table)
- D81 PR #53 — quota_v2 shape producer (commit
|
||
|
|
5288493f19 |
feat: D81 — dashboard-data quota_v2 shape + models-registry schema_version (Phase 5) (#53)
Authority citations (required per CLAUDE.md § Hard requirements):
1. ADR 0012 D81 — the D-day being implemented (Phase 5 charter, audit-query
+ dashboard-data extension row in § D-day table)
2. ADR 0013 Rule 5 — schema_version in models-registry.json mandate. D80
used a local constant QUOTA_SCHEMA_VERSION = '2026-05-26' (reviewer nit
#4 at PR #52). D81 folds in Rule 5 compliance: adds quota_probe.schema_version
to models-registry.json and has anthropic.mjs read from there with the
constant as fallback via _resolveSchemaVersion().
3. ADR 0008 — the audit-query design being amended (Amendment 1 added at D81
to docs/adr/0008-dashboard-and-audit-query.md documenting: quota_probe in
registry, aggregateProviderQuota() API shape, quota_v2 key, deprecation
timeline for legacy quota key).
4. D80 PR #52 (commit
|
||
|
|
82d2e1cbea |
feat: D80 — anthropic plan-usage probe port (Phase 5) (#52)
Port OCP server.mjs:842-1109 plan-usage probe to lib/providers/anthropic.mjs:quotaStatus(). ## Authority citations (CLAUDE.md hard requirement #1) 1. Schema pin: ~/.cc-rules/memory/learnings/anthropic_plan_usage_probe_schema_2026_05_26.md — 13-field canonical schema verified live 2026-05-26; 3 fields new vs OCP 2026-04 capture (5h-status, 7d-status, overage-reset). 2. OCP port source: OCP server.mjs:842-1109 — usageCache, oauthRefreshBackoff, getOAuthCredentials, refreshOAuthToken, fetchUsageFromApi, parseRateLimitHeaders. Claude Code CLI uses this same POST /v1/messages internally (verified 2026-05-26 by `strings` on @anthropic-ai/claude-code v2.1.142 / v2.1.150 Mach-O binary — see audit memory). This is observed CLI behaviour, not invention. 3. ADR 0002 Amendment 8 — READ-ONLY exemption for quotaStatus() direct-API access. Three constraints satisfied: READ-ONLY (max_tokens:1, body discarded), subscription-scope (same readAuthArtifact() creds as spawn path), idempotent-failure (returns null / stale on any error, never throws). 4. ADR 0013 — OAuth READ-ONLY consumption rules. All 7 rules satisfied: Rule 1: credential reuse via readAuthArtifact() (env → .credentials.json → keychain). Rule 2: only POST /v1/messages (no other endpoints). Body discarded; headers-only. Rule 3: 5min TTL cache; 60s–3600s exponential backoff; stale-on-failure. Rule 4: opt-in via ~/.olp/config.json providers.anthropic.quota_probe_enabled (default false). Rule 5: schema pin committed to memory file; drift detection protocol in place. Rule 6: doctor check anthropic.quota_probe_reachable surfaces probe status. Rule 7: does not govern spawn-path refresh (separate concern). 5. ADR 0012 D80 — Phase 5 charter: this commit is the D80 deliverable. ## Live probe transcript (2026-05-26 from MacBook keychain OAuth credentials) Path B verification per ADR 0013 Rule 5: curl -s -i -m 20 -X POST https://api.anthropic.com/v1/messages \ -H "Authorization: Bearer <token>" \ -H "anthropic-beta: oauth-2025-04-20" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{"model":"claude-haiku-4-5-20251001","max_tokens":1,"messages":[{"role":"user","content":"."}]}' Response (header lines only): HTTP/2 200 anthropic-ratelimit-unified-status: allowed anthropic-ratelimit-unified-5h-status: allowed anthropic-ratelimit-unified-5h-reset: 1779794400 anthropic-ratelimit-unified-5h-utilization: 0.09 anthropic-ratelimit-unified-7d-status: allowed anthropic-ratelimit-unified-7d-reset: 1780225200 anthropic-ratelimit-unified-7d-utilization: 0.32 anthropic-ratelimit-unified-representative-claim: five_hour anthropic-ratelimit-unified-fallback-percentage: 0.5 anthropic-ratelimit-unified-reset: 1779794400 anthropic-ratelimit-unified-overage-disabled-reason: org_level_disabled_until anthropic-ratelimit-unified-overage-status: rejected (no anthropic-ratelimit-unified-overage-reset — expected: only present on active overage) 12/13 fields present. overage-reset absent = no active overage (expected per audit memory). All fields parsed correctly by _parseRateLimitHeaders(). Confirmed via D80 smoke test. ## Implementation A. quotaStatus() — full probe implementation replacing D4 null stub: - _readProviderConfig('anthropic') gate (Rule 4 opt-in) - 5min module-level cache check (quotaProbeState.cache) - 60s–3600s exponential backoff check (quotaProbeState.backoffUntil / backoffMs) - readAuthArtifact() credential read (env → .credentials.json → macOS keychain) - _probeOnce() → POST /v1/messages with 4 required headers; body discarded - 401/403 → single refresh-and-retry via _refreshAccessToken() - On success: cache { fetchedAt, data } + reset backoff to MIN - On failure: _scheduleBackoff() (doubles backoffMs, caps at MAX) + return stale or null - Return shape: { probedAt, source, schemaVersion, stale, fields:{...13}, raw:{...} } B. _parseRateLimitHeaders() — all 13 fields (3 new vs OCP): - status, representative_claim, reset, fallback_percentage (aggregate) - status_5h, utilization_5h, reset_5h (5h window) - status_7d, utilization_7d, reset_7d (7d window) - overage_status, overage_disabled_reason, overage_reset (overage) - Numeric strings → numbers; missing fields → null (not 0 or "unknown") C. _refreshAccessToken() — uses Node.js built-in https (no fetch/3rd-party deps). Shared backoff state via quotaProbeState. Max one refresh per backoff window. D. _probeOnce() — uses Node.js built-in https. 15s timeout. Drains + discards body. E. _readProviderConfig() — reads ~/.olp/config.json providers.<name> block. OLP_HOME respected (same as lib/keys.mjs). Never throws; returns {} on error. F. doctorChecks() — new anthropic.quota_probe_reachable check (ADR 0013 Rule 6): - status: ok when probe disabled (returns advisory message) - status: ok when probe succeeds (shows utilization %) - status: warn when stale cache exists (probe failed but cache present) - status: fail when no cache + probe failed (fix_commands + human_steps recipe) G. docs/v1x-roadmap.md — #8 Dashboard enrichment entry (D79 follow-up) added. ## Tests - All 720 existing tests pass (npm test). - Suite 33j updated to include anthropic.quota_probe_reachable in the expected probe set (3 probes total, previously 2). - D83 (Suite 38) will add quota-probe unit tests with mock HTTP server. ## What NOT changed - dashboard.html — untouched (D82) - lib/audit-query.mjs — untouched (D81) - lib/providers/codex.mjs, mistral.mjs — untouched (D84 NO-GO per ADR 0012 Amendment 1) Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
187e79321f |
docs: D79 cleanup — ALIGNMENT.md N9 + ADR 0012 Amendment 1 (D84 NO-GO) (#51)
Two governance-layer cleanup items bundled per Iron Rule 11 (same layer + same severity). Both are docs-only, both close-loop on D79 reviewer + spike. (1) ALIGNMENT.md N9 cross-reference — the third outside-PR nit from the D79 fresh-context reviewer (PR #50). Class-specific Exceptions section gains its first numbered exception (Anthropic plan-usage probe via direct /v1/messages). Previously the section said "(none at project founding)" + invited "future Rule 3 deviation"; this entry is a Rule 2 deviation, so the section header text was updated to "Any Rule 2 or Rule 3 deviation". (2) ADR 0012 Amendment 1 — D84 Mistral NO-GO per 2026-05-26 spike. Per the D79 reviewer N5 fold-in, the Mistral GO/NO-GO decision was scheduled for D79 close (before D80 starts). Spike completed 2026-05-26 with verdict NO-GO: - docs.mistral.ai/api has no usage/quota/credits endpoint - Direct probe /v1/usage returns 404 - Mistral's "Limits and Usage" help points only at web console UI - No x-ratelimit-* response headers documented on /v1/chat/completions - OLP mistral.mjs DL-7 comment already records this from independent D8 investigation Disposition: D84 row struck through in D-day plan. Mistral dashboard row in D82 will show "spend tracking only" badge from audit-query aggregates. DL-7 remains as the documented re-entry point. Phase 5 total D-day budget revised ~6 → ~5 (anthropic-only quota probe). Outside-PR nits N6 + N7 already addressed in ~/.cc-rules commit 9fa533a (audit memory chronology + D-day mapping fixes). Authority: - N9: PR #50 review thread (D79 fresh-context opus reviewer) - D84 NO-GO: docs.mistral.ai/api spike 2026-05-26; OLP DL-7 precedent Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
1605400052 |
docs: D79 — Phase 5 constitutional layer (ADR 0012 + ADR 0002 Amendment 8 + ADR 0013) (#50)
* docs: D79 — Phase 5 constitutional layer (ADR 0012 + ADR 0002 Amendment 8 + ADR 0013)
Three coupled governance documents land together as the Phase 5 constitutional
layer (Iron Rule 11 IDR — reviewing them separately cannot verify
consumer-producer alignment). Phase 5 opens 2026-05-26; D79 is governance-only,
no code changes.
- ADR 0012 (Phase 5 charter) — port OCP's plan-usage probe to
`lib/providers/anthropic.mjs:quotaStatus()` (D80) + extend
`/v0/management/dashboard-data` for new shape (D81) + Claude.ai-style
dashboard restructure with 1-min auto-refresh + manual refresh (D82) +
Suite 38/39 tests (D83) + optional mistral probe at D84 (codex skipped —
no public API) + v0.5.0 close (maintainer-triggered). ~6 D-days.
- ADR 0002 Amendment 8 (direct-API READ-ONLY exemption) — plugin contract
amendment permitting quotaStatus() to call provider HTTP APIs directly,
subject to three constraints: READ-ONLY (no mutating calls),
subscription-scope (reuses spawn-path credentials), idempotent failure
(returns null on any error, never throws). No other contract method gains
this permission.
- ADR 0013 (OAuth READ-ONLY consumption + schema-drift mitigation) —
implementation discipline for ADR 0002 Amendment 8. Seven rules: (1)
credential reuse via plugin's readAuthArtifact(), (2) READ-ONLY at wire
(max_tokens:1, headers-only parse, body discarded), (3) cache TTL 5min +
60s-3600s exponential refresh backoff + stale-cache-on-failure, (4)
opt-in via `~/.olp/config.json providers.<name>.quota_probe_enabled`
(default false), (5) schema-drift mitigation via dual-path verification
(compiled-binary `strings` + live API probe diff), (6) failure
transparency through `olp doctor` + dashboard staleness markers, (7)
explicit out-of-scope clarifications.
Pre-flight institutional-knowledge audit (Iron Rule 12 prior-art search)
captured at `~/.cc-rules/memory/learnings/anthropic_plan_usage_probe_schema_2026_05_26.md`
(cross-machine git-sync). Findings:
- OCP probe (server.mjs:842-1109) still works against current
api.anthropic.com — tested live from PI231 OAuth credentials 2026-05-26.
- 13 `anthropic-ratelimit-unified-*` response headers confirmed
(3 new since OCP 2026-04 capture: 5h-status, 7d-status, overage-reset;
no removals or renames).
- Claude Code v2.1.x is now distributed as compiled native binary
(Mach-O on macOS, ELF on Linux) — OCP's "grep cli.js" verification is no
longer applicable. ADR 0013 Rule 5 replaces with dual-path verification
(`strings` over the binary + live API probe diff).
- OAuth refresh path (platform.claude.com/v1/oauth/token + client_id
9d1c250a-...) all unchanged.
Authority:
- ALIGNMENT.md Rule 1 (citation): audit memory + OCP server.mjs:842-1109 +
live `/v1/messages` probe transcript 2026-05-26.
- ALIGNMENT.md Rule 2 (provider-CLI-as-authority): Amendment 8 documents the
exemption; the probe mirrors observed CLI behaviour.
- ALIGNMENT.md Rule 5 (CI alignment.yml): not triggered (docs/ excluded by
workflow `paths:` filter); blacklisted `/api/oauth/usage` token referenced
only as meta-references ("must continue to blacklist").
- CLAUDE.md release_kit overlay: Phase 5 open; D-day commits stay under
"Unreleased" until maintainer-triggered v0.5.0 close.
Iron Rule 10: fresh-context reviewer required before merge per CLAUDE.md
hard requirement #3.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: D79 fold-in — 6 in-PR nits from fresh-context reviewer (PR #50)
Reviewer verdict: APPROVE_WITH_MINOR (0 blocking, 9 nits — 6 in-PR, 3 outside-PR).
Folding in the 6 in-PR nits here; the 3 outside-PR ones (audit-memory chronology,
audit-memory D80/D81 mapping, ALIGNMENT.md cross-ref to Amendment 8) are deferred.
Folded-in nits:
1. ADR 0002 Amendment 8: added a 5th "does NOT permit" bullet making per-endpoint
containment explicit. Amendment 8 permits the kind of call; ADR 0013 Rule 2
enumerates which specific endpoint. Re-opening per-endpoint scope requires an
ADR 0013 amendment, not a Amendment-8-only interpretation.
2. ADR 0013 Rule 5: added "Path A prerequisites" paragraph documenting that
strings (GNU/BSD binutils/coreutils) + Claude Code v2.1.x install are required
for compiled-binary verification. Windows reviewers need WSL or binutils-mingw.
3. ADR 0013 Rule 5: added "Trigger for re-running the diff" paragraph naming three
explicit hooks for major-version-bump detection: Annual Alignment Audit
(14 May), olp doctor anthropic.quota_probe_reachable failure, manual
maintainer attention. Documented graceful-degradation failure mode.
4. ADR 0012 D80 estimate: 1.5d → 2d. Reviewer flagged 1.5d as optimistic
compared to D61-D63 (2.5d for narrower SSE heartbeat scope). Aligning.
5. ADR 0012 D84: moved Mistral GO/NO-GO spike to D79 close (before D80 starts),
not mid-phase. Reduces mid-phase scope drift risk. Outcome will be amended
into this charter as a D79-close amendment.
6. ADR 0012 Authority + cross-references: replaced "Claude Code <version> §
OAuth bearer + ratelimit headers" with "compiled-binary strings evidence
per audit memory § Path A". Claude Code v2.1.x has no traditional section
structure because it is a Mach-O / ELF compiled binary.
Deferred (outside-PR) nits documented in PR review thread:
- Audit memory historical-table chronology error (cb6c2a8 placed last; was
second chronologically — narrative arc still holds, dates need correction).
- Audit memory D80/D81 mapping mismatch (memory says D81 adds new fields;
ADR 0012 says D80 parses all 13).
- ALIGNMENT.md cross-reference to Amendment 8 (Class-specific Exceptions
subsection should name Amendment 8 explicitly).
All three outside-PR items are docs-only and not load-bearing for D80
implementation. Will fold in either at D80 commit (audit-memory updates)
or as a tiny constitutional cleanup PR (ALIGNMENT.md cross-ref).
Iron Rule 10: reviewer was a fresh-context opus subagent; their full review
is recorded in PR #50 thread.
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>
|
||
|
|
6605b7b14a |
feat+docs+release(v0.4.3): D76 — README install-path overhaul + OLP_BIND env + AI-install prompt + ADR 0011 amendment (#48)
10 README install gaps catalogued and fixed in one D-day after v0.4.2's PI231 E2E session exposed that the v0.4.0-v0.4.2 README Quick Start was fictional (npm package isn't published; olp setup/start commands don't exist). F5 (OLP_BIND env) ships in the same patch so the documented LAN onboarding flow actually works. AI-driven install prompt added per Phase 4 charter brainstorm Top-5 inheritance candidate #2 (D64-D67 built the doctor framework; D76 closes the README half). ## G1-G7: README Quick Start now real Rewrote § "Manual install" from placeholder text to the empirically-verified sequence: - Prerequisites (Node >= 18 + provider CLI install matrix) - git clone + npm test verify - olp-keys keygen --owner FIRST (allow_anonymous=false default needs a key) - Per-provider OAuth (claude setup-token / codex login --device-auth / MISTRAL_API_KEY) - ~/.olp/config.json with the minimum that actually serves traffic - npm start - Smoke-test via curl + olp doctor - IDE pointing ## G8 / F5: OLP_BIND env shipped server.mjs: - const BIND = process.env.OLP_BIND ?? '127.0.0.1' (safe default unchanged) - server.listen(PORT, BIND, ...) replaces hard-coded '127.0.0.1' - New startup warn anonymous_key_advertised_with_lan_bind fires when OLP_BIND is non-loopback AND auth.advertise_anonymous_key: true (operator visibility into trust-context overlap) Pre-D76 the server only accepted loopback connections, so the documented olp-connect <ip> family-onboarding flow was unreachable from LAN without SSH tunneling. F5 makes ADR 0011 operational instead of aspirational. ## G10: AI-driven install prompt README § "Install with your AI (the fast path)" — verbatim prompt the operator pastes into Claude Code / Cursor / Copilot / Aider. The AI follows README + uses olp doctor --json next_action.ai_executable[] for self-repair, stopping only when human_required[] is non-empty (provider OAuth dances). Closes the Phase 4 brainstorm #2 inheritance candidate. ## Opening compressed § "Why OLP" (3 paragraphs of Anthropic 2026-06-15 billing history) removed from the top. The OCP-trigger context moved to § "Migration from OCP" at the bottom, condensed into a single paragraph. New users land on value-prop + § "What you get" + install paths without needing to digest 2026-05-14 billing history first. OCP users get a one-line pointer at the top. ## Configuration + Env Variables sections updated - § Configuration: placeholder replaced with full ~/.olp/config.json schema documentation, every field cross-referenced to its ADR - § Environment Variables: added OLP_BIND, OLP_API_KEY, OLP_OWNER_TOKEN, OLP_PROXY_URL rows that were used in the manual-install flow but previously undocumented ## ADR 0011 § Deployment configurations amendment Codifies the three deployment trust contexts: - 127.0.0.1 (loopback only) — safe with any auth posture - RFC1918 / tailnet / specific LAN IP — anonymous_key OK (the documented trusted-LAN zero-config family onboarding flow) - Public IP — incompatible with advertise_anonymous_key: true Documents the new anonymous_key_advertised_with_lan_bind startup warn event. Closes ADR 0011's pre-D76 dangling reference to a non-existent BIND_ADDRESS concept. ## Test count 714 (v0.4.2) -> 717 (v0.4.3). +3 D76 regression tests in Suite 36 (36s/36t/36u) pinning OLP_BIND wiring + safety warn + ADR amendment. ## Process learning Every D-day reviewer rubric should add "open README §-Quick-Start and verify the commands literally exist + work in the current repo" — would have caught G1-G7 at v0.4.0 close. Combined with D74 (review-against-spec) + D75 (review-without-deployment), D76 (review-without-following-README) codifies the third tier of review discipline. ## Out of D76 scope (deferred) - F6 (doctor client-side vs server-side check separation) — needs design ADR for --remote mode. Phase 5. - D75 reviewer P2-1 (ADR 0004 per-hop schema amendment) + P2-2 (defensive typeof hopModel === 'string') — both genuine follow-ups, neither blocking. - scripts/migrate-from-ocp.mjs — Phase 7. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
53afea47ca |
feat+test+docs: D71+D72+D73 — olp-plugin/ (OpenClaw /olp Telegram+Discord) + docs/integrations/*.md + README cross-refs (#44)
Final Phase 4 substantive D-day group. 3 D-days bundled per Iron Rule 11 IDR (plugin consumes existing endpoints; integration docs reference plugin + olp CLI + olp-connect together; README index links all). After this PR merges, Phase 4 has shipped all 5 D-day groups (D60 charter + port / D61-D63 SSE heartbeat+ring+/status / D64-D67 olp CLI+doctor / D68-D70 olp-connect+anonymous-key+ADR0011 / D71-D73 plugin+docs). The v0.4.0 close PR is maintainer-triggered per CLAUDE.md release_kit overlay. ## D71 — olp-plugin/ (OpenClaw gateway plugin) Port OCP ocp-plugin/index.js (311 lines) → OLP olp-plugin/index.js (482 lines) as the /olp Telegram+Discord slash command, but MINUS mutations (no /olp keys keygen, no /olp keys revoke, no /olp restart, no /olp logs — all of these require SSH out of chat for security). Plugin shape: - olp-plugin/index.js — registers /olp command via OpenClaw api.registerCommand - olp-plugin/openclaw.plugin.json — manifest, apiKey REQUIRED, proxyUrl default http://127.0.0.1:4567 (matches D60) - olp-plugin/package.json — minimal: name/version/type:module + OpenClaw discovery block - olp-plugin/README.md — install + configure + use docs; documents the "no mutations from chat" security stance and the dedicated-bot-key pattern (don't share maintainer's personal key with the bot) Subcommand parity with olp CLI (D64-D67): - /olp status → GET /v0/management/status (owner-only) - /olp health → GET /health (public-ok) - /olp usage → GET /v0/management/dashboard-data (owner-only) - /olp models → GET /v1/models (public-ok) - /olp cache → GET /cache/stats (owner-only) - /olp providers → local cross-ref (public-ok) - /olp chain show [<model>] → local (public-ok, advisory if no FS access — defer to ssh + olp chain show) - /olp doctor → informational (HTTP doctor endpoint deferred; advisory to ssh + olp doctor for live use) - /olp help → usage text Port resolution: OLP_PROXY_URL env → OLP_PORT env → plugin config proxyUrl → http://127.0.0.1:4567. Output: Telegram/Discord monospace code block with status icons (🟢🟡🔴). Long responses truncated for the 4096-char message limit. No npm deps. OpenClaw provides Telegram/Discord transport; plugin uses fetch + node builtins only. ## D72 — docs/integrations/*.md (6 IDE pages + index) Per the Phase 4 brainstorm prior-art survey + ADR 0010 § Out-of-scope posture for Claude Code: - continue.md ✅ — config.yaml (NOT config.json); apiBase; requestOptions.headers - cline.md ✅ — "OpenAI Compatible" provider; Cline #7128 base-URL UI bug warning - cursor.md ⚠️ — known base-URL fragility; only enable models OLP serves - aider.md ✅ — OPENAI_API_BASE env + openai/ prefix; .env support - claude-code.md ❌ — explicitly NOT supported per ADR 0010 § /v1/messages defer rationale; recommended alternative: Cline + OLP - openclaw.md ✅ — install olp-plugin via CLI or symlink; configure apiKey; restart gateway Each ~60-120 lines: status / quick setup / known issues / OLP-specific notes / test-it command. docs/integrations/README.md is the index. ## D73 — README cross-references - New § "IDE Setup" links to docs/integrations/README.md - New § "Telegram / Discord Usage" — install + configure + restart + use - Quick Start mentions olp-connect <ip> as family-onboarding command - package.json `files` field extended to include olp-plugin/ so the published tarball ships the plugin ## Test count 672 → 696 (+24 D71-D73 tests in Suite 35: helpers / formatters / dispatch / error paths). All 696 pass locally. ## Scope discipline - server.mjs UNTOUCHED (plugin consumes EXISTING endpoints) - No new npm deps (no Telegram or Discord SDK — OpenClaw provides transport) - No /v1/messages (out of Phase 4 per ADR 0010) - No CHANGELOG / package.json version bump (Phase 4 close handles versioning; only package.json `files` extended for olp-plugin/ publication) ## Implementor flagged for reviewer 1. /olp doctor returns SSH advisory (no HTTP doctor endpoint yet). When future phase exposes /v0/management/doctor, swap advisory branch for real fetchJSON + fmtDoctor (already implemented + tested). 2. /olp providers + chain show have no FS access (plugin runs in OpenClaw gateway process); registry read via lazy-imported models-registry.json from repo root. For live enabled-state visibility users still need /olp status (owner-tier) or ssh + olp providers / olp chain show. 3. No live-server wire test in Suite 35 — existing Suites 31/32 already cover the integration path against the same endpoints; mock-fetch in Suite 35 is sufficient signal for the plugin layer. ## Authority - ADR 0010 § Phase 4 D-day plan D71-D73 line - OCP ocp-plugin/index.js (port reference) - ADR 0010 § Out-of-Phase-4-scope (claude-code.md ❌ rationale) - 2026-05-26 brainstorm (Top OCP inheritance candidates + prior-art survey IDE-specific quirks for cline/cursor/continue docs) Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0bdecd1235 |
feat+test+docs: D68-D70 — bin/olp-connect + /health.anonymousKey + ADR 0011 (#43)
* feat+test+docs: D68+D69+D70 — bin/olp-connect + /health.anonymousKey + ADR 0011
Third substantive Phase 4 implementation. 3 D-days bundled per Iron Rule
11 IDR — olp-connect consumes /health.anonymousKey for zero-config
client setup, both governed by ADR 0011's trusted-LAN-only invariant.
## D68 — bin/olp-connect (zero-config client setup)
Ports OCP ocp-connect (721 lines) → OLP olp-connect (564 lines, pure bash).
Bash over Node (per ADR 0010 § Notes) because client machines may lack
recent Node; bash + curl + python3 = max portability.
CLI: `olp-connect <host-ip> [--port PORT] [--key API_KEY] [--no-system-env]
[--dry-run] [--help] [--version]`
Workflow:
1. Connectivity probe (curl /health, 5s timeout, distinguishes TCP
unreachable from auth-required)
2. Auth resolution: --key flag → /health.anonymousKey (D69) → interactive
prompt fallback
3. Smoke test (GET /v1/models with bearer)
4. IDE detection + per-IDE config:
- Claude Code: detect + warn (NOT supported as OLP client per ADR 0010)
- Cline: detect + print manual VSCode-settings snippet
- Continue.dev: detect (extension OR ~/.continue/config.yaml) + write
idempotent models: entry
- Cursor: detect + print snippet + WARNING (per prior-art known
base-URL fragility)
- Aider: detect + write OPENAI_API_BASE + OPENAI_API_KEY to rc files
- OpenClaw: detect + print "install /olp plugin (D71-D73 deliverable)"
5. System-level env: macOS launchctl setenv / Linux ~/.config/environment.d
(so VSCode/Cursor started via Dock inherit)
6. Summary + test command
Idempotent (bracketed `# OLP LAN (added by olp-connect)` ... `# /OLP LAN`
blocks in rc files). --dry-run exercises every state-change site without
modifying anything. Exit 0/1/2 conventions.
Installed via package.json bin so `npx olp-connect` works.
## D69 — /health.anonymousKey + auth.advertise_anonymous_key
server.mjs handleHealth emits OPTIONAL `anonymousKey: "olp_..."` field
when ALL THREE prerequisites hold:
1. config.json auth.advertise_anonymous_key === true
2. config.json auth.allow_anonymous === true (per ADR 0007 § 7)
3. At least one non-revoked key has plaintext_advertise field set
Default-off: field is ABSENT (not null) — preserves v0.3.x /health shape;
existing tests don't regress.
bin/olp-keys.mjs new flags: `keygen --anonymous --advertise` writes the
plaintext into the manifest's optional `plaintext_advertise` field AND
prints a WARNING about disk-storage + /health exposure + ADR 0011
pointer. Owner-tier --advertise rejected at BOTH CLI + lib layers.
Implementation note: reused existing guest tier (no new owner_tier:
'anonymous'); plaintext_advertise is a forward-compat optional manifest
field per ADR 0007 § 4 unknown-fields-allowed convention. Cleaner than
introducing a new tier.
anonymousKey appears in BOTH trimmed AND full /health payloads — the
trimmed payload's purpose is to be readable by anonymous clients so they
can self-bootstrap. Tested.
Startup warns on prereq failure (anonymous_key_advertised_but_denied /
anonymous_key_advertised_but_no_anonymous_key_exists) so the relaxed-
posture failure mode is observable. Graceful-degrade: server still
boots; handleHealth re-checks at request time and silently omits the
field when any prereq fails (defense-in-depth).
## D70 — ADR 0011 (anonymous-key deployment-context limits)
New ADR codifying the trusted-LAN-only invariant.
Trade-off documented: anonymous key advertised via /health = anyone who
can reach the server can read /health and use the key. Acceptable ONLY
when "anyone who can reach the server" ≈ "trusted family on the LAN".
Public-internet deployment = instant compromise.
Soft enforcement: server logs startup warn if BIND_ADDRESS resolves to
a public IP AND advertise_anonymous_key: true. No hard allowlist (TLS-
fronted private networks indistinguishable from public from server's
perspective).
Re-evaluation trigger: any time OLP gains "expose to public internet"
deployment mode (e.g., Cloudflare Tunnel guidance in README), revisit.
References ADR 0007 § 7 (identity classes), ADR 0010 § Phase 4 charter
D68-D70 line, OCP server.mjs:148/1454/1488/1555 (PROXY_ANONYMOUS_KEY
reference).
## Test count
658 → 672 (+14 D68-D70 tests across Suite 34: 5 keys.mjs unit + 6 /health
HTTP integration + 3 CLI integration).
## Scope discipline
NO /v1/messages entry surface (out of Phase 4 per ADR 0010).
NO olp-plugin/ Telegram plugin (D71-D73).
NO docs/integrations/*.md files (D71-D73).
NO CHANGELOG / package.json version bump (Phase 4 close handles versioning;
only package.json bin entry for olp-connect added).
NO new npm deps.
## Authority
- ADR 0010 § Phase 4 D-day plan D68-D70 line
- ADR 0011 (this commit — new ADR)
- ADR 0007 § 4 (manifest forward-compat unknown fields) + § 7 (identity
classes) + § 9 (keygen flow) — extended by D69 plaintext_advertise
- OCP ocp-connect /Users/taodeng/ocp/ocp-connect (port reference)
- OCP server.mjs:148, 1454, 1488, 1555 (PROXY_ANONYMOUS_KEY reference)
- 2026-05-26 brainstorm (Top 5 OCP inheritance candidates, item 3:
/health.anonymousKey + olp-connect zero-config UX)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: D68-D70 reviewer P1 + P2 fold-in — README impact note + listKeys redaction + schema_version note
Reviewer APPROVE WITH MINOR — 0 P0, 1 P1, 2 P2; all three folded in.
P1 — README impact note for new Phase 4 user-visible surfaces.
Per CLAUDE.md release_kit.new_feature_doc_expectations:
- new env / config knob → README § Environment Variables
- new endpoint or response field → README § API Endpoints
- new CLI surface → dedicated §
README now documents:
- /health.anonymousKey optional field (in API Endpoints table) with cross-
ref to ADR 0011 + the three-prereq gate
- streaming.heartbeat_interval_ms config (D61) + auth.advertise_anonymous_key
config (D69) under new "config.json keys introduced at Phase 4" subsection
- Operator CLI surfaces summary: olp / olp-connect / olp-keys keygen
--anonymous --advertise, with cross-refs to ADR 0010 + 0002 Amendment 7
P2-1 — lib/keys.mjs listKeys() now strips plaintext_advertise alongside
token_hash. Callers wanting the advertised plaintext for the /health
publication path MUST go through findAdvertisedKey() — the only sanctioned
read site. Defends against a future caller of listKeys() leaking the
plaintext into logs / HTTP responses / dashboards. Tests still pass
(no in-repo caller of listKeys depends on plaintext_advertise being
present).
P2-2 — ADR 0011 now documents the schema_version-stays-at-1 decision
explicitly. Additive optional fields don't require bump per ADR 0007 § 4,
but a future archaeologist asking "why didn't D69 bump schema_version?"
now has a one-line answer. Same paragraph documents the listKeys()
redaction policy in plain text alongside the manifest-field contract.
672/672 tests still pass.
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>
|
||
|
|
e69e908dae |
feat+test+docs: D64-D67 — olp Node CLI + doctor framework + per-provider doctor checks + ADR 0002 Amendment 7 (#42)
* feat+test+docs: D64+D65+D66+D67 — olp Node CLI + olp doctor framework + per-provider doctor checks + ADR 0002 Amendment 7 Second substantive Phase 4 implementation. 4 D-days bundled per Iron Rule 11 IDR — CLI dispatches to doctor; doctor calls into provider plugins via the new contract method; ADR amendment authorizes the contract change. Single PR is the minimum reviewable unit for "does plugin amendment + plugin impl + doctor consumer line up?" ## D64 — bin/olp.mjs Node CLI scaffold Operator surface for OLP. Node not bash (per ADR 0010 § Notes — bash with python3 JSON parsing is a known fragile point; OLP standardizes on Node). Subcommands: - status / health / usage / models / cache — HTTP calls to existing endpoints - providers — local: cross-references models-registry.json + config.json - chain show [<model>] — local: prints routing.chains from ~/.olp/config.json - logs [N] [--level X] — reads ~/.olp/logs/audit.ndjson via audit-query - restart — launchctl (macOS) / systemctl --user (Linux), best-effort - keys ... — delegates to bin/olp-keys.mjs runCli (no logic duplicated) - doctor [--check <id|category>] [--json] — D65 framework - help / --help / -h Token / URL resolution: - OLP_PROXY_URL env → OLP_PORT env → http://127.0.0.1:4567 (D60 default) - OLP_API_KEY env → OLP_OWNER_TOKEN env (filesystem manifest tokens are one-way SHA-256 per ADR 0007 § 5 — not recoverable; CLI surfaces helpful 401 message pointing at olp-keys keygen) Output: - Default: human-readable ANSI-colored text (no chalk dep, auto-suppressed under --json) - --json: raw JSON for scripting - Exit codes: 0=ok / 1=usage / 2=network|HTTP / 3=auth No npm deps. Built-ins only. Installed via package.json bin entry so `npx olp <subcommand>` works. ## D65 — lib/doctor.mjs framework Ports OCP scripts/doctor.mjs (the bedrock of AI-driven self-repair per the OCP audit's #2 inheritance candidate). Machine-readable next_action so a Claude Code / Cursor / etc. agent can self-repair OLP. Check shape: { id, category, async run(): { status: 'ok'|'fail'|'warn', message, evidence? } } Built-in checks: server.running, server.version, config.exists, config.providers_enabled, config.chains_configured, auth.owner_key_exists, system.node_version. Per-provider checks collected dynamically via provider.doctorChecks() per D67. --json output: { checks: [...], kind: noop|update|fix_oauth|fix_config|fresh_install| fix_server|fix_provider, next_action: { ai_executable: [], human_required: [], verify: 'olp doctor' }, summary } --check <id-or-category> for tight repair-loop fast paths. ## D66 — Per-provider doctorChecks() implementations Each shipped plugin contributes its own checks (lives in plugin file so the provider's maintainer updates it naturally): - anthropic.mjs: cli_available (claude --version) + oauth_token_present (~/.claude/.credentials.json OR ANTHROPIC_OAUTH_TOKEN env) - codex.mjs: cli_available (codex --version) + auth_present (~/.codex/config.json) - mistral.mjs: cli_available (vibe --version) + api_key_present (MISTRAL_API_KEY env OR ~/.vibe/.env) Each fail returns evidence.fix_commands (for ai_executable[]) or evidence.human_required (e.g., 'run: claude auth login'). ## D67 — ADR 0002 Amendment 7 New amendment adds OPTIONAL provider.doctorChecks(): DoctorCheck[] to the Provider contract. Backwards compatible — plugins without doctorChecks() contribute no provider checks (default behavior). Validator extended in lib/providers/base.mjs validateProvider. ## Test count 636 → 658 (+22 tests across Suites 32, 33). - Suite 32 — bin/olp.mjs CLI scaffold (10 tests): parseArgv, USAGE, unknown-subcommand, providers local + --json, chain show, status via ephemeral server with owner token, ECONNREFUSED → exit 2, resolveBearerToken precedence - Suite 33 — lib/doctor.mjs framework (12 tests): all kind branches (noop / fresh_install / fix_server / fix_oauth / fix_provider), collectProviderChecks reads doctorChecks(), throwing plugin captured, --check filter, built-in checks against temp HOME, anthropic plugin probe set, resolveProxyUrl precedence, deriveKind/deriveNextAction units ## Scope discipline server.mjs UNTOUCHED. All HTTP subcommands consume EXISTING endpoints. No new endpoints. No /health.anonymousKey. No olp-connect. No Telegram plugin. No IDE docs bundle. No CHANGELOG / package.json version bump (Phase 4 close handles versioning; only package.json bin entries updated). ## Known limitations (flagged for reviewer) - olp restart not unit-tested (would require mocking child_process.spawn in invasive way; manual smoke-test only at this D-day) - olp logs --level filtering matches optional level field if present in audit-event objects; appendAuditEvent already populates it where meaningful — no schema change needed in this bundle - olp usage panel shape inferred from lib/audit-query.mjs exports; if /v0/management/dashboard-data wire shape differs in subtle ways, formatter degrades to '?' but --json always works ## Authority - ADR 0010 § Phase 4 D-day plan D64-D67 line - ADR 0002 Amendment 7 (this commit — new amendment) - OCP ocp bash wrapper /Users/taodeng/ocp/ocp (subcommand reference, translated to Node) - OCP scripts/doctor.mjs /Users/taodeng/ocp/scripts/doctor.mjs (framework reference) - 2026-05-26 brainstorm (Top 5 OCP inheritance candidates, item 2: olp doctor machine-readable next_action) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: D64-D67 reviewer P2 fold-in — shell-quote ai_executable paths + launchctl kickstart caveat Reviewer APPROVE — 0 P0/P1, 2 P2 hardening notes folded in. P2-1 — Shell-quote interpolated paths in fix_commands. lib/doctor.mjs config.exists fix_commands previously interpolated ${olpHome} / ${configPath} unquoted into the printf template. A malicious OLP_HOME env value containing shell metacharacters could inject commands into the suggested-fix string an AI agent (or human) pastes back into a terminal. Added _shellQuote(s) helper (POSIX single-quote-wrap with escape for embedded single quotes per POSIX shell rules). Risk surface is narrow at family scale (operator local env, single-user proxy), but hardening cost is one helper. P2-2 — Document launchctl kickstart -k env-stale pitfall. cmdRestart header now carries an explicit caveat that `launchctl kickstart -k` does NOT re-read the plist EnvironmentVariables block — launchd uses cached env from the most recent bootstrap. This is a known OCP institutional lesson (PIT INDEX in cc-rules MEMORY.md). The comment documents the bootout/bootstrap dance for env reloads and notes that the Phase 4 installer (post-D73) will expose `olp restart --full` for the safer reload path. 658/658 tests still pass; the _shellQuote change is invisible to existing tests because the test fixtures use safe paths. 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> |
||
|
|
0048481764 |
feat+docs: D60 — Phase 4 charter (ADR 0010) + default OLP_PORT 3456 → 4567 (#40)
* feat+docs: D60 — Phase 4 charter (ADR 0010) + default OLP_PORT 3456 → 4567 Opens Phase 4 (Operator + Client UX) end-to-end. Per release_kit.phase_rolling_mode, the version bump fires at Phase 4 close (v0.4.0), not at this D-day; D60 only ships governance + the port default change. ## ADR 0010 — Phase 4 Charter Phase 4 scope = 5 D-day groups (~13 D-days total): - D60 (this commit): charter + default port - D61-D63: SSE heartbeat + recentErrors[20] ring + /status combined endpoint - D64-D67: olp Node-based CLI scaffold + olp doctor next_action framework - D68-D70: olp-connect zero-config IDE setup + /health.anonymousKey + ADR 0011 - D71-D73: olp-plugin/ OpenClaw gateway plugin + docs/integrations/*.md bundle Charter records the EXPLICIT DECISION to DEFER /v1/messages (Anthropic-shape entry surface) on the rationale: under ADR 0009 P0 failure it provides no billing benefit AND degrades worse on fallback than OpenAI-shape clients (because OpenAI tool schema is the cross-provider lingua franca; Anthropic- specific features cache_control / computer_use / text_editor / thinking blocks have no clean fallback mapping). Re-open trigger: (a) ADR 0009 P0 success AND (b) maintainer-named family CC user. README posture updated: Claude Code listed as NOT SUPPORTED as an OLP client; recommended alternative is "Cline + OLP" (same fallback chain available, better cross-provider compatibility). ## Default port 3456 → 4567 server.mjs:74 default value moves so OLP and OCP (which stays on 3456) can co-host on the same machine without OLP_PORT env override. Existing deployments wanting the pre-D60 default can set OLP_PORT=3456 in launchd plist / shell env. Verified port-change invariants: - All test-features.mjs suites use port: 0 (ephemeral) — 0 test-surface impact - Cache / fallback / provider plugins port-agnostic - Dashboard 30s poll + management endpoints use relative paths - 623/623 tests pass on D60 branch HEAD ## ADR amendments - ADR 0001 § "Decision" port-conflict paragraph: struck + amended (co-host is now possible via 3456 → 4567 + launchd labels dev.olp.proxy vs dev.ocp.proxy) - ADR 0008 § 6.6 default-port reference updated - docs/adr/README.md index gains ADR 0010 row ## Authority - ADR 0010 (this commit) - ADR 0009 (interactive-mode placeholder — /v1/messages defer rationale) - 2026-05-26 brainstorm: OCP comprehensive feature audit (subagent output) + multi-provider proxy / IDE integration prior-art survey (subagent output, both this session) - docs/v1x-roadmap.md (Phase 4 was the named destination) - CLAUDE.md release_kit.phase_rolling_mode (current_phase already Phase 4; this charter formalizes contents) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: D60 reviewer P2-1 — soften ADR 0001 launchd-label assertion to forward-tense Reviewer flagged the ADR 0001 amendment's launchd-label collision claim ("avoided via dev.olp.proxy vs dev.ocp.proxy") as present-tense fact when the OLP plist generator hasn't shipped yet (lands D64-D70 per ADR 0010). Soften to forward-tense with explicit cross-reference. The factual claim ("co-host is possible") still stands because plist label is controlled by the OLP project anyway; this is precision, not correction. P2-2 (README "since v0.4.0" forward-dated branding) explicitly accepted as prior-art-consistent with D44+ Phase 2 mid-window doc conventions — no change. 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> |
||
|
|
679e3b367d |
docs: D59 — streaming SF docs polish + v1.x roadmap #1 + #6 status updates (#38)
Third of three D-days for v1.x roadmap #1 (streaming-path singleflight + TOCTOU close). D57 (PR #36) shipped the cache layer; D58 (PR #37) shipped the server wiring + integration. D59 polishes docs and closes the roadmap entry. ## README.md § Known limitations Inverted the streaming-path-singleflight-not-implemented bullet to a ✅ shipped marker. New text documents: - D4 singleflight now wired end-to-end on streaming path via cacheStore.getOrComputeStreaming(...). - Two concurrent identical streaming requests share one CLI spawn via tee fan-out. - Late joiners receive accumulated replay + the live tail. - Per-client backpressure (PER_CLIENT_QUEUE_CAP=1MB) protects against slow consumers. - Full-disconnect aborts source CLI via AbortController propagation. - New X-OLP-Streaming-Inflight: source | attached header annotates role. - New cache_status: 'streaming_attached' audit value tracks singleflight wins. - Authority: ADR 0005 Amendment 8, v1.x roadmap #1. ## docs/v1x-roadmap.md #1 entry rewritten to closed-state with: - Three D-day breakdown (D57 cache layer + D58 server wiring + D59 docs). - Final test count delta (603 → 623). - Deferred sub-items NOT blocking #1 closure (solo wire-value, streaming_inflight_join from cache layer, isFirst unused API). #6 entry updated to note that the implementation chose NOT to bundle streaming SPAWN_FAILED salvage with #1 (D57 tee writes cache only on clean source completion; SPAWN_FAILED mid-stream rejects + does not persist). #6 now needs its own ADR amendment when triggered. Reading-order paragraph updated to reflect that #1, #2, #4, #7 are closed and only #3, #5, #6 remain — all trigger-gated. ## Issue #16 Closed via PR squash-merge of D57 (#36) + D58 (#37). D59 docs reflect the closure. ## Scope Pure docs. No code change, no test change. 623/623 still pass. ## Authority - ADR 0005 Amendment 8 (the spec D57+D58 implemented). - docs/v1x-roadmap.md (rewritten for #1 closure + #6 unbundling). - GitHub issue #16 (closed at merge of D57+D58). Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
179b4707a7 |
docs: ADR 0009 — Anthropic Interactive-Mode Path Placeholder (blocked on OCP P0) (#33)
Records OLP-side decision for the cross-project work OCP started in
ADR 0007 (interactive-mode execution pool to address post-2026-06-15
Anthropic billing split).
OPTION 3 — wait + port:
- No OLP code change to lib/providers/anthropic.mjs until OCP
ADR 0007 P0 experiment outcome lands (>= 2026-07-15).
- Avoid duplicating the P0 risk by running an independent OLP
experiment against the same Anthropic billing pool.
- If P0 confirms Transport A (stdio NDJSON) or Transport B (PTY)
bills as subscription rather than Agent SDK credit, port the
validated pattern to OLP at that time.
- If both transports fail, shelve this ADR; OLP Anthropic users
fall back to Agent SDK $100 / month credit OR shift to multi-
provider routing (Codex / Mistral) per Phase 1 design.
DECISION TREE recorded in § 3 so a future Phase 4 brief can act
mechanically once OCP P0 lands:
Transport A wins → Option 1 (parallel impl) likely
Transport B wins → Option 1 with PTY adapter (node-pty native dep
triggers engines-bump prior PR)
Both fail → Shelve ADR 0009
Unobservable → Extend wait
IMPLEMENTATION LANES (§ 4) — informational, not selected:
Option 1 — OLP parallel implementation in lib/providers/anthropic.mjs
Option 2 — Chain OCP as backend (OLP → OCP → Claude)
Option 3 — Hybrid (prefer OCP backend, fallback to local pool)
NO PHASE 4 D-DAY scheduled. This is a decision-record placeholder
ONLY. Phase 4 standing-autopilot grant ("Phase 4+ requires new
authorization") still applies. When OCP P0 lands, maintainer issues
explicit Phase 4 "go" naming this ADR to trigger implementation
selection + D-day work.
DOCUMENTATION:
- docs/adr/0009-interactive-mode-path-placeholder.md (new, ~200 lines)
- docs/adr/README.md index: ADR 0009 row added
- Cross-machine cc-rules memory (~/.cc-rules/memory/learnings/
ocp_adr_0007_interactive_mode_pool.md, committed in cc-rules
a16b775) bridges OCP ADR 0007 + OLP ADR 0009 so future Phase 4
planning sessions in either repo pull the shared context.
Test count: 601 → 601 (docs-only; no test or .mjs file touched).
AUTHORITY:
- OCP ADR 0007 (~/ocp/docs/adr/0007-interactive-mode-pool.md,
Draft 2026-05-25) — the triggering external work.
- OLP ADR 0001 (Project Founding) — the 2026-06-15 billing-split
motivation that this ADR reaffirms.
- OLP ADR 0006 (Provider Inclusion) — the anthropic plugin's tier
that this ADR would amend if/when implemented.
- CLAUDE.md release_kit overlay phase_rolling_mode (current_phase:
Phase 4) — this ADR explicitly does NOT consume a Phase 4 D-day.
- Standing autopilot grant — Phase 4+ requires new authorization;
this placeholder is a decision-tree pre-record, not Phase 4
implementation.
ALIGNMENT.md scope check: docs-only commit (new ADR + index update).
No provider plugin / entry surface / IR change.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
68e50da68a |
fix+test+docs: D53 — tried_providers schema semantic fix (D45 P2 deferral closed) (#30)
Sixth Phase 3 D-day. Small focused fix for the D45 fresh-context opus
reviewer P2 finding that was deferred: auditCtx.tried_providers on the
key_no_provider_access 403 path was being stamped with the ORIGINAL
chain (which was filtered out, never dispatched), distorting downstream
audit queries like "which providers did key X actually call".
CHANGES:
- server.mjs handleChatCompletions ~L815: on key_no_provider_access
403, auditCtx.tried_providers = [] (was _originalChainProviders).
The configured-but-blocked chain still appears in the human-
readable error message body — the audit just doesn't claim those
providers were "tried" when the server's filter dispatched zero.
- docs/adr/0007-multi-key-auth.md § 8 amendment: new paragraph
spelling out the tried_providers semantic. "The list of providers
the server actually dispatched a spawn against. A provider that
was configured in the chain but filtered out by providers_enabled
gating is NOT included — the key didn't try the provider, the
gate did. On the 403 path tried_providers is the empty array."
Plus a forward note that audit log rotation moved to Phase 3 /
ADR 0008 § 5.
- test-features.mjs Suite 20h-extra-audit (+1 test — 600 → 601):
creates guest key with providers_enabled: ['mistral']; fires
request for Anthropic-routed model; asserts 403
key_no_provider_access; reads audit row from audit.ndjson;
asserts tried_providers === []. Pins the D53 semantic against
regression.
- CHANGELOG.md: D53 entry under Unreleased.
NOT IN D53:
- E2E + docs polish (D54)
- Phase 3 close → v0.3.0 (D55; maintainer-triggered)
Test count: 600 → 601 (+1). Verified locally via npm test.
AUTHORITY:
- ADR 0007 § 8 amendment (D53, 2026-05-25).
- D45 fresh-context opus reviewer P2 deferral note.
- CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased.
- Standing autopilot grant.
ALIGNMENT.md scope check: small entry-surface change (audit context
field assignment on one error path) + ADR amendment + new test. Per
ALIGNMENT.md Rule 1 the ADR amendment is the authority citation for
the server change. No provider plugin / IR / models-registry change.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
c0b696984f |
docs: D48 — ADR 0008 Phase 3 design draft (Dashboard + audit query layer) (#25)
* docs: D48 — ADR 0008 Phase 3 design draft (Dashboard + audit query layer, design-only)
First Phase 3 D-day. Design-only. Ratifies the storage / query model /
rotation / dashboard / refresh / scope decisions ahead of D49+
implementation D-days. Opens ADR 0007 § 12 deferral for Dashboard +
audit query layer + rotation.
NEW docs/adr/0008-dashboard-and-audit-query.md (~368 lines): 13 sections
+ Consequences + Authority citations. Per maintainer-pinned lanes
A/A/B/A/B from Phase 3 kickoff brief 2026-05-25:
Lane 1 (tech stack): A — static HTML + vanilla JS + fetch; no build
step; matches OLP "no bundler" ethos.
Lane 2 (query model): A — in-memory scan of audit ndjson per request;
O(N) per call; family-scale acceptable; defers SQLite hybrid to
Option 3 trigger per ADR 0007 § 13 (requires engines bump as
separate prior PR).
Lane 3 (rotation): B — daily rotation, audit-YYYY-MM-DD.ndjson on
first append after UTC midnight + optional bin/olp-audit-rotate.mjs
external cron.
Lane 4 (refresh): A — 30s page poll (no SSE infra at v0.3.0;
pause when document.visibilityState is hidden).
Lane 5 (dashboard scope): B — full per spec § 4.6: 4 panels (per-
provider quota / 24h request+cache+fallback / 30d spend trend /
top-N fallback chains).
ADR sections:
§1 Context (what Phase 3 closes; what stays out)
§2 Decision (5 lanes pinned)
§3 Storage layout (~/.olp/logs/ with rotated date files)
§4 lib/audit-query.mjs API (readAuditWindow, aggregateRequests,
topFallbackChains, spendTrendDaily, cacheHitRateWindow)
§5 Audit rotation (first-append-after-UTC-midnight trigger + cron
alternative + concurrent-safety per-process lock)
§6 Dashboard panels (4 panels per spec § 4.6, refresh + localhost-
bound notes)
§7 Server endpoints (/dashboard, /v0/management/dashboard-data, /v0/
management/quota, /cache/stats — owner-only gated)
§8 Auth gating (reuses ADR 0007 § 7 owner_only_endpoints config)
§9 Failure modes + degradation (per-panel error states)
§10 Acceptance criteria (15 testable items for D49-D54)
§11 Forward path (Phase 4+ — SQLite migration, SSE push, key-mgmt UI)
§12 Out of scope (explicitly NOT in Phase 3)
§13 Phase 3 sprint shape (D48-D55)
CHANGED:
- docs/adr/README.md: added ADR 0008 row with one-paragraph summary.
- CHANGELOG.md Unreleased: D48 entry per release_kit overlay
phase_rolling_mode discipline. Includes Phase 3 sprint shape table.
NOT IN D48 scope:
- lib/audit-query.mjs (D49)
- server.mjs endpoints (D50)
- dashboard.html (D51)
- lib/audit.mjs rotation extension + bin/olp-audit-rotate.mjs (D52)
- tried_providers schema fix (D53; D45 P2 deferral)
- Phase 3 close (D55; v0.3.0; maintainer-triggered)
Test count: 544 / 544 pass (design-only, no test change). Verified
locally via npm test before commit.
AUTHORITY:
- ADR 0007 § 12 (opens Phase 3 Dashboard + audit query deferral).
- ADR 0007 § 13 (rejects SQLite at Phase 3 per Node baseline +
documents forward-path trigger).
- OLP v0.1 spec § 4.6 + § 4.7 (Dashboard + observability endpoints
planning authority).
- OCP dashboard.html (prior-art reference for multi-panel HTML
structure).
- ADR 0002 (Provider.quotaStatus contract — Panel 1 data source).
- ADR 0005 § Cache stats (/cache/stats endpoint pre-existing design).
- CC 开发铁律 v1.6 § 10 — fresh-context opus reviewer required for
design ADR.
- Phase 3 kickoff via maintainer "go" 2026-05-25 + standing-autopilot
grant (~/.cc-rules/memory/auto/standing_autopilot_phase_2.md in
cc-rules bf0ed9a — Phase 3+ requires new authorization; the "go"
supplied that).
ALIGNMENT.md scope check: this PR introduces a new ADR with full
authority citations per Rule 1 (Cite First). No provider plugin /
entry surface / IR change in this commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: D48 fold-in — opus reviewer findings (1 P2 + 2 P3, all ADR-text polish)
Fresh-context opus reviewer (PR #25) returned APPROVE_WITH_MINOR with 3
findings, all ADR-text polish. No design semantic change beyond the
clarifications.
P2 — § 8 + § 10 #9 gating-mode wording
Original § 8 implied a new "block non-owner identities" behaviour
without naming it; § 10 #9 tested only the universal
allow_anonymous: false 401 case. Fix:
- § 8 now formalizes two gating modes — owner_only_trim (Phase 2
/health pattern) vs owner_only_block (new Phase 3 management-
endpoints pattern) — and explains the management endpoints are
owner_only_block because the entire payload is sensitive.
- § 10 #9 now covers both 401 paths: (a) allow_anonymous: true
+ no header → anonymous identity → STILL 401 because management
endpoints are owner_only_block; (b) allow_anonymous: false + no
header → 401 at the authenticate middleware itself.
P3 #1 — /cache/stats citation accuracy
Original § 7.4 + Authority block cited "ADR 0005 § Cache stats"
which is not a real section. Corrected: planning authority is OLP
v0.1 spec § 4.6; ADR 0005 references the endpoint in
Consequences/Mitigations (~line 279) for the per-(provider, model)
cache-hit-rate breakdown surface.
P3 #2 — cacheStore.stats() shape gap
§ 7.4 now explicitly acknowledges the current shape
({ hits, misses, size, inflightCount } global aggregate) lacks the
per-(provider, model) breakdown spec § 4.6 implies; Phase 3 Panel 2
sources per-provider counts from aggregateRequests (audit-side)
instead. If a future panel needs the breakdown, D50 amends the
store shape + an ADR 0005 amendment fires at that time. Phase 3
acceptance criteria do not require the breakdown.
CHANGELOG.md Unreleased D48 entry: fold-in bullet added enumerating
the 3 fixes.
Test count: 544 / 544 pass (design-only, no test change). Verified
locally via npm test.
Authority: PR #25 fresh-context opus reviewer findings; 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>
|
||
|
|
d253c2b98d |
docs: D43-B — ADR 0007 multi-key auth design draft (design-only) (#19)
* docs: D43-B — ADR 0007 multi-key auth design draft (design-only, no code change) Phase 2 mainline design ADR. Ratifies the storage / token / manifest / atomic-write / owner-gating / bootstrap / Node-baseline decisions ahead of D44+ implementation D-days. Pure design doc — no .mjs / no tests / 4 files touched. Test count 468 → 468. - docs/adr/0007-multi-key-auth.md (new, ~400 lines): 13 sections covering Context / Decision (Option 2 filesystem manifest + opaque token) / Storage layout / Manifest schema / Token format (olp_+32B base64url, SHA-256 hash) / Atomic write & audit append (manifest lifecycle-only atomic via tmpfile+fsync+rename; audit per-request append with warn+1-retry, no memory buffer at Phase 2) / Owner-vs- guest-vs-anonymous gating (config.json auth.allow_anonymous default false, no env auto-detection) / Audit ndjson schema (no PII) / Bootstrap & recovery (minimal keygen command surface + OLP_OWNER_TOKEN env override with stable __env_owner__ keyId) / Acceptance criteria (11 test surfaces) / Node baseline (Option 1 SQLite port rejection rationale citing engines >=18 + CI 20/24 vs node:sqlite v22.5.0/RC) / Out of scope (Dashboard, quota enforcement, audit query, file locking deferred to Phase 3+) / Future forward (Option 3 hybrid migration trigger + preconditions). - docs/adr/README.md index: added ADR 0007 row with one-paragraph summary covering storage choice + rejection rationale. - docs/v1x-roadmap.md #2: marked PHASE 2 ACTIVE (no longer deferred); "Design ADR (NOT YET RATIFIED)" → "Design ADR (ratified) → ADR 0007"; trigger updated to "already fired 2026-05-25"; code anchors pinned to exact line numbers (cache/store.mjs:77-79/:287, server .mjs:502/:531/:392/:1072/:1101). - CHANGELOG.md Unreleased: D43-B entry per release_kit overlay phase_rolling_mode discipline. Authority: - Phase 2 kickoff handoff (~/.cc-rules/memory/handoffs/2026-05-25-phase-2-kickoff.md, cc-rules d9da966) - OLP v0.1 spec § 4.5 (planning authority for ~/.olp/ layout) - OCP keys.mjs (prior-art for opaque-key + per-key isolation model) - Node node:sqlite docs (https://nodejs.org/api/sqlite.html — Option 1 rejection per ADR 0007 § 11) - CC 开发铁律 v1.6 § 10 — fresh-context opus reviewer required for design ADR per Iron Rule 10 ALIGNMENT.md scope check: this PR introduces a new ADR; per ALIGNMENT.md Rule 1 (Cite First), the ADR itself contains the authority citations its decisions rest on (v0.1 spec § 4.5, OCP keys.mjs, Node docs URL). No provider plugin / entry surface / IR change in this commit. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs: D43-B fold-in — ADR 0007 reviewer findings (2 P2 + 3 P3, all polish) Fresh-context opus reviewer (PR #19) returned APPROVE_WITH_MINOR with 2 P2 load-bearing-but-non-blocking findings + 3 P3 polish findings. All five are accepted as suggested; design contract clarified without semantic change. - § 6.2 step 1 (P2 #1) — pin audit serialization to fire AFTER status_code is determined and latency_ms is measured. Makes acceptance criterion #2 (anonymous-401 audit event records the 401 + latency) testable in the way the criterion was written. - § 6.3.5 (P2 #2, new subsection) — explicit "Token validation MUST hit the manifest on every authenticated request (no in-process validation cache at Phase 2)" rule. The acceptance criterion #6 (post-revoke 401 within the next request) was previously enforced only by the test; the rule now belongs to the design contract. Forward-path note documents when a Phase 3+ amendment may add a cache. - § 6.1 atomic-write step 5 follow-up (P3 #3) — document the deliberate omission of directory fsync after rename. Single-process family-scale deployment accepts the tiny rename-loss window under abrupt host crash; future POSIX-strict deployments know where to add the step. - § 9.4 (P3 #4) — declare token-collision between OLP_OWNER_TOKEN and a filesystem-stored key's plaintext as undefined behaviour. Operators MUST NOT reuse plaintext across both surfaces. Phase MAY add startup collision-detection later. - § 10 criterion #4 (P3 #5) — rephrased to assert against the config- driven owner_only_endpoints predicate rather than a hardcoded trimmed payload shape. The test stays stable if an operator removes /health from owner_only_endpoints. CHANGELOG D43-B entry: fold-in bullet added to summarize the 5 fixes. Test count: 468 → 468 (npm test verified locally after fold-in). Authority: PR #19 fresh-context opus reviewer findings; CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs: D43-B fold-in #2 — maintainer text-review findings (1 P1 + 1 P2 + 1 P3) Maintainer (codex) did final text review of PR #19 against the Phase 2 ADR-ratification checklist. Returned 3 findings; all accepted as suggested. Two are contract-level (P1 safety + P2 factual); one is trivial (P3 line-count drift). No semantic change beyond what the findings called out. P1 — § 6.3 / § 6.4 / § 10 #7 — revoke-dominates-touch safety contract Original § 6.3 said touchLastUsed used the same atomic-write pattern as 6.1; § 6.4 said concurrent CLI revoke + touchLastUsed left "both states valid" with "observability-grade" failure mode. Codex correctly identified the bug: a stale manifest snapshot held by the touch path could overwrite a fresh revoke and silently clear revoked_at back to null, breaking acceptance criterion #6 (post-revoke 401 within next request) under concurrent CLI revoke + in-flight server request. The ADR was promising security-grade behavior on a path that was actually last-write-wins. Fix: - § 6.3 rewritten with explicit read-modify-write discipline: touch MUST re-read latest manifest from disk inside the per-key write-lock, NO-OP if revoked_at is non-null, otherwise merge last_used_at preserving all other fields including revoked_at. - § 6.4 reframed from "both states valid" to "revoke dominates touch" safety frame, citing § 6.3 as the load-bearing discipline. The CLI revoke writer always wins the dimension that matters; touch may lose its last_used_at update if it raced. - § 10 criterion #7 expanded to test all three orderings (revoke -> touch, touch -> revoke, interleaved) with the explicit MUST: revoked_at is non-null and equals the revoke writer's timestamp after any interleaving; FAIL if any path produces revoked_at: null. - Forward-path § 6.4 file-locking note updated to clarify §6.3 already holds the contract single-process; flock adds defense-in- depth for rare multi-writer TOCTOU. P2 — § 11 forward path step (1) — Node baseline version history corrected Original wording "Node v22.5.0+ for unflagged but RC; Node TBD for stable" was wrong. v22.5.0 added with --experimental-sqlite flag; v22.12 still required the flag; the module moved past flag-gating in v22.13.0 (LTS) / v23.4.0 (current); entered Release Candidate at v25.7.0 per current docs. Fix: § 11 forward path step (1) rewritten with accurate versions + two Node release-history URLs cited (https://nodejs.org/download/ release/v22.12.0/docs/api/sqlite.html and https://nodejs.org/api/ sqlite.html). Minimum non-flag-gated baseline is now stated as >=22.13.0 (LTS) / >=23.4.0 (current); stable baseline TBD pending Node v25.x+. The rejection-evidence paragraph earlier in § 11 ("v22.12 still required --experimental-sqlite ... current docs mark RC") was already correct and is untouched. P3 — CHANGELOG D43-B line-count corrected Entry said ADR was "~270 lines"; actual file is 420 lines after both fold-ins. Changed to "~420 lines after fold-ins". Phase 2 fold-in #2 bullet enumerates the 3 fixes in this commit; fold-in #1 bullet retained for the opus reviewer round. Test count: 468 / 468 (npm test verified locally after fold-in; design-only doc changes, no test file touched). Authority: PR #19 maintainer text review findings 2026-05-25; CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased; Node SQLite docs URLs cited in ADR § 11 forward path. 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> |
||
|
|
8fd8f86942 |
release(phase-1-cleanup): v0.1.1 — pre-Phase-2 batch (D35-D42)
Phase 1 cleanup release per CLAUDE.md release_kit.phase_rolling_mode
policy. Closes 16 of 17 pre-Phase-2 GitHub issues; #16 stays OPEN as
v1.x tracker with design ratified in ADR 0005 Amendment 8.
Bumps package.json from 0.1.0 → 0.1.1. Promotes CHANGELOG "Unreleased"
section to "## v0.1.1 — 2026-05-25" with full D35-D42 entries
(backfilled D35/D36/D37 entries per cold-audit round 7 Finding 1) and
final cleanup batch summary covering all 8 D-day commits that landed
on `main` between 2026-05-24 and 2026-05-25.
Tag v0.1.1 will be pushed after this commit lands and CI is green.
release.yml auto-creates the GitHub Release from the CHANGELOG v0.1.1
section (extracted via awk pattern match on `## v0.1.1` heading).
D37's phase_rolling_mode gate now passes because Unreleased is
sentinel-only after this commit.
**What v0.1.1 delivers (D35-D42):**
- D35 — streaming empty-stream headers (#9) + truncation marker (#10)
+ irVersion strict check (#11) + alignment.yml scripts/** removal
(#12) + X-OLP-Latency-Ms uniform audit (#4)
- D36 — cache_control partial-noop log (#2) + ADR 0002 filename
correction (#5) + mistral A5 flip (#6) + /v1/models alias governance
(#13) + cache_control determinism test (#14) + anthropic v2.1.89
transcript artifact (#15)
- D37 — release.yml phase_rolling_mode CI gate (#17)
- D38 — maxConcurrent runtime enforcement + new CONCURRENCY_LIMIT
hard trigger (#1)
- D39 — CacheStore.delete + cache_evicted_truncated log + sticky-
cache regression test + SPAWN_TIMEOUT asymmetry ADR (#3)
- D40 — X-OLP-Fallback-Detail debug header + per-hop tuple collection
+ 4KB cap + RFC 7230 non-ASCII escape (#7)
- D41 — X-OLP-Provider-Used chain-origin semantics documented (#8)
- D42 — streaming singleflight v1.x design ADR + multi-layer
safeguards (issue #16; STAYS OPEN as v1.x tracker)
**Test growth:** 416 (v0.1.0) → 468 (v0.1.1). +52 tests across the
8 D-day batch. All 468 pass at the release-commit head.
**Cold-audit cycle:** 1 final round (round 7) over D35-D42 + v0.1.0
state. Result: PASS_WITH_MINOR with 4 findings (1×P3 + 3×P4).
- Finding 1 (P3 CHANGELOG missing D35/D36/D37) — FIXED in this commit
via backfill.
- Finding 2 (P4 ADR 0005 Amendment 8 "Amendment 1/3/5" notation
ambiguity) — FIXED in this commit (replaced with explicit
§ Decision body item-1 + Amendment 3 + Amendment 5 wording).
- Finding 3 (P4 ADR 0005 Amendment 8 Context paragraph cites line 811
but actual branch is at lines 817-823) — FIXED in this commit
(Context paragraph now points at lines 817-823 with the TODO anchor
at ~810 as the navigable landmark).
- Finding 4 (P4 ADR 0002 missing Amendment 2 — pre-existing gap)
— FIXED in this commit (added explanatory note under the
Amendments heading explaining the gap is intentional and load-
bearing).
No P1 or P2 findings.
**Phase 1 cleanup release_kit checklist** (per CLAUDE.md):
- [x] All 8 D-day deliverables landed on main (D35-D42)
- [x] CI green on every D-day commit + on this release commit's head
- [x] Cold-audit round 7 PASS_WITH_MINOR (0 P1/P2; 4 findings all
fixed in this commit)
- [x] 16 of 17 pre-Phase-2 GitHub issues closed (#1-#15 and #17);
#16 stays OPEN as v1.x tracker with design ratified
- [x] Issue #16 status comment posted (D42 commit
|
||
|
|
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> |
||
|
|
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> |
||
|
|
04f797f917 |
feat+docs+test: D40 — X-OLP-Fallback-Detail header (issue #7)
ADR 0004 § Decision § Chain advancement step 4 promised a per-hop
failure detail debug header `X-OLP-Fallback-Detail`. From D9 through
D39 the engine logged per-hop events (D28 added correlation fields)
but never surfaced the failure trail on the response. D40 fulfills
the promise via Option A — ungated v0.1 emission. Phase 2 will
re-introduce owner-vs-non-owner gating when `lib/keys.mjs` lands.
**Per-hop tuple collection** (lib/fallback/engine.mjs)
executeWithFallback now collects `fallbackDetail` — an array of per-hop
tuples — and returns it on EVERY return shape (success / client-error /
AUTH_MISSING / non-trigger / chain-exhausted). Soft-trigger skipped
hops are also recorded with `trigger_type: 'soft'` (currently dead-
code-by-config per ADR 0004 Amendment 2; shape forward-compatible).
Tuple shape (reuses D28 log-event field shapes so logs and the header
pivot on the same keys):
```
{
hop: <0-indexed hop number>,
provider: <provider name>,
model: <model name from IR>,
code: <ProviderError code, engine-synthetic SOFT_TRIGGER, or 'UNKNOWN'>,
error_message: <truncated to 200 chars with ellipsis on overflow>,
trigger_type: 'hard' | 'soft' | 'client_error' | 'auth_missing' | 'unclassified'
}
```
**Header serialiser** (server.mjs)
- `FALLBACK_DETAIL_BYTE_CAP = 4096` (UTF-8 bytes).
- `serializeFallbackDetailHeader(fallbackDetail)` exported. Returns
`null` for empty/null/undefined → caller omits the header.
- `jsonStringifyAscii` escapes every non-ASCII code point as `\uXXXX`
to satisfy RFC 7230 §3.2.6 field-vchar (Node's HTTP header validator
rejects multi-byte UTF-8). The D38 CONCURRENCY_LIMIT synthesised
message contains a U+2014 em dash — without this escape, every
CONCURRENCY_LIMIT response crashed at writeHead with
"Invalid character in header content". The regression test pins
this exact string.
- 4KB cap algorithm: builds candidates as `[...slice(0, kept), sentinel]`
and measures the FULL serialised length (including sentinel) before
comparing against the cap, so the result is guaranteed under cap.
Tail tuples dropped one at a time. Sentinel form:
`{ truncated: true, omitted_hops: N }`.
**Header emission** (server.mjs)
- `withFallbackDetailHeader(base, fallbackDetail)` wraps the base
header object; emits the header only when serialiser returns
non-null.
- Emitted on chain-exhausted, non-trigger-error, client-error,
AUTH_MISSING, and success-with-prior-failure paths.
- ABSENT on clean primary success — verified by a dedicated HTTP
integration test.
**Tests** (test-features.mjs): 452 → 468 (+16):
- Engine-level tuple shape: 2-hop/exhausted, 2-hop/success-with-prior-
failure, 1-hop/success (empty array), 1-hop/fail, non-ProviderError-
yields-UNKNOWN, 500-char-message → 200-char-with-ellipsis, client
error → 1 tuple + client_error trigger type
- Serialiser: empty/null → null, small array round-trip, >4KB cap
with `{truncated:true,omitted_hops:N}` sentinel, RFC 7230 newline/CR
escaping, non-ASCII escaping (em dash regression guard for the D38
CONCURRENCY_LIMIT synthesised message)
- HTTP integration: clean-1-hop-success (header absent), 2-hop-
exhausted (2 tuples on the wire), 2-hop-success-with-prior-failure
(1 tuple on the wire)
Pre-commit fold-in (per evidence-first checkpoint #4):
- **Reviewer Suggestion #2**: `jsonStringifyAscii` regex character
class `[U+0080-U+FFFF]` contains an invisible U+0080 boundary marker
that editors render as nothing, making the line easy to misread as
the empty class `[-...]`. Folded in a 5-line comment block above
the function body explaining the literal byte range and citing
RFC 7230 §3.2.6.
Two reviewer suggestions not folded:
- **Suggestion #1**: AUTH_MISSING tuple path lacks a dedicated D40
test. Code is structurally correct (tuple pushed before early-
return); low priority. Future polish.
- **Suggestion #3**: defensive `err.code != null` guard. Extremely
low priority — PROVIDER_ERROR_CODES is a closed enum with no
falsy values.
**ADR amendments** (docs/adr/0004-fallback-engine.md)
- Amendment 5 added at top of amendments stack — full tuple schema,
cap behavior, RFC 7230 hygiene, ungated v0.1 rationale, Phase 2
follow-up.
- § Chain advancement step 4 updated to remove TBD / not-yet-
implemented qualifiers and cross-reference Amendment 5.
- § Observability headers section gains the X-OLP-Fallback-Detail
schema as IMPLEMENTED at v0.1.
**CHANGELOG.md** — D40 sub-entry appended under existing D38/D39
entries in Unreleased section. No package.json bump per
phase_rolling_mode.
Authority:
- ADR 0004 § Decision § Chain advancement step 4 — D40 fulfils
the promise
- ADR 0004 Amendment 5 (this commit) — implementation contract
- ADR 0004 § Observability headers — updated to IMPLEMENTED state
- D18 (5 standard X-OLP-* headers) — D40 builds on this convention
- D28 (per-hop structured log fields) — D40 reuses the field shapes
- GitHub issue #7 — closed by this commit
- CC 开发铁律 v1.6 § 10.x — fresh-context opus reviewer independent
- CLAUDE.md release_kit_overlay phase_rolling_mode — no version bump
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus,
independent of drafter): APPROVE. Critical depth checks:
- Tuple pushed once per hop, BEFORE client-error / AUTH_MISSING
early-return branches — both paths include the failing tuple
- fallbackDetail returned on all 5 return paths (verified line by line)
- Cap algorithm measures FULL serialised length (with sentinel)
before comparing — no overshoot
- RFC 7230 compliance verified end-to-end: imported the function
in Node, confirmed em-dash → —, unescaped form throws
"Invalid character in header content"
- Serialiser handles null / undefined / [] gracefully → header
absent on clean primary success (HTTP test pins this)
- Hygiene: 0 hits for personal markers, home paths, tokens
- 468/468 tests pass in reviewer's independent npm test run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
bdfea6884b |
feat+docs+test: D39 — D16 follow-ups (issue #3, 4 parts)
D16 reviewer (commit `bafa6d1`) left 4 non-blocking suggestions
batched into issue #3 as a tracker. D39 closes all 4.
**Part 1 — CacheStore.delete(keyId, cacheKey) API** (lib/cache/store.mjs)
D16 originally evicted truncated entries via
`cacheStore.set(keyId, hopCacheKey, result, ttlMs=0)` — a TTL=0
tombstone purged lazily on next access. D39 introduces an explicit
delete primitive that removes the entry immediately.
- Synchronous: `delete(keyId, cacheKey) → boolean`. Returns true if
the entry was present and removed, false if absent. Sync (not
async) for the simplest in-memory Map contract — mirrors clear().
Other CacheStore methods are async to leave room for a Phase 2
file-backed adapter; delete being sync was a deliberate choice.
- Namespace cleanup: when the inner Map becomes empty after delete,
the outer Map's per-keyId entry is also removed (memory hygiene;
mirrors the _activeSpawns cleanup pattern from D38).
- Behavior: peek/get/getOrCompute see no trace after delete; the
subsequent getOrCompute triggers a fresh compute.
**Part 2 — `cache_evicted_truncated` observability log** (server.mjs)
After the D16 eviction call in collectAllChunks, emit:
```js
logEvent('info', 'cache_evicted_truncated', {
provider, model, cache_eviction_hit,
});
```
Dashboard sees salvage frequency per (provider, model). The
cache_eviction_hit boolean distinguishes "we evicted an entry" (true)
from "we tried to evict but it was already gone" (false — race with
concurrent eviction or TTL purge), preserving observability accuracy
under concurrency.
**Part 3 — Sticky-cache regression test** (test-features.mjs)
Defense-in-depth around the eviction path. Two consecutive identical
buffered requests; the first triggers SPAWN_FAILED after partial
chunks → Case B salvage returns `{ chunks..., finish_reason: 'length' }`
to the client and evicts via delete(). The second identical request
must trigger a fresh spawn (NOT serve the salvaged response from a
stale cache entry).
Asserts on BOTH invariants for defense-in-depth:
- Mock provider spawn count == 2 across the 2 identical requests
- Second request's X-OLP-Cache header is 'miss'
If eviction silently breaks in a future regression, both assertions
catch it independently.
**Part 4 — SPAWN_TIMEOUT salvage parity: DOCUMENT ASYMMETRY**
(docs/adr/0004-fallback-engine.md)
Maintainer decision: SPAWN_TIMEOUT is NOT salvaged. Document the
asymmetry rather than implementing parity. ADR 0004 Amendment 1 is
extended with a new section "Why SPAWN_TIMEOUT is excluded from
salvage" with 4-point rationale:
1. SPAWN_FAILED indicates the provider crashed mid-stream — there's
nothing more coming; partial > nothing. Next-hop spawn has no
advantage (same input may crash same way).
2. SPAWN_TIMEOUT indicates the provider was slow (deadline exceeded
per `hints.maxSpawnTimeMs`). Fallback advancement to a DIFFERENT
provider is more likely to give a complete response than salvaging
a partial from a slow provider.
3. The "user paid for partial content" framing from D16 captures only
SPAWN_FAILED. For SPAWN_TIMEOUT the user actually paid for "result
within time T" — partial-at-time-T is not what was paid for;
"full result soon after T" via fallback is closer.
4. Code-level inspection confirms the asymmetry: collectAllChunks
catch matches ONLY `code === 'SPAWN_FAILED'` (server.mjs:563).
SPAWN_TIMEOUT propagates via re-throw and hits evaluateHardTriggers.
v1.x re-evaluation trigger: if real usage shows users want partial-
on-timeout for very long deadlines, add a v1.x design ADR.
Stale comment fix: `lib/providers/anthropic.mjs:369` previously said
"SPAWN_TIMEOUT salvage parity is tracked in issue #3". D39 closes
that issue, so the comment is updated to point at ADR 0004 Amendment 1.
**Tests** (test-features.mjs): 447 → 452 (+5):
- 3 unit tests on CacheStore.delete (Suite 9): present-key true, absent-key
false, namespace cleanup at empty
- 1 D16 integration test: cache_evicted_truncated log fires with
correct fields during salvage
- 1 sticky-cache regression: spawn count 2 across 2 identical requests,
X-OLP-Cache miss on second
Pre-commit fold-ins (per evidence-first checkpoint #4):
- **Reviewer Suggestion #1**: cacheStore.delete() return value was
discarded at the call site → log inflated salvage metric under
concurrent-eviction race. Folded: captured `evicted` boolean and
added to log payload as `cache_eviction_hit`.
- **Reviewer Suggestion #2**: anthropic.mjs:369 stale comment
pointing at now-closed issue #3. Folded: rewrote to point at
ADR 0004 Amendment 1 § "Why SPAWN_TIMEOUT is excluded from
salvage".
- **Reviewer Suggestion #3**: ADR 0004 attribution ambiguity —
parenthetical "(per Amendment 3 — SPAWN_TIMEOUT is one of the 4
live hard-trigger codes alongside SPAWN_FAILED, CLI_NOT_FOUND, and
CONCURRENCY_LIMIT from Amendment 4)" could mis-parse as Amendment 3
covering all four. Folded: split to
"(per Amendment 3: SPAWN_FAILED, CLI_NOT_FOUND, SPAWN_TIMEOUT;
per Amendment 4: CONCURRENCY_LIMIT)".
**CHANGELOG**: D39 sub-entry appended under the existing D38 entry
in Unreleased section. No package.json bump (phase_rolling_mode).
Authority:
- ADR 0005 § Cache layer — CacheStore API extension (Part 1)
- ADR 0004 Amendment 1 update — SPAWN_TIMEOUT asymmetry rationale (Part 4)
- GitHub issue #3 — closed by this commit
- D16 commit
|
||
|
|
994568a8fb |
feat+ci: D38 — maxConcurrent runtime enforcement (issue #1)
ADR 0002 Amendment 1 declared `hints.maxConcurrent` declarative-only at
v0.1 — type-validated at startup but with no runtime enforcement.
D38 wires the runtime enforcement via a per-provider in-flight spawn
counter with immediate-advancement on saturation.
Design choice: **immediate-advancement via fallback engine** (queue +
timeout DEFERRED). When a provider is at its maxConcurrent limit, the
spawn call synchronously fails with a new `CONCURRENCY_LIMIT` error
code; the fallback engine treats this as a hard trigger and advances
to the next chain hop. If the entire chain is saturated, the user
sees a chain-exhausted error (existing path).
Rationale for immediate-advancement over queue+timeout:
1. The fallback chain exists precisely for this kind of overflow —
adding a queue layer would duplicate the advancement semantics.
2. Queue + timeout adds new config surface (timeout duration, queue
depth bounds, queue eviction policy) that isn't needed at the
personal/family scale OLP serves.
3. Head-of-line blocking risk: a long-running spawn would stall
queued requests behind it even though other providers in the chain
could serve them immediately.
4. Fail-fast latency aligns with the multi-provider proxy philosophy
("spread risk across providers, not within a provider").
Queue + timeout is deferred to a v1.x design ADR if real usage shows
demand. ADR 0002 Amendment 6 and ADR 0004 Amendment 4 capture the
decision explicitly.
Changes (8 files, +<delta>):
**Code**
1. **lib/providers/base.mjs** — add `CONCURRENCY_LIMIT` to
`PROVIDER_ERROR_CODES`. JSDoc clarifies the code is synthesised by
the orchestration layer, not thrown by provider plugins themselves.
2. **lib/providers/index.mjs** — new semaphore primitives:
- `tryAcquireSpawn(providerName, maxConcurrent)` — atomic
check-then-increment. Returns `true` on success, `false` if at
limit. Atomicity rests on the JS single-threaded invariant
(read + write synchronous, NO `await` between them); module-level
comment block warns future maintainers against breaking this.
- `releaseSpawn(providerName)` — decrement; throws on
under-decrement (defensive bug guard for missing acquire / double
release). Map.delete at zero for clean memory footprint.
- `getActiveSpawnCount(providerName)` — returns current count
(0 for unseen providers). For diagnostics + tests.
- `DEFAULT_MAX_CONCURRENT_SPAWNS = 4` — defense-in-depth fallback
matching the v0.1 plugin defaults (anthropic/codex/mistral all
declare hints.maxConcurrent: 4). Also coerces non-integer / NaN
/ negative inputs to the default.
- `__resetSpawnCounters()` — internal test seam.
3. **lib/fallback/engine.mjs** — `CONCURRENCY_LIMIT: true` added to
`HARD_TRIGGER_CODES`. `classifyTrigger` and `evaluateHardTriggers`
both pick it up via the same lookup. v0.1 live hard-trigger codes
are now 5 (was 4 post-D34): SPAWN_FAILED, CLI_NOT_FOUND,
AUTH_MISSING:false, SPAWN_TIMEOUT, CONCURRENCY_LIMIT.
4. **server.mjs** — gate the spawn call in handleChatCompletions at
BOTH spawn call sites:
- **Buffered path** (executeHopFn → collectAllChunks): acquire
before provider.spawn; on failure synthesise
`ProviderError(CONCURRENCY_LIMIT)` with providerName /
maxConcurrent / activeSpawns diagnostic fields and throw —
fallback engine catches and advances. On success, outer try/
finally wraps the inner D16 truncation-salvage try/catch so
releaseSpawn fires on EVERY exit path (return, D16 salvage
return, re-throw).
- **Streaming path** (single-hop real-SSE branch): acquire BEFORE
the streaming branch entry. If acquire fails, branch is skipped
and request falls through to buffered path (whose own gate
surfaces chain-exhausted for single-hop chains). If acquire
succeeds, existing streaming try/catch gains
`finally { releaseSpawn(streamProvider) }` — slot releases on
stop-chunk completion, generator exhaustion, abort, or any
exception path. `releaseSpawn` fires at END of stream
consumption, not when spawn() returns.
**Tests** (test-features.mjs): 431 → 447 (+16):
Suite 18 — D38 — maxConcurrent runtime enforcement:
- 18a: PROVIDER_ERROR_CODES.CONCURRENCY_LIMIT exists
- 18b: evaluateHardTriggers true for CONCURRENCY_LIMIT
- 18c: AUTH_MISSING regression guard (D38 did NOT flip it to hard)
- 18d.1-18d.4: semaphore unit (increment / saturate-no-increment /
release / map-delete-at-zero)
- 18e: tryAcquireSpawn returns false without side-effect when at limit
- 18f: releaseSpawn throws on under-decrement
- 18g.1-18g.2: DEFAULT_MAX_CONCURRENT_SPAWNS applied for invalid
inputs (undefined / NaN / negative / non-integer)
- 18h: __resetSpawnCounters clears state
- 18i: HTTP integration — 5 concurrent requests to single-hop
chain w/ maxConcurrent=2 → peak in-flight exactly 2, 2 succeed,
3 fail
- 18j: counter releases after buffered request (sequential test)
- 18k: 2-hop chain — saturated primary advances to fallback
- 18l: streaming counter releases at END of stream (not at spawn)
**ADR amendments**
5. **docs/adr/0002-plugin-architecture.md** — Amendment 6 added.
Removes "Declarative hint only at v0.1" caveat from the
`maxConcurrent` description in the Provider contract section.
Adds implementation reference + design-choice rationale (4 points).
Lists all exported symbols.
6. **docs/adr/0004-fallback-engine.md** — Amendment 4 added.
CONCURRENCY_LIMIT added to v0.1 hard-trigger taxonomy. Documents
synthesis-vs-plugin-thrown distinction. Documents first-chunk
safety (acquire before any res.write). v1.x re-evaluation triggers
named.
**CHANGELOG**
7. **CHANGELOG.md** — Unreleased sentinel replaced with proper D38
entry. Per CLAUDE.md release_kit phase_rolling_mode, this lands
under Unreleased; promotion to ## v0.1.1 happens at the v0.1.1
release. The D37 phase_rolling_mode gate now correctly fires if
anyone tags v0.1.x with this content present without promotion —
intentional.
Pre-commit fold-ins (per evidence-first checkpoint #4):
- **Reviewer Suggestion #1 (test 18c name mismatch)**: 18c is named
"classifyTrigger returns hard for CONCURRENCY_LIMIT" but body tests
AUTH_MISSING regression. Renamed header comment to
"AUTH_MISSING regression guard" and clarified that
CONCURRENCY_LIMIT classification is covered by 18b.
- **Reviewer Suggestion #3 (activeSpawns diagnostic field)**: server
.mjs:532 set `concurrencyErr.activeSpawns = maxConcurrent` (the
limit). Technically correct (since acquire just failed, live
count == limit) but confuses future readers. Changed to query
`getActiveSpawnCount(hopProvider)` directly. Added import of the
new symbol to the lib/providers/index.mjs import block.
- **Reviewer Suggestion #5 (ADR overstatement)**: ADR 0002 Amendment 6
said getActiveSpawnCount is "exported for /health, diagnostics,
and tests" but /health integration is not wired at D38. Reworded to
"exported for diagnostics and tests" + "/health integration
deferred — when surfaced there will land at providers.status.<name>
.activeSpawns; not wired at D38." Avoids overstating current state.
Two reviewer suggestions not folded:
- Suggestion #2 (streaming test 18l comment about branch entry
conditions) — low priority; the test passes and the branch is
taken (verified by reviewer). Future polish if confusion arises.
- Suggestion #4 (additional test for streaming-saturation → chain-
exhausted) — code path is straightforward and intentional; 18i
covers the buffered-path version directly. Defer.
Authority:
- ADR 0002 Amendment 1 (declarative-only caveat) — superseded by
Amendment 6
- ADR 0002 Amendment 6 (this commit) — runtime enforcement landed
- ADR 0004 Amendment 4 (this commit) — CONCURRENCY_LIMIT added to
v0.1 hard-trigger taxonomy
- GitHub issue #1 — closed by this commit
- CC 开发铁律 v1.6 § 10.x — independent fresh-context reviewer
- CLAUDE.md release_kit_overlay phase_rolling_mode — no version
bump; Unreleased entry written
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus,
independent of drafter): APPROVE. Critical depth checks:
- Atomicity in tryAcquireSpawn (lines 285-290 read+set with no
intervening await) — confirmed; module-level invariant comment
warns future maintainers
- Release on every exit path: buffered (outer finally wraps inner
try/catch covering D16 salvage return + normal return + re-throw);
streaming (try/finally covers stop-chunk return + loop exhaustion +
catch paths). No double-release path identified.
- Streaming release timing: fires in finally after res.end() at all
exit paths, never before stream consumption completes
- Counter leak: every code path traced — no orphan acquire identified
- 447/447 tests pass in reviewer's independent npm test run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
60570ef074 |
fix+docs: D34 — FINAL batch (F1+F4+F7+F8); audit cadence stops
cold-audit catch from 2026-05-24 (round 6 — FINAL)
This is the closing D-day of a 24-day round-1→round-6 audit cycle.
After this commit + the 9 round-6 follow-up issue filings, no more
audit rounds. Trajectory R1=17 → R2=13 → R3=13 → R4=10 → R5=12 →
R6=14 — the method did not converge; owner chose Option A (focused
batch of most consequential items, then STOP).
Changes (6 files, +138 / -47):
**Code changes**
1. lib/cache/keys.mjs (+14/-?) — F4 P2 cache key array-field
normalization:
- New `normalizeArrayField` helper: `(Array.isArray(v) && v.length === 0) ? null : (v ?? null)`
- Applied to `tools` and `stop` in computeCacheKey
- Now `tools: []` and `tools` omitted produce IDENTICAL cache
keys (and same for `stop: []` vs omitted). ADR 0005 Amendment 2's
own claim that "[] and undefined share a cache entry" was
empirically FALSE pre-D34; round-6 reviewer verified hashes
differ. The fix makes the claim literally true at the
key-composition layer.
2. lib/providers/base.mjs (+16/-?) — F7 P2 dead error code removal:
- `QUOTA_EXHAUSTED` removed from PROVIDER_ERROR_CODES
- `RATE_LIMITED` removed from PROVIDER_ERROR_CODES
- v0.1 live codes: SPAWN_FAILED, CLI_NOT_FOUND, AUTH_MISSING,
SPAWN_TIMEOUT
- Comment block documents removal + cites ADR 0004 Amendment 3
3. lib/fallback/engine.mjs (+29/-?) — F7 P2 sibling:
- HARD_TRIGGER_CODES: QUOTA_EXHAUSTED + RATE_LIMITED removed
- SPAWN_FAILED, CLI_NOT_FOUND, AUTH_MISSING(false), SPAWN_TIMEOUT
remain
- evaluateHardTriggers HTTP-status branches KEPT (option (b)) with
forward-compat comment: "v0.1 plugins never attach statusCode;
branches reserved for v1.x when plugin gains HTTP-status parsing"
4. test-features.mjs (+93) — F4 + F7 test work:
- 4 new F4 regression tests (tools:[] vs undefined, stop:[] vs
undefined, tools:[] vs null, tools:non-empty vs undefined sanity)
- ~14 integration test code-swap edits (QUOTA_EXHAUSTED →
SPAWN_FAILED, RATE_LIMITED → SPAWN_FAILED) preserving original
hard-trigger semantic
- 2 dead unit tests for QUOTA_EXHAUSTED/RATE_LIMITED removed
(tombstone comment retained for audit trail)
**ADR amendments (docs-only, no code change)**
5. docs/adr/0004-fallback-engine.md (+12) — F7 Amendment 3:
- Documents the v0.1 hard-trigger code narrowing
- 4 live codes listed explicitly
- Captures evaluateHardTriggers HTTP-status branch retention rationale
- v1.x re-activation path: plugin gains HTTP-status parsing →
re-add codes → branches activate naturally
6. docs/adr/0005-cache-cross-provider.md (+21) — TWO amendments + 1
prior-amendment update:
- **Amendment 6 (F1 P1)**: Formal v1.x deferral of D4 streaming
singleflight. Buffered path (executeHopFn) uses cacheStore.getOrCompute
and participates in D4 fully. Streaming cache-miss path
(server.mjs:609-741) bypasses singleflight — N concurrent identical
streamers each spawn fresh. v0.1 trade-off accepted for
personal/family scale; v1.x design ADR needed for tee-streaming +
per-key inflight Map. Cross-references CLAUDE.md release_kit.
phase_rolling_mode as the deferral pattern precedent.
- **Amendment 7 (F8 P2)**: Documents the v0.1 conservative cache-key
posture: includes all IR fields including those plugins discard
(anthropic/codex/mistral drop temperature/max_tokens/top_p/stop/
tools/tool_choice at spawn). Consequence: 2 requests with different
temperature produce identical CLI output (CLI ignores) but
different cache keys → spurious miss. Trade-off justified:
spurious miss > spurious hit. v1.x forward path:
per-plugin cacheKeyFields contract extension (ADR 0002 amendment
needed). 3 implementation subtasks enumerated for the v1.x PR.
- **Amendment 2 update (F4)**: heading renamed to "Note on
null-coalescing AND array normalization"; body documents the
new normalizeArrayField helper; quotes the regression test name.
Tests: 414 → 416 (+4 F4 regression, -2 F7 dead, +0 net from F7
integration rewrites).
Pre-commit fold-in: NONE — D34 reviewer APPROVE with all 4 suggestions
non-blocking/cosmetic.
Authority:
- ADR 0005 Amendment 2 invariant restored at code level (F4)
- ADR 0005 Amendment 6 formalizes F1 streaming-singleflight deferral
per the same pattern as D22 ADR 0004 Amendment 2 soft-trigger
deferral
- ADR 0005 Amendment 7 documents F8 conservative posture as v0.1
intentional design (not an accident)
- ADR 0004 Amendment 3 narrows v0.1 trigger taxonomy (F7)
- Round-6 cold audit findings F2 / F3 / F6 / F9 / F10 / F11 / F12 /
F13 / F14 filed as GitHub issues after this commit (NOT in scope)
- CC 开发铁律 v1.6 § 10.x — final round of the audit cadence
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Critical depth checks:
- B5 over-normalization: verified `normalizeArrayField` only applies
to `tools` and `stop`; `response_format: {}` and `tool_choice: ''`
unaffected (Array.isArray guard)
- C10 test cleanup: 21 references reconciled (3 tombstone, 18
rewrites/removals); integration test rewrites preserve hard-trigger
semantics (QUOTA_EXHAUSTED → SPAWN_FAILED is also a hard trigger,
so fallback advancement behavior unchanged)
---
**End of audit cycle.** 24 D-days shipped from D10 (P1 hardening) through
D34 (final batch). 6 cold-audit rounds executed; 78+ findings raised;
~50 closed via implementation; ~28 deferred to GitHub issues / v1.x
ADR amendments. v0.1 tag remains explicit-maintainer-action per
phase_rolling_mode policy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f784fdb947 |
fix+docs: D33 — round-5 cleanup batch (F1/F3/F5/F8/F9/F10/F11/F12)
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>
|
||
|
|
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 |
||
|
|
d6347e33f2 |
docs(governance): D31 — ADR amendment trio (F5 ADR 0003 + F11 ADR 0005 § D2 + F13/F14 ALIGNMENT Speculative-Candidate class)
cold-audit catch from 2026-05-24 (round 3)
Three coordinated governance amendments per the major-decisions already
made (per "继续吧 unless major decision" + my F5/F11/F13+F14 option-(b)
calls earlier in the session). All P3-class, all docs-only. Single
PR per IDR cleanup-batch convention given the unified theme
("spec was aspirational; reality is X; amend spec to match").
Changes (3 docs files, +52 / -2):
1. docs/adr/0003-intermediate-representation.md — Amendment 1 (NEW
Amendments section, first ever for ADR 0003):
- F5 closure: removes the unimplemented `__irRoundTripTest()`
export-name claim from § Mitigations. The mental model
(symmetric irToNative+nativeToIR pair on each plugin) does NOT
fit the actual plugin shape (asymmetric spawn-wrappers:
request→CLI-args+stdin, response-stream→IR-chunks). Zero plugins
export this function.
- Documents the substitute test strategy that IS live at v0.1:
· Suite 3 covers entry-surface IR↔OpenAI round-trip
· Per-plugin spawn-with-mock test blocks cover IR→CLI→IR-chunk
· Lossy-translation edges documented inline in each plugin's
header comment (mistral.mjs DOCS-N markers being the most
thorough; anthropic.mjs + codex.mjs carry equivalent tables)
- Future plugins MUST satisfy both (a) spawn-with-mock test block
and (b) header lossy-field documentation — the enforcement
mechanism replacing the original export-name aspiration.
- Original § Mitigations bullet replaced with a pointer to
Amendment 1 (preserves history).
2. docs/adr/0005-cache-cross-provider.md — Amendment 5 (after D27
Amendment 4):
- F11 closure: acknowledges that the "Anthropic's own prompt cache
is consulted at the provider" half of § D2 is structurally
impossible at v0.1.
- Root cause: Anthropic plugin uses `claude -p --output-format text`
(verified at anthropic.mjs:196), a plain-text wire surface that
has no Messages-API field for cache_control markers. The markers
are also stripped at IR construction (openai-to-ir.mjs:45-89
whitelists fields, drops cache_control) per ADR 0003 IR design
reaffirmed in D27 Amendment 4 (F10).
- Clarifies v0.1 effective behavior: OLP-cache-bypass works
correctly (no double-caching when Anthropic + markers); the
delegate-to-Anthropic-prompt-cache half does not fire.
- Forward path (v1.x): switch Anthropic plugin to --output-format
json (NDJSON wire) or direct Messages API spawn — substantial
parser rewrite; deferred since no user has requested delegation.
- § D2 paragraph stays intact; an inline parenthetical reference
to Amendment 5 is added immediately after the affected sentence.
3. ALIGNMENT.md — new "Rule 4 exception class — Speculative-Candidate
Plugin" sub-section after Rule 5, before § Authorities:
- F13+F14 closure: formalizes the gap between strict ALIGNMENT
Rule 2 (no speculative shape assumptions) + Rule 4 (unalignable
plugins are deleted, not feature-flagged) and the actual practice
where Codex (D6) + Mistral (D8) plugins ship with explicit
UNPINNED assumptions documented in their headers, gated as
Candidate-not-Enabled.
- 5 precise conditions for a Speculative-Candidate plugin:
(1) STATIC_REGISTRY entry present, (2) candidate: true in
models-registry, (3) NOT in any user's providers.enabled config,
(4) header has explicit UNPINNED assumption section with labeled
IDs and planned-pin triggers, (5) defensive multi-shape parsers
tied to UNPINNED markers (greppable for future single-shape
replacement).
- Enablement criteria (Speculative-Candidate → Enabled): 3 explicit
requirements (remove UNPINNED labels from header; replace
multi-shape parsers with single-shape; file pin ADR amendment).
- Currently-in-class table:
· codex.mjs (D6) — A3 (auth token field name), A4 (NDJSON event
schema)
· mistral.mjs (D8) — A4 (JSON output event schema), A5 (model
flag), A6 (exact model IDs), A7 (streaming vs json output
mode), A8 (stdin prompt passing)
- Anthropic explicitly NOT in this class (CLI version pinned at
@anthropic-ai/claude-code v2.1.89 per Provider Authority Pins
table).
- CI enforcement noted as deferred (reviewer obligation today; a
future PR may add grep-based gate).
Pre-commit fold-in (per evidence-first checkpoint #4):
- **D31 reviewer flagged wording precision** ("Provider Inventory
table above" was structurally wrong — the table is BELOW the new
sub-section). Folded in: 1-word fix "above" → "below" on the
Speculative-Candidate condition #2.
Tests: 400/400 unchanged (docs-only).
Authority:
- ADR 0003 § Mitigations (modified) + new Amendment 1 (self-amendment)
- ADR 0005 § D2 (annotated) + new Amendment 5 (self-amendment)
- ALIGNMENT.md § Rules (extended with Rule 4 exception class)
- D27 F10 Amendment 4 of ADR 0005 (cited for cache_control IR-strip
precedent)
- CC 开发铁律 v1.6 § 10.x — Round-3 Cold Audit caught all 3 items
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE_WITH_MINOR. Verified:
- Suite 3 + 3 plugin spawn-with-mock blocks + 3 plugin lossy-field
header tables all present (F5 substitute test strategy claims
match implementation)
- anthropic.mjs:196 hardcodes --output-format text + openai-to-ir.mjs
drops cache_control from messages (F11 wire-limitation technically
accurate)
- All 8 UNPINNED assumption labels (codex A3/A4 + mistral A4/A5/A6/A7/A8)
match plugin header text exactly — no invention
- Anthropic-not-in-class claim verified against Provider Authority Pins
table
Follow-up items (reviewer's non-blocking notes, NOT in this PR):
1. **mistral.mjs A5 internal disconnect** — header lines 145-156 label
A5 as `UNPINNED-D-later-verifies`, but function body at 371-374
already cites DeepWiki enumeration as the pin and marks A5
`CONFIRMED-NOT-APPLICABLE` (no --model flag exists in vibe CLI).
The disconnect predates D31 and is a per-plugin header cleanup
(not a constitutional amendment), so it stays out of D31 scope per
IDR. File as separate follow-up issue — A5 should be moved out of
the Speculative-Candidate table once the header is updated.
2. **ADR 0005 Amendment ordering cosmetic** — strict reverse-chronological
would put Amendment 5 above Amendment 4 (current order is 4, 5, 3,
2 because both 4 and 5 share the 2026-05-24 date and 5 was inserted
after 4). Not blocking; future cleanup if maintainers want strict
numeric-descending.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
c3ba751a8f |
feat: D27 — round-3 P3 batch (F8 IR validator + F10 ADR amend + F15 /v1/models aliases)
cold-audit catch from 2026-05-24 (round 3)
3 round-3 P3 items batched per IDR. Mixed surfaces but all single-severity
and conceptually independent.
Changes (5 files, +324 / -17):
1. lib/ir/types.mjs — F8 validator extension (+28):
- `response_format`: must be object with string `.type` (undefined/omitted
accepted; null, non-object, missing-type all rejected). Forward-compat:
accepts any string for type so future OpenAI additions (json_schema,
etc.) flow through without schema bump.
- `tool_choice`: string form 'auto'/'none'/'required' OR object form
`{type:'function', function:{name:string}}`. All other shapes rejected.
Pre-D27 the validator silently accepted any value; the Anthropic plugin's
`if (irRequest.response_format?.type === 'json_object')` would no-op on
a malformed string payload.
2. docs/adr/0005-cache-cross-provider.md — F10 Amendment 4 (+8):
- Documents the IR-vs-body detection ambiguity in § D2: the ADR text
reads as if detection happens on the IR, but `openai-to-ir.mjs` strips
`cache_control` from messages per ADR 0003's whitelist policy, so
IR-side detection is structurally always empty at v1.0
- Documents the actual v1.0 detection mechanism (server.mjs side-channels
into the raw body)
- Documents the cache key `cache_control` slot's always-null status as
forward-compat (when a future ADR 0003 amendment adds cache_control
to IR, the slot will start carrying meaningful data without schema bump)
- Explicit "no code change" — F10 is docs-only
3. lib/providers/index.mjs — F15 alias map export (+11):
- `getAliasMap()` returns `new Map(_aliasMap)` — defensive copy preventing
caller mutation of the module-private alias map
- JSDoc documents use case + defensive-copy intent
4. server.mjs — F15 /v1/models alias surfacing (+25/-7):
- `handleModels` now emits TWO loops: canonical entries first (preserves
existing client expectations), then alias entries via `getAliasMap()`
- Each alias entry has the same 4 OpenAI-spec fields (id/object/created/
owned_by) — no invented fields per ALIGNMENT Rule 2(b)
- Disabled-provider alias non-leak: `loadedProviders.has(providerName)`
gate skips aliases whose target provider is not currently enabled
- createdTs reused (same per-request timestamp across all entries)
- JSDoc updated to document new ordering + F15 origin
5. test-features.mjs — +269 / +18 new tests:
- F8: 13 tests covering response_format (object/string/non-object/missing-
type) + tool_choice (string variants/object variants/wrong type/no name)
- F15: 5 tests covering canonical-first ordering, alias presence/count,
disabled-provider non-leak (with anthropic-only enabled, mistral and
openai aliases must NOT appear), Rule 2(b) shape conformance
Tests: 358 → 376 (+18). All pass on Node 20.
Reviewer notes (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE. Critical checks verified:
- F8: all 9+ tool_choice rejection axes traced through code (including
partial-function-object edge cases). The code handles them correctly even
where tests don't exercise (non-blocking gap).
- F10: amendment substantively correct. One wording-precision note: the
amendment says "body only" but the actual code is an OR-disjunction
(hasCacheControl(ir) || extractCacheControlMarkers(body.messages)).
Functionally identical at v1.0 because IR strips markers, so first
disjunct is always false. Forward-compat by construction.
- F15: disabled-provider non-leak verified by manual trace with
anthropic-only enabled — mistral/openai aliases correctly filtered out
by `loadedProviders.has(providerName)` gate. Test 17e explicitly
asserts this.
- F15+D17 round-trip: client GET /v1/models → sees alias entry → POSTs
with alias → getProviderForModel resolves via same _aliasMap → cache
key uses canonical → response works. Both surfaces read the same Map
(SPOT).
- F15+D23 cacheable interaction: cacheable opt-out doesn't suppress
alias surfacing in /v1/models — cacheable is about cache-write behavior
while discovery should still surface enabled providers. Intentional.
Authority:
- ADR 0003 § Optional fields (response_format + tool_choice IR shape)
- OpenAI Chat Completions API spec (the field semantics)
https://platform.openai.com/docs/api-reference/chat/create
- ADR 0005 § D2 + Amendment 4 (F10's own amendment landing here)
- ADR 0002 § Loading model + D17 getProviderForModel SPOT (F15's alias
origin)
- ALIGNMENT.md Rule 2(b) — no invented OpenAI fields
- CC 开发铁律 v1.6 § 10.x — Round-3 Cold Audit caught all 3
Follow-up items (reviewer's non-blocking suggestions, NOT in this PR):
- F8 micro-tests for null/boolean/partial-function inputs (code handles;
test gap only)
- F10 wording precision on OR-disjunction
- Nested-describe wrapper quirk in test-features.mjs (pre-existing
structural issue; D26/D27 describes are children of Suite 16 wrapper).
Cleanup in a future hygiene pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
7ef5510837 |
feat(cache): D23 — implement hints.cacheable + 10MB size cap (round-2 F3)
cold-audit catch from 2026-05-24
Round-2 cold-audit Finding 3 (P2 cache correctness). ADR 0005 § "Cache
write conditions" items 3 and 4 were documented but never wired in
code:
- Item 3: "The provider's hints.cacheable flag is not false"
- Item 4: "The response is below a size cap (default 10 MB; configurable)"
Grep verified zero matches for `cacheable` / `10485760` / size-cap
patterns in lib/ or server.mjs pre-D23.
Changes (9 files, +391 / -12):
1. docs/adr/0002-plugin-architecture.md — Amendment 3 adds `cacheable`
to the Provider contract hints list (after D11's Amendment 1 added
maxSpawnTimeMs). Authority chain cites ADR 0005 § Cache write
conditions item 3 as the field's origin.
2. docs/adr/0005-cache-cross-provider.md — Amendment 3 documents the
D23 implementation of items 3 + 4 + the D16-interaction edge case
(truncated > 10MB → no-op eviction, structurally bounded since
responses > 10MB are anomalous by ADR's own rationale).
3. lib/providers/base.mjs — ProviderHints typedef gains
`[cacheable]` (optional boolean); validateProvider rejects non-
boolean non-undefined values. Omission accepted (default = true).
4. 3 plugins (anthropic / codex / mistral) each declare
`cacheable: true` explicitly with citation comment.
5. lib/cache/store.mjs — CacheStore constructor accepts
`maxEntryBytes` (default 10 * 1024 * 1024 = 10_485_760) +
injectable `_warnFn`. `set()` computes
`Buffer.byteLength(JSON.stringify(value))`; if exceeded, warns via
`_warnFn` and returns undefined (no persistence). `getOrCompute`
still returns the computed value to caller — cache write skipped
but caller gets data; subsequent identical requests re-spawn.
6. server.mjs — 4 sites coordinated for cacheable opt-out:
- `executeHopFn`: cacheable check before D13 shouldBypassCacheForHop
(permanent provider policy precedes per-request bypass condition)
- `cacheStore.peek` gate at line ~504: `cacheableForFirstHop`
short-circuit
- Real-streaming branch entry condition at line ~522:
`cacheableForFirstHop` added (so cacheable: false + stream falls
through to buffered path which honors the opt-out via executeHopFn)
- Both `cacheStore.set` sites in streaming branch wrapped in
`if (cacheableForFirstHop)` defensive guards (post-D23
restructure these are unreachable for cacheable: false, but the
guards make intent explicit and survive future refactors)
7. test-features.mjs — 13 new tests:
- 5 validator tests (Suite 4): explicit true/false, omitted, string
rejected, number rejected
- 5 size-cap unit tests (Suite 9): default 10MB, custom override,
oversize skip + warn capture, within-limit normal persistence,
getOrCompute oversize returns-but-doesn't-cache + re-spawn
- 3 cacheable integration tests (Suite 9e): non-streaming opt-out,
streaming opt-out (the regression case that pre-fold-in failed),
X-OLP-Cache header consistency on both paths
Tests: 335 → 348 (+13). All pass on Node 20.
Pre-commit fold-in (per evidence-first checkpoint #4):
- **D23 reviewer flagged 2 blocking issues**: (1) the cacheable opt-out
in initial implementation was only in `executeHopFn` (buffered path);
the D10 real-streaming branch in server.mjs bypassed the check
entirely — calling streamPlugin.spawn() directly and writing to
cacheStore.set() at 2 sites without consulting cacheable. (2) Suite
9e integration tests didn't cover stream: true so the leak wasn't
caught.
Both diff-review and the implementer focused on `executeHopFn`
because that's where the cold-audit reviewer pointed for Finding 3.
Same class of "narrow attention" miss as several earlier D-days.
Fold-in: compute `cacheableForFirstHop` once at request entry; add
`!cacheableForFirstHop` short-circuit to peek gate; add
`cacheableForFirstHop` to streaming-branch entry condition (forces
fall-through to buffered path which has the opt-out); add defensive
guards on both `cacheStore.set` call sites. Added a 3rd Suite 9e
test covering stream: true + cacheable: false (which pre-fold-in
would have failed by serving the second request from cache).
This is now the FOURTH D-day where a doc-vs-code or path-coverage
gap was caught by the reviewer rather than the implementer. The
v1.6 § 10.x diff-review discipline continues to pay off.
Default behavior unchanged for 3 shipped plugins (all explicitly
`cacheable: true` → cache path identical to pre-D23).
Authority:
- ADR 0002 Amendment 3 (in-place) — establishes cacheable in contract
- ADR 0005 Amendment 3 (in-place) — documents implementation of items
3 + 4
- ADR 0005 § Cache write conditions items 3 + 4 — the original
authority for both rules
- CC 开发铁律 v1.6 § 10.x — Round-2 Cold Audit caught the missing
implementation; diff-review Mode A caught the streaming-path gap
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): REQUEST_CHANGES on initial, APPROVE after fold-in (implicit
— fold-in followed the exact recommendation). Verified:
- ADR amendment placement + structure
- Validator typedef + checks
- Size cap implementation in CacheStore + inflight slot release on
oversize-skip
- All 4 interaction cases (cacheable × cache_control × D16
× ordering) coherent post-fold-in
- 13 new tests including the regression test that would have failed
on pre-fold-in code
Follow-up items (reviewer's non-blocking notes, NOT in this PR):
- ADR 0005 Amendment 3 could add one sentence on the prior-write-also-
oversize case (file as docs polish)
- Consider extracting `shouldUseCacheForHop(hopProvider, ir)` helper
combining D13 + D23 logic — reduces miss-risk for next reviewer
- Test 30 could add `assert.equal(store._inflight.size, 0)` as
inflight-slot leak regression guard
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
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>
|
||
|
|
bafa6d1991 |
fix(fallback)+docs(adr-0004): D16 — honor "usable chunks streamed" qualifier on SPAWN_FAILED
cold-audit catch from 2026-05-23
Cold-audit Finding 17 (P2 fallback correctness). ADR 0004 § Trigger
taxonomy Hard triggers bullet 3 says "Provider CLI exit code ≠ 0
**with no usable response chunks streamed**" — the qualifier was
not honored. Pre-D16 code in `collectAllChunks` re-raised SPAWN_FAILED
unconditionally, discarding any partial chunks. Concrete failure:
provider yields 1000 chars of completion then exits non-zero (e.g.,
post-stream cleanup error) → chunks dropped, fallback to next provider,
user pays double spawn cost and loses the original provider's output.
Coordinated change across two layers, single commit per the
ADR-with-code pattern (D11 / D15 precedent):
1. docs/adr/0004-fallback-engine.md — Amendment 1 (top of doc, matching
D11/D15 placement convention):
- Documents the "usable chunks streamed" semantics precisely
- Behavior split: chunks.length > 0 + SPAWN_FAILED → synthesize stop
+ return (Case B); chunks.length === 0 + SPAWN_FAILED → re-throw
(Case A, hard trigger fires as before)
- finish_reason='length' rationale (4 reasons documented)
- Streaming-path note: ADR 0004 first-chunk rule already handles the
analogous case for D10's real-streaming branch; D16 applies to
buffered path only
- Cache behavior: write-through `getOrCompute` (preserves D4 singleflight
during truncation event) then evict via `set(ttlMs=0)` (so future
fresh callers re-spawn). `__truncated` non-enumerable marker
travels with the chunks array for follower visibility
2. server.mjs `collectAllChunks` salvage path:
- try/catch around the for-await loop
- On SPAWN_FAILED with chunks.length > 0: synthesize stop chunk
`{type:'stop', finish_reason:'length'}`, log warn event
`spawn_failed_after_usable_chunks`, mark chunks array with
non-enumerable `__truncated`, return (no re-throw)
- On SPAWN_FAILED with chunks.length === 0 OR any other error:
re-throw (preserves existing hard-trigger semantics)
3. server.mjs `executeHopFn` cache eviction:
- After `cacheStore.getOrCompute(...)` returns, check `result.__truncated`
- If truncated: `cacheStore.set(keyId, hopCacheKey, result, 0)` —
ttlMs=0 causes `_isAlive` to treat the entry as expired on next
read (verified in lib/cache/store.mjs)
4. test-features.mjs Suite 13 — 3 new tests:
- Case A regression: SPAWN_FAILED at iter 0 + 2-hop chain → openai
serves, X-OLP-Fallback-Hops: 1 (no behavior change)
- Case B 2-hop: 2 deltas + SPAWN_FAILED → anthropic serves with
synthesized stop, hops=0, finish_reason='length', content
concatenates, openai NOT called
- Case B single-hop: 1 delta + SPAWN_FAILED → HTTP 200 (not 502),
finish_reason='length', partial content visible
Tests: 297 → 300 (+3). All pass on Node 20.
Pre-commit fold-ins (per evidence-first checkpoint #4 — fold-ins
themselves need second-pass review):
- **Error-chunk-in-chunks fold-in (sonnet flagged)**: pre-D12 code
pushed error chunks BEFORE throwing. Post-D16's `chunks.length > 0`
check would incorrectly include an error chunk and trigger Case B
for a path that's actually Case A. Moved the `type === 'error'`
check BEFORE the push, restoring the invariant that the chunks
array contains only delta/stop chunks. Verified all 3 scenarios:
(1) error at iter 0 → throws before push → length=0 → Case A
(2) delta×2 + error at iter 3 → throws before push → length=2 → Case B
with delta×2 + synthesized stop (no error chunk leaks)
(3) delta + non-zero exit from outside loop → length=1 → Case B
- **ADR doc-code drift fold-in (D16 reviewer flagged)**: original
Amendment 1 text said the salvaged result "bypasses
cacheStore.getOrCompute and is returned directly, exactly as the
cache-bypass path does." This was factually wrong — the code
write-throughs via getOrCompute then evicts via ttlMs=0. The drift
was ironic: D16 was about removing doc-code drift in ADR 0004
bullet 3 itself, and the amendment was about to ship fresh drift.
Corrected to accurately describe the write-then-evict pattern and
the rationale (preserving D4 singleflight during truncation events).
Authority:
- ADR 0004 § Trigger taxonomy Hard triggers bullet 3 (the qualifier
this amendment makes load-bearing)
https://github.com/dtzp555-max/olp/blob/main/docs/adr/0004-fallback-engine.md
- ADR 0005 § Cache write conditions item 1 — "response completed
successfully (no truncation, no error mid-stream)"
- OpenAI Chat Completions finish_reason enum (stop|length|tool_calls|
content_filter|function_call|null)
https://platform.openai.com/docs/api-reference/chat/object
- ADR 0004 § Fallback safety — first-chunk rule (already governs the
analogous case in the real-streaming path)
- ALIGNMENT.md Rule 2(c) spirit — ADR amendment + code change land
in same merge (D11 / D15 precedent)
- CC 开发铁律 v1.6 § 10.x — Cold Audit caught this; diff-review
passes focused on first-chunk rule for streaming missed the
buffered path's truncation-vs-fallback decision point
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE_WITH_MINOR. Folded the ADR doc-code drift minor
before commit. Walked all 3 error-chunk scenarios against actual code
to verify the pre-commit fold-in is correct. Analyzed the eviction
race window (sub-ms post-inflight pre-eviction window where a fresh
caller could hit the truncated cache before eviction lands) and
concluded it's structurally bounded — one-shot leak per truncation
event; subsequent callers re-spawn. Acceptable as v0.1.
Follow-up items (reviewer's non-blocking suggestions, NOT in this PR):
- 4th test asserting second identical request triggers fresh spawn
(defense-in-depth around the eviction; store.mjs ttlMs=0 semantics
are independently established)
- `cacheStore.delete()` API (cleaner than set-with-ttlMs=0 — leaves
no dead entry in the namespace map; future PR)
- `cache_evicted_truncated` log event for dashboard observability
- SPAWN_TIMEOUT salvage parity — same architectural argument as
SPAWN_FAILED (user paid for partial content); deferred as a
separate cold-audit finding for a future D-stage
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
8ae77c3ae3 |
fix(cache)+docs(adr-0005): D15 — expand cache key composition to include max_tokens/top_p/stop/tool_choice
cold-audit catch from 2026-05-23
Cold-audit Finding 7 (P2 cache correctness). ADR 0005 § Cache key
composition (v1.0) listed 7 fields. ADR 0003 § Optional fields added
`max_tokens`, `top_p`, `stop`, `tool_choice` as IR-carried fields
that affect model output. Pre-D15 cache key omitted those 4 —
identical IRs differing only in those fields collided on the same
cache key but produced legitimately different outputs. Concrete
hazard: a request with `max_tokens: 100` could receive a cached
response generated by `max_tokens: 4000` — wrong content (truncated
or unexpectedly extended).
Coordinated change across two layers, single commit per the ADR-with-
code pattern established by D11:
1. docs/adr/0005-cache-cross-provider.md — Amendment 2 (top of doc,
matching D11 Amendment 1 placement convention)
- Documents the 4 missing fields + concrete failure mode
- Expands the v1.0 cache key composition spec
- Documents the `?? null` collapsing semantics (explicit-null
equals absent — consistent with existing temperature/response_format)
- Adds forward-looking note: future IR field additions must be
evaluated for cache-key inclusion at addition time; default is
include unless explicit rationale documents safe omission
2. lib/cache/keys.mjs — append the 4 fields to `keyObj` after the
existing 7 (preserves field ordering; pre-D15 cache entries are
forced misses on first request — schema forward-compatible)
- `max_tokens: ir.max_tokens ?? null`
- `top_p: ir.top_p ?? null`
- `stop: ir.stop ?? null`
- `tool_choice: ir.tool_choice ?? null`
- Updated file-level docblock + function-level JSDoc + @param ir
enumeration to reflect new schema (the @param fix also closes a
pre-existing partial-list issue that omitted cache_control)
3. test-features.mjs — 5 new tests in Suite 9:
- max_tokens differs → different key
- top_p differs → different key
- stop differs → different key
- tool_choice differs → different key (covers string-form
`'auto'` vs `'none'`; object-form coverage left for future
defense-in-depth — slot existence is verified by the string case)
- Both-absent stability check (`?? null` collapsing produces
identical keys for omitted-vs-omitted)
Tests: 292 → 297 (+5). No hash constants pinned in existing tests
(grep verified) so no pre-D15 tests required updating; assertions
were already shape-based (`assert.equal` / `assert.notEqual` on
hash strings, never specific hex values).
Authority:
- ADR 0003 § Optional fields — source of the 4 IR field definitions
https://github.com/dtzp555-max/olp/blob/main/docs/adr/0003-intermediate-representation.md
- ADR 0005's stated invariant: "different output → different cache
entry" — the addition restores compliance with this invariant
- OpenAI /v1/chat/completions spec — confirms the 4 fields affect
output:
https://platform.openai.com/docs/api-reference/chat/create
- ALIGNMENT.md Rule 2(c) spirit — ADR amendment + code change land
in same merge (D11 precedent established this pattern for
Provider-contract changes; D15 applies same pattern for cache-key
schema changes)
- CC 开发铁律 v1.6 § 10.x — Cold Audit caught this; diff-review pass
that approved original ADR 0005 did not cross-reference all ADR
0003 optional fields
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE_WITH_MINOR. Folded the one minor (`@param ir`
JSDoc stale partial list) before commit — same JSDoc precision logic
that drove D11's B1 fold-in. Two remaining non-blocking suggestions
(tool_choice object-form test coverage; D11-vs-D15 amendment structure
template) tracked as defense-in-depth opportunities, not required for
spec compliance.
Reviewer's highest-value verification: field ordering preserved.
keyObj at lib/cache/keys.mjs:203-217 reads exactly:
`{provider, model, messages, tools, temperature, response_format,
cache_control, max_tokens, top_p, stop, tool_choice}` — existing 7
in unchanged positions, 4 new appended at end. JSON.stringify on
Node 18+ preserves insertion order; SHA-256 hash deterministic
across runs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
f659e29c09 |
docs(adr-0002): D11 amendment — add maxSpawnTimeMs to Provider contract hints
cold-audit catch from 2026-05-23 Retroactive contract sync addressing cold-audit Finding 4 (P2 governance). D10 (commit |
||
|
|
dff428f3d0 |
docs(governance): fold in codex round-2 review findings (6 issues)
External Codex CLI review pass 2 surfaced 6 substantive issues that round 1 fold-in missed — the self-consistency trap recurred when fold-in was scoped only to files codex explicitly named in round 1. This commit closes round 2 in full. 1. ADR 0002 contradicted ALIGNMENT.md (P1, codex round 2 finding 1) ADR 0002 still said "three default-enabled (Anthropic, OpenAI Codex, Mistral Vibe)" while ALIGNMENT.md (post round 1) said v0.1 ships zero Enabled Providers. Accepted ADR contradicted constitution. Fix: ADR 0002 + ADR 0001 + docs/adr/README.md index rewritten to Candidate framing. 2. release.yml would publish stale v0.1.0-bootstrap notes (P1, round 2 finding 2) The "Unreleased" amendments would have been silently dropped on tag push because release.yml extracts only the matching version section. Fix: CHANGELOG restructured so the amended state IS the v0.1.0- bootstrap section. Full review history (opus + 2 codex rounds) captured inline. 3. package.json advertised non-existent entrypoints (P2, round 2 finding 3) main/scripts.test/scripts.start pointed to files that do not exist. Local npm test and npm start failed; CI masked. Fix: remove all three from package.json. They return in Phase 1 alongside the real files. test.yml bootstrap-tolerance updated to also skip when scripts.test is absent. 4. models-registry.json missing despite SPOT claim (P2, round 2 finding 4) Fix: minimal stub committed (version + empty providers map). alignment.yml validator now actually runs. 5. alignment.yml commit-citation soft check Bash subshell trap (P2, round 2 finding 5) git log ... while read ... WARN=1 — the while loop ran in a subshell because of the pipe, so WARN never propagated out. The post-loop check always reported "clean" even when warnings fired. Fix: process substitution done less than less than (git log ...). 6. Tier A "permanent" wording inconsistent across ADR 0006 + alignment. yml workflow text (P3, round 2 finding 6) Fix: unified to "Excluded by default with no routine reinstatement path; re-inclusion requires ADR 0006 supersession or amendment with new primary-source evidence." Reviewer: OpenAI Codex CLI (external, fresh-context, pass 2). Iron Rule 10 satisfied — round 2 reviewer was not the implementer of round 1 fold-in. Memory learning updated: the self-consistency trap recurs in the fold-in step. Future fold-ins must grep the entire repo for the concept, not only edit files the reviewer named. See learnings/ai_reviewer_self_ consistency_trap.md in cross-machine memory. Co-Authored-By: Claude Opus 4.7 (noreply@anthropic.com) |