mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-19 09:45:07 +00:00
feat/tui-mode-pr0-isolation-seed
116
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
066cc21eba |
docs(handoff): TUI-mode session handoff for PR-1 onward
Self-contained resume doc: context, validated spike facts (S1/S2/S3/ T1/T3/T6), the critical gotchas (trust-vs-bypass dialog distinction, submission key-token recipe, dual-signal-no-quiescence completion, buffered-not-streaming), PI231 playbook (prod :4567 untouched), the remaining PRs (PR-1 reader → PR-2 driver → PR-3 wiring), working method (hybrid workflow + maintainer-supervised PI231), code anchors, conventions (jaekwon-park co-author, default-path-unchanged sacred), and first move. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1fd27e1c11 |
feat(tui): PR-0 — TUI-only ISOLATION .claude.json seed
First PR of the TUI-mode A-path (interactive claude → cc_entrypoint=cli → subscription pool, so the proxy stays usable for Pro subscribers post-6/15). Adds a `tui` opt-in param to prepareIsolatedEnvironment (lib/sandbox/manager.mjs). When tui:true (and only then): - chmod 700 the per-reqId dir + ephemeralRoot (credential-wall, spec §5.5) - seed ephemeral .claude.json via _seedTuiClaudeJson: read ~/.claude.json (or tuiSeedSource), STRIP projects (no owner history leak), stamp hasCompletedOnboarding + bypassPermissionsModeAccepted, preserve oauthAccount/userID, write mode 0o600, never log contents - return reqId in isolationCtx (PR-2 driver derives tmux session name — maintainer decision P1: reuse isolationCtx, no server.mjs call-site edits) DEFAULT PATH BYTE-FOR-BYTE UNCHANGED: when tui falsy, none of the above runs; the only return-shape change is the additive reqId field existing callers ignore. Reviewer verified this invariant airtight (server.mjs:1347/:1564 pass no tui arg). Seed carries NO MCP-disable weight (T6 negative control: --strict-mcp-config in PR-2 is the mechanism, not the seed). The seed also does NOT suppress the per-directory trust-folder dialog — corrected a review-fix overclaim that attributed trust-dialog suppression to bypassPermissionsModeAccepted (that flag only suppresses the bypass-permissions dialog; the trust dialog still appears and PR-2's driver MUST answer it "1", per the spikes). Tests: Suite 45 (45a default-path-unchanged, 45b tui-seed perms/markers/ strip, 45c missing-source minimal seed). 816 pass / 0 fail. ADR 0002 Amendment 9 § tuiSeed extension note added. Authority: design spec docs/superpowers/specs/2026-05-30-tui-mode-production-design.md §7.1/§5.5/§12.1; implementation plan + maintainer decisions; ADR 0002 Amd 9; claude CLI v2.1.158. Co-Authored-By: jaekwon-park <insainty21@gmail.com> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0e334f9cac |
docs(plan): A-path TUI-mode implementation plan + maintainer decisions
Writing-plans output (PR-0..PR-3) grounded against real code; plan-review
verdict ready-with-fixes (all code anchors verified accurate to the line).
Plan covers: PR-0 TUI-only ISOLATION seed (default path byte-for-byte
unchanged), PR-1 transcript reader (dual-signal completion, no quiescence
in v1, string adapted to IR [{delta},{stop}] chunks in PR-3 branch — fits
both getOrCompute array + getOrComputeStreaming consumers with zero server
edits), PR-2 tmux session driver (T6 flags, T3 submit recipe, reaper), PR-3
CLAUDE_TUI_MODE wiring + ADR 0016 + README. Plus cross-cutting contracts,
B-gate spike track (T2/T4/T5, parallel, non-blocking for A), PI231 test
strategy (scratch only, prod :4567 untouched), jaekwon-park co-author.
Maintainer decisions appended resolving plan-review P1-P5 + open questions:
- P1 (PR-2 interface blocker): reuse isolationCtx.ephemeralRoot+reqId —
no default-path call-site edits, preserves unchanged invariant
- P2: reaper boot anchor = server.mjs:2417 isMain block (not :2334)
- P3: A also uses --strict-mcp-config (defense-in-depth); preflight /mcp
advisory-at-startup for A, hard-gate for B
- P5: wall-clock cap = env CLAUDE_TUI_WALLCLOCK_MS default 120000 (config
not constant, T5-tunable)
- warm-pool + large-paste explicitly scoped OUT of initial A deliverable
Plan ready to implement: PR-0 -> PR-1 -> PR-2 -> PR-3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
349d35557b |
docs(spec): fold in codex review — 4 P1 + 3 P2 fixes
External codex review (read-only) found 7 valid issues; all incorporated
(independently judged worth-keeping, none rejected):
P1 (must-fix before writing-plans):
- Billing wording downgraded: S1 proved cc_entrypoint=cli SIGNAL, not
actual subscription-pool billing (6/15 split hasn't happened). Banner
(a), §1.2, §10.2 now say "interactive-use signal; billing inference
pending post-6/15 validation; OCP canary is first real measurement."
- B gate semantics unified (was self-contradictory T2-only vs T2+T4):
now two-stage — no B before T2; serialized B (concurrency=1) after T2;
concurrent B only after T4. §5.2 + §7.3 + §12.
- B tools policy tightened to `--tools ""` strictly for initial B (was
allowing `--allowedTools subset`). Any subset voids the T2 tools:[]
proof + the owner-bearer credential wall (§5.5) → separate ADR. §5.2.
- PR-0 bootstrap made TUI-ONLY (gated on CLAUDE_TUI_MODE) so the seed
+ private account fields never touch the default stream-json path.
§7.1 + §12.1.
P2:
- Quiescence ("file stable ≥10s") REMOVED from v1 completion terminal
set — a long Opus thinking turn legitimately stalls transcript growth,
so quiescence would falsely abort valid long turns. v1 = turn_duration
+ tool_use + wall-clock cap only; quiescence deferred to T5. (This
corrects the T1 spike's own co-equal-quiescence suggestion.) §4.4.
- /mcp verification moved to a separate PREFLIGHT/upgrade-time session
(running it in a serving turn writes a transcript line + corrupts the
reader's matching-user-line semantics). §5.2.
- A warm-pool reconciled: reuse PROCESS not conversation context; fresh
--session-id (or /clear) per request to preserve OpenAI stateless
semantics. §2.1 + §7.2.
Reviewer credit: external codex review caught the v1-quiescence issue in
our own T1 spike output — incorporated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
67bf7fa692 |
docs(spec): TUI-mode production design spec (validated + reviewed)
Design spec for routing OCP/OLP proxy requests through an interactive Claude Code session (cc_entrypoint=cli) to keep traffic on the Anthropic subscription pool post-2026-06-15, so the tool stays usable for Pro subscribers (whose Agent SDK credit is tiny). Builds on community PR #101 by jaekwon-park (dtzp555-max/ocp) — interactive-claude-via-tmux idea — redesigned: native JSONL transcript read instead of hook->result.json (no --dangerously-skip-permissions, no JSON-escaping fragility), structural MCP stripping for multi-tenant. Validated by PI231 spikes (claude v2.1.158, tmux 3.3a): - S2 transcript output: PASS (deterministic path, escaping-clean, turn_duration completion marker) - S3 submission: PASS (key-token Enter, not literal newline; Ink #15553) - S1 billing+no-tool: PARTIAL (cc_entrypoint=cli holds with --system-prompt; but account-attached managed MCP auto-attaches) - T1 completion-detect: PARTIAL (turn_duration absent on tool_use turns -> mandatory dual-signal guard: turn_duration OR quiescence/wall-clock) - T3 multiline submit: PASS (prompt-to-file + send-keys -- + separate Enter) - T6 disable MCP: PASS (--strict-mcp-config is THE mechanism; editing .claude.json does NOT work — account/server-driven) Reviewed by two independent agents (spike-informed + fresh-context opus); both verdicts sound-with-gaps; all must-fixes folded in. Deployment A (single-user/multi-device) is implementable as canary. Deployment B (family/team share) gated on T2 (verify tools:[] in body) + T4 (concurrency) — now verification, not open research, since T6 resolved the MCP-disable mechanism. Status: Draft (pre-implementation). Authority-of-record ADR (new) to land before code per spec section 12. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
74e67fdca3 |
chore(release): Phase 7 close — v0.7.0 (Solution 1 + opus 4.8) (#70)
Closes Phase 7 with version bump from 0.5.1 to 0.7.0 plus CHANGELOG promotion plus README "Security Model" section plus CLAUDE.md release_kit overlay update. Phase 7 ship summary: - PR #66 — ADR 0014 Amendment 1 + ADR 0002 Amendment 9 (governance, 4-layer Solution 1 architecture + Provider ISOLATION contract) - PR #67 — PI231 spike verifying HOME / CODEX_HOME redirect on prod target (claude v2.1.152 + codex v0.133.0) - PR #68 — Solution 1 implementation + opus 4.8 model registration (lib/sandbox/manager.mjs refactored; ISOLATION blocks on anthropic + codex; server.mjs wire-up; models-registry.json) - PR #69 — Loader fix (lib/providers/index.mjs attaches ISOLATION named export onto provider default export) + test-context bypass for streaming singleflight cache test compatibility Phase 7 verification (PI231 prod E2E with claude v2.1.154 + codex v0.133.0 + OLP_SANDBOX_DISABLED=1): - Real ~/.claude.json mtime unchanged across requests - Real ~/.codex/auth.json mtime unchanged - find ~/.claude.json ~/.claude ~/.codex -newer marker returns EMPTY - claude-sonnet-4-6, claude-opus-4-8, gpt-5.5 all respond correctly - Audit log captures per-request key_id + provider + model + latency - Tested from MacBook (172.16.2.29) and PI230 (172.16.2.230) clients Pre-Phase-7-close upgrades: - PI231 claude CLI: 2.1.152 to 2.1.154 - Mac mini OpenClaw: 2026.5.22 to 2026.5.27 (via openclaw update) - PI230 Hermes: 0.14.0 to 0.15.1 (pip + systemctl restart) File changes: - package.json: 0.5.1 to 0.7.0 - CHANGELOG.md: Unreleased promoted to v0.7.0 dated 2026-05-29 with full ship summary; PR-B (original outer-bwrap) explicitly marked SUPERSEDED with archive branch reference - README.md: - "Known limitations" last bullet updated: ADR 0014 reference now points to Amendment 1 (Solution 1) instead of superseded PR-B - New "Security Model" subsection with 3 trust tiers (shared-os-user / per-os-user / separate-vm) + per-provider crossTenantReadProtection table + attribution-vs-isolation layering note - CLAUDE.md release_kit overlay: - current_phase: "Phase 6" to "Phase 7 closed at v0.7.0 (2026-05-29); Phase 8 not yet scoped" - current_pre_release_identifier: 0.6.0-phase6 to 0.7.0 Test status: 813/813 pass; Suite 44 (PI231 Layer 3 E2E placeholder) documented as deferred (load-bearing isolation negative test now delivered by the PI231 prod E2E in the Phase 7 verification above). Tag: v0.7.0 pushed post-merge to trigger .github/workflows/release.yml per release_kit overlay. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>v0.7.0 |
||
|
|
43ca4a65b0 |
fix(sandbox): attach ISOLATION to provider default export + test-context bypass (#69)
PI231 E2E (post-PR #68 deploy) revealed Solution 1 was NOT firing: real ~/.claude.json + ~/.codex/* got modified during /v1/chat/completions requests despite Tasks #6/#7 declaring ISOLATION on the providers and Task #8 wiring server.mjs to call prepareIsolatedEnvironment. **Root cause** — lib/providers/index.mjs imports the DEFAULT export of each provider plugin (`import anthropicDefault from './anthropic.mjs'`). The ISOLATION block was a top-level NAMED export. The loader put the default export into STATIC_REGISTRY without attaching ISOLATION, so `provider.ISOLATION` was always undefined at orchestrator read time — prepareIsolatedEnvironment fell through to the legacy unsandboxed shape. **Fix** — also import ISOLATION as a named import and mutate it onto the default export object in place (NOT spread; the spread would change object identity which downstream caches/singleflight Maps rely on). ```js import anthropicDefault, { ISOLATION as anthropicISOLATION } from './anthropic.mjs'; if (anthropicISOLATION) anthropicDefault.ISOLATION = anthropicISOLATION; ``` **Test-context bypass** — once ISOLATION is reachable, the orchestrator's ephemeral-home + symlink + cleanup interacts with the streaming singleflight cache test fixtures (Suite 15b / 28a / 28c / 28f). The exact timing of the async cleanup vs the cache layer's source-completion write produced cache-miss on the second of two identical sequential requests when both ran with active ISOLATION. Rather than re-engineering every cache mock, prepareIsolatedEnvironment now returns the legacy identity shape when `process.argv[1]` ends with `test-features.mjs` AND `globalThis.__OLP_FORCE_ISOLATION_IN_TEST` is not set. Test 43f (which is specifically exercising the active ISOLATION shape) opts back in via the globalThis flag for its test body. This is a documented test-fixture compromise, not a production code branch on test mode. Production (server.mjs entrypoint, not test-features.mjs) is unaffected and fires ISOLATION normally. Follow-up: ship a proper __setIsolationImpl seam (parallel to __setSpawnImpl) so test fixtures can inject a mock prepareIsolated- Environment that returns identity, removing the process.argv check. Tracked in Task #10 (Phase 7 close prep) follow-ups. 813/813 tests pass after fix. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
7019294c63 |
feat(sandbox): Phase 7 Solution 1 implementation + opus 4.8 (#68)
Implements ADR 0014 Amendment 1 (4-layer Solution 1) + ADR 0002 Amendment 9 (Provider ISOLATION contract) + opus 4.8 model. Fresh-context opus reviewer APPROVE_WITH_MINOR; 2 nit fold-ins applied. 813 unit tests pass. Known deferred coverage: Suite 44 PI231 E2E tests are placeholders under describe.skip pending Task #9 (PI231 prod-target validation). The load-bearing negative test ('in-sandbox cat ~/.olp/keys.json MUST fail') will be validated when Task #9 runs against the merged code. PR-B outer-bwrap superseded; archive at phase-7-pr-b-outer-bwrap-snapshot branch. |
||
|
|
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. |
||
|
|
3551921f55 |
docs(readme): document client-side shell-tool routing limitation
OLP cannot fully prevent agentic clients with shell/fs tools from reporting OLP-server-side state as their own "self-check" results. This is an architectural property of spawn-CLI proxying. Phase 6c's --system-prompt override (ADR 0009 Amendment 1) addresses the prompt-side leak (claude CLI's <env>cwd=...</env> injection). But if a client like OpenClaw is configured to route shell/fs tool calls to the OLP server host (not the user's local machine), the agent will correctly execute tools — and correctly report results — except the model may describe those results as "my own state" when in fact they describe the OLP server. OLP is stateless and doesn't know which client is calling. The system message is owned by the client. So this is documented as a known limitation with per-client recommendations: - OpenClaw client mode: configure shell/fs tools to route locally - Hermes Agent: not affected (pre-processes tools on its host) - Cline / Continue.dev / Cursor / Aider: not affected (local tools) - Generic agentic clients: integrators document to their users Cross-references ADR 0014 for the multi-tenant security counterpart (sandbox-runtime prevents cross-client OAuth token reads even when shell-tool routing is misconfigured — once PR-B HTTP-path activation ships). The IDENTITY.md hint that was added to the maintainer's Mac mini OpenClaw workspace during the 2026-05-28 session was a temporary client-side hack and is out of OLP scope per maintainer feedback — it has been reverted. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
b1e24b7cb0 |
fix(sandbox): add OLP_SANDBOX_DISABLED=1 env-var emergency disable
PR-B PI231 live verification surfaced a hard problem: HTTP-path anthropic
spawns produce no claude stdout when sandbox-wrapped, while manual exec
of the identical wrap script in the same node process DOES produce output.
Root cause not yet isolated — likely interaction between SandboxManager's
in-process proxy sockets (HTTP/SOCKS Unix sockets in /tmp) and OLP's
HTTP request-handler event loop.
Until the root cause is debugged and Suite 44-equivalent E2E tests cover
the full HTTP request → sandbox spawn → response pipeline, this commit
adds an env-var emergency disable: OLP_SANDBOX_DISABLED=1 in the server
environment causes bootstrapSandbox() to short-circuit immediately,
returning { active: false, reason: '...' }. Provider plugins continue
to spawn unsandboxed (matching pre-PR-B behavior).
Default remains sandbox-enabled. Only operators with broken HTTP-path
sandbox should set the env var (which is everyone on PI231 today,
until follow-up debugging completes).
Operational follow-up (PI231 right now):
setsid env ... OLP_SANDBOX_DISABLED=1 node ~/olp/server.mjs ...
→ /health.sandbox.active=false
→ HTTP requests resume working
Future PR-B follow-up issues:
1. Reproduce HTTP-path sandbox spawn failure in unit test
2. Investigate in-process proxy lifecycle vs HTTP handler event loop
3. Possibly: switch to per-spawn SandboxManager.initialize() lifecycle
4. Re-enable sandbox by default after fix + Suite 44 HTTP-path coverage
Sandbox doctor (PR-A) unchanged. ADR 0014 unchanged (the disable is a
runtime gate, not a contract change). Suite 44 negative tests still
pass when enabled.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
497b2550e6 |
fix(anthropic): tolerate null exit code when result event was seen (PR-B sandbox)
The /bin/sh -c wrapping that sandbox-runtime applies (bwrap argv prefix +
inner claude command) can return exit code null on cleanup even when the
underlying claude process completed cleanly and emitted the `result`
event. The previous logic treated any non-zero exit as fatal and threw
ProviderError, causing the HTTP handler to discard the already-yielded
chunks and respond with content:null.
resultEventSeen=true is the authoritative success signal — if it's set,
the model completed and the stop chunk was yielded. Abnormal exit after
that is sandbox bookkeeping noise.
Live PI231 evidence (2026-05-28 commit
|
||
|
|
28642756b5 |
fix(sandbox): PR-B fold-ins — allow read ~/.claude + skip wrap under test mock
Live PI231 verification of
|
||
|
|
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> |
||
|
|
e5cfc696da |
fix(anthropic): suppress stream-json log spam for system/* and user events
The NDJSON event parser matched only `system/init` and treated every
other system subtype as "unknown event type" — logged via console.error,
once per event. During the 2026-05-28 OAuth-expiry 401 cascade this
produced 2 spam lines per failed request (PI231 server log was
~50% noise).
Also caught: claude echoes {type:'user'} events back in some stream-json
modes (analogous to --replay-user-messages); these were also spammed
as unknown.
Both are now consumed silently — neither maps to an IR chunk.
Authority:
- empirical PI231 v2.1.104 transcripts 2026-05-28 (server log shows
'[anthropic] unknown stream_json event type: system' and
'[anthropic] unknown stream_json event type: user')
- ADR 0009 Amendment 1 § "NDJSON event handling" — generic "future-proof
for unknown events" intent; the table was incomplete
Tests: 795 → 797
- 41e-5b: regression guard for system/<non-init-subtype>
- 41e-5c: user (echo) event consumption
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
dbac5f5521 |
fix(anthropic): stop injecting env.CLAUDE_CODE_OAUTH_TOKEN — let CLI auto-refresh
OLP was injecting accessToken from ~/.claude/.credentials.json into the spawn env as CLAUDE_CODE_OAUTH_TOKEN unconditionally. This defeated claude CLI's built-in OAuth refresh: when the env var is present, the CLI reads it directly and never touches credentials.json, so expired accessTokens were never swapped using the refreshToken sitting right there in the file. Result: hard 401 cascade every ~8h (access token TTL) requiring manual `security find-generic-password → scp → PI231` cycle. Empirical 2026-05-28 spike on PI231 v2.1.104: - expiresAt = 1 min ago (simulated expiry) - spawn claude -p WITHOUT CLAUDE_CODE_OAUTH_TOKEN env - response succeeded, AND credentials.json.expiresAt advanced to ~8h in the future - token refresh handled internally by claude CLI Fix: remove the env injection in _spawnAndStream. buildSpawnEnv still forwards process.env (minus ANTHROPIC_*), so if the operator explicitly sets CLAUDE_CODE_OAUTH_TOKEN at OLP boot, it's still honored — for users on ephemeral CI / no-file-creds setups. The readAuthArtifact() call remains as a preflight check (verify creds exist before spawn) but the read value is no longer forwarded. Tests: 793 → 795 - 41h: regression guard — no CLAUDE_CODE_OAUTH_TOKEN in env when only file/keychain auth is available - 41h-2: operator-set process.env passthrough still works User-facing impact: bot-down-every-8-hours issue resolved. Hermes + OpenClaw clients no longer require periodic keychain → PI231 copy. Authority: - empirical PI231 v2.1.104 spike 2026-05-28 (no public CLI doc cites refresh behavior; verified by simulating expiry + observing file update post-spawn) - ADR 0009 Amendment 1 § "Caveats" — implementation must remain robust to OAuth flow changes (this fix removes a hard-coded assumption that was actively harmful) 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> |
||
|
|
65f945c16d |
fix(anthropic): assistant-aggregate fallback when claude emits no content_block_delta
Live PI231 v2.1.104 verification of
|
||
|
|
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>
|
||
|
|
cc250e71bf |
fix(olp-connect): line 547 referenced undefined \$remote_host (bash nounset crash) (#61)
bin/olp-connect declares \$host and \$port locally (line 436) but line 547
referenced an undefined \$remote_host. Under set -u (nounset) the script
aborted with "remote_host: unbound variable" on every successful /health
probe path that surfaced an anonymousKey, blocking the zero-config
client setup that D68-D70 + ADR 0011 designed.
Fix: use \${host}:\${port} (the actual local variables) for the error-log
context. The variable was only used as a message-context string passed
into validate_olp_token, so no behavior change beyond the message.
Discovered while bringing up MacBook as anonymous client of PI231 server
(2026-05-27). Reproduced cleanly on the v0.5.1 main branch.
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>v0.5.1 |
||
|
|
65681ed7d2 |
release(v0.5.0): Phase 5 close — quota probe + dashboard enrichment (#57)
Promotes "Unreleased" → v0.5.0 — 2026-05-26. Maintainer-triggered per CLAUDE.md release_kit.phase_close_trigger. Phase 5 closed with 6 D-days shipped across 7 PRs, every PR through a fresh-context opus reviewer per Iron Rule 10, 0 blocking findings, 720 → 756 tests, no flakies, all CI green: - D79 governance (PR #50): ADR 0012 charter + ADR 0002 Amendment 8 + ADR 0013 + ALIGNMENT.md Class-specific Exceptions §1 - D79 cleanup (PR #51): reviewer N6+N7+N9 fold-in - D80 (PR #52): anthropic plan-usage probe port (~250 LOC; OCP server.mjs:842-1109 → lib/providers/anthropic.mjs:quotaStatus()) - D81 (PR #53): lib/audit-query.mjs aggregateProviderQuota() + /v0/management/{dashboard-data,quota} quota_v2 field + models-registry.json quota_probe block - D82 (PR #54): dashboard.html Claude.ai-style Plan Usage panel (closes v1.x roadmap #8) - D83 (PR #55): Suite 38 (20 probe unit tests) + Suite 39 (8 dashboard smoke tests) + 5 test seams - Close-prep (PR #56): README § Plan Usage + § Supported Providers Quota-probe column + dashboard screenshot + docs/exit-gates/phase-5-e2e.json + 3-finding fold-in Changes in this commit: - package.json: 0.4.4 → 0.5.0 - CHANGELOG.md: "Unreleased" promoted to "## v0.5.0 — 2026-05-26" with comprehensive release notes spanning all Phase 5 deliverables; fresh empty "## Unreleased" added above for Phase 6 - CLAUDE.md release_kit.phase_rolling_mode: current_phase: Phase 5 → Phase 6 current_pre_release_identifier: "0.5.0-phase5" → "0.6.0-phase6" Tag push (git tag v0.5.0 && git push --tags) happens AFTER this merges to main. Tag push fires .github/workflows/release.yml which auto-creates the GitHub Release with notes derived from CHANGELOG.md. Tests: 756/756 pass locally; no production-code changes beyond version bumps. Authority: CLAUDE.md release_kit overlay (Iron Rule 5.5); ADR 0012 § Exit gate (all 9 items satisfied); Phase 5 D-day commitsv0.5.0 |
||
|
|
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
|
||
|
|
2b07a3bd1b |
test: D83 — Suite 38 (quota probe) + Suite 39 (dashboard smoke) (Phase 5) (#55)
* test: D83 — Suite 38 (quota probe) + Suite 39 (dashboard smoke) (Phase 5) ADR 0012 D83 row: comprehensive test coverage for the Phase 5 quota probe machinery (D80) and dashboard rendering (D82). ## Authorities cited - ADR 0012 D83 (D-day specification) - ADR 0013 Rules 2–6 (quota probe constraints being tested) - D80 PR #52 (anthropic.quotaStatus() + _probeOnce + _parseRateLimitHeaders) - D81 PR #53 (aggregateProviderQuota quota_v2 shape) - D82 PR #54 (dashboard.html Claude.ai-style restructure) - Schema pin: ~/.cc-rules/memory/learnings/anthropic_plan_usage_probe_schema_2026_05_26.md ## Test seams added to lib/providers/anthropic.mjs Minimal test seams added — 0 production-logic changes: - `_setQuotaUrlsForTest(apiUrl, oauthUrl)` — redirects probe HTTP to local mock server (auto-detects http: vs https: to switch transport module) - `_resetQuotaProbeStateForTest()` — resets cache + backoff + URLs + auth seam - `_resetQuotaStateOnlyForTest()` — resets cache + backoff only (URLs stay) - `_getQuotaProbeStateForTest()` — returns direct reference to quotaProbeState for test assertions and controlled state mutation - `_setQuotaAuthReadFnForTest(fn)` — injects a mock auth reader into quotaStatus() so tests are not affected by real ~/.claude/.credentials.json on the machine (fixes 38f: no-auth test was finding real keychain credentials) ## Suite 38 — 20 quota-probe unit tests (38a–38t) 38a: all 13 ratelimit-unified-* headers parsed correctly (numeric types, null defaults) 38b: missing overage-reset → overage_reset: null 38c: missing all three overage fields → all three null 38d: new 5h-status + 7d-status fields (NEW vs OCP 2026-04) parsed correctly 38e: quota_probe_enabled: false → null without HTTP call 38f: auth returns null → quotaStatus returns null without HTTP call 38g: 200 + all 13 headers → full shape with stale:false + backoff reset 38h: cache hit within 5min TTL → no second HTTP call (request count stays 1) 38i: expired cache (6min > 5min TTL) → fires fresh HTTP probe 38j: 401 with no refreshToken → null (idempotent-failure per ADR 0002 Amendment 8 §3) 38k: 429 + stale cache → returns stale cache with stale:true + last_fresh_at 38l: 429 + no cache → null + backoff scheduled (backoffUntil in the future) 38m: exponential backoff growth: 60s→120s→240s→cap at 3600s 38n: successful probe resets backoffMs to 60s + backoffUntil to 0 38o: schemaVersion from models-registry.json (falls back to constant) 38p: doctor quota_probe_reachable disabled → ok with opt-in advisory 38q: doctor probe enabled + probe succeeds → ok with utilization in message 38r: doctor probe enabled + fails + stale cache → warn 38s: doctor probe enabled + fails + no cache → fail with fix_commands 38t: doctor probe enabled + no creds → fail with human_steps only ## Suite 39 — 8 dashboard rendering smoke tests (39a–39h) 39a: /dashboard with owner token → 200 + text/html 39b: /dashboard without token → 401 39c: /dashboard with guest key → 401 (owner-only_block enforcement per ADR 0008 §8) 39d: HTML contains "Plan Usage" header (D82 panel) 39e: HTML contains ↻ Refresh button (D82 manual refresh) 39f: HTML contains QUOTA_POLL_INTERVAL_MS = 60000 (D82 1-min refresh) 39g: HTML contains visibilitychange / visibilityState guard (D82 ADR 0012) 39h: HTML contains quota_v2 consumer + legacy renderQuota fallback code ## Test count: 727 → 755 (+28) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test: D83 fold-in — fix 38j misleading name + add 38j2 positive-path + fix 38o ESM require Fresh-context reviewer (PR #55) flagged one blocking issue + 1 nit: BLOCKING (Q5) — 38j misleading name + uncovered 401→refresh→retry positive path. The original 38j test description said "401 → refresh succeeds → retry probe succeeds" but the test actually asserted the OPPOSITE behavior (401 with no refreshToken → null, no retry). The most important non-trivial control-flow branch in _probeOnce (anthropic.mjs:451-457 the refresh-and-retry on 401) was uncovered, and the misleading name actively masked the gap. Fix: - Renamed 38j to accurately describe what it tests: "401 with no refreshToken → null (idempotent-failure, no refresh attempt)". The test pins valuable behavior — idempotent-failure when refreshToken is absent — but now its name matches. - Added 38j2 (NEW) to exercise the actual positive-path: inject creds with refreshToken via _setQuotaAuthReadFnForTest, mock returns 401 on first API call + 200 with 13 headers on retry. Asserts: 2 API calls, 1 OAuth call, retry parsed full shape, OAuth body contains the injected refreshToken (Rule 1 credential reuse verification). NIT (Q5 dead assertion) — 38o tried to read models-registry.json via require('fs') which is undefined in ESM. The assert was silently never exercised. Replaced with the already-imported readFileSync from node:fs (added _readFileSync38 import). Also tightened: schemaVersion MUST equal the registry's quota_probe.schema_version (no longer conditional on "if expected" which was always falsy). Other reviewer nits deferred (non-blocking per reviewer): - N2: seam naming convention (_ vs __) — defer - N3: commit message "zero production changes" — note for v0.5.0 close - N4: _getQuotaProbeStateForTest returns mutable ref — defer - N5: test seam runtime guard — defer - N6: coverage gaps (concurrent probes, registry-missing fallback, network error path) — v1.x roadmap follow-ups - N7: Suite 39 stray error listener — defer (benign) Test count: 755 → 756 (+1 net, +2 new minus existing 38j rename). All 756 pass; 0 fail. Local re-run: stable. Authority: PR #55 review thread (D83 fresh-context opus reviewer) + ADR 0013 Rule 1 (credential reuse via refresh). 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> |
||
|
|
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>
|
||
|
|
704d4fc8a0 |
fix+docs+release(v0.4.4): D78 — olp-connect stale strings + CDN-safe README URL + pre-publish audit (#49)
* fix+docs+release(v0.4.4): D78 — olp-connect stale strings + README CDN-safe tag URL + repo public-flip pre-audit Patch release on top of v0.4.3. Three small issues caught when running olp-connect for real on MacBook (D77 client-install verification): ## G11: repo visibility flip Repo dtzp555-max/olp flipped PRIVATE -> PUBLIC during this session. Closes the original G11 finding (anonymous curl can't fetch raw URL from private repos). Pre-publish audit (per cc-rules pre-publish-audit.md checklist): - Identity scrub: 0 hits — no taodeng, no 老大, no /Users/.../ paths, no personal hostnames, no personal emails, no real LAN IPs (only RFC documentation placeholders 192.168.1.10 + 10.0.0.5) - Credential scrub: gitleaks 'no leaks found' — all olp_ matches are placeholder (olp_XXXX...) or test fixtures (olp_not-a-real-key-...) - Git history: maintainer accepted Option A (GitHub-account email already verified-public on profile; flip exposes nothing new) ## G11 mitigation: README curl URL CDN-cache-safe GitHub raw CDN serves a stale 404 for /main/<file> for ~5-15min after a private->public visibility flip (negative-cache TTL). Tag-pinned URLs bypass this because the tag ref was never queried while private. D78 makes README's primary olp-connect curl URL tag-pinned: bash <(curl -fsSL .../v0.4.4/bin/olp-connect) <ip> with /main/ listed as alternative for trusted-head users. ## G12: detect_openclaw claimed plugin not shipped bin/olp-connect's OpenClaw detection block said "The OpenClaw OLP plugin (D71-D73) is NOT YET SHIPPED" — but D71-D73 shipped olp-plugin/ at v0.4.0. Replaces stale text with real install instructions: git clone https://github.com/dtzp555-max/olp.git /tmp/olp-repo openclaw plugins install /tmp/olp-repo/olp-plugin # or symlink: ln -sf .../olp-plugin ~/.openclaw/extensions/olp Points at docs/integrations/openclaw.md for the full setup with dedicated bot apiKey + restart-gateway notes. ## G13: olp-connect self-version hardcoded literal Pre-D78 the script declared OLP_CONNECT_VERSION="0.4.0-phase4" as a hardcoded literal that nobody updated through v0.4.1 / v0.4.2 / v0.4.3. D78 derives the version at runtime from sibling package.json via python3. When invoked from a checked-out repo, version resolves to the actual value; when curl-piped (no on-disk package.json next to script), falls back to "unknown". bash bin/olp-connect --version # -> olp-connect 0.4.4 (automatic) ## Test count 717 (v0.4.3) -> 720 (v0.4.4). +3 D78 regression tests in Suite 36: - 36v: pins absence of NOT YET SHIPPED text + presence of real install path - 36w: pins runtime version derivation from package.json - 36x: pins README tag-pinned URL recommendation ## Authority - D77 MacBook client-install verification session (2026-05-26) - ~/.cc-rules/docs/guides/pre-publish-audit.md (the checklist that preceded the visibility flip) - Process learning: every README that includes a `curl raw-URL | bash` install pattern should pin to a release tag (not /main/) for CDN- cache resilience. ## Out of D78 scope (deferred) - F6 (doctor client-side limitation) — Phase 5 ADR amendment - D75 reviewer P2-1 (ADR 0004 per-hop schema) + P2-2 (defensive type assert) — non-blocking - scripts/migrate-from-ocp.mjs — Phase 7 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: D78 reviewer P2 fold-in — _resolve_version defensive guards (require /bin suffix + env-var path passthrough + nounset default) --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>v0.4.4 |
||
|
|
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>v0.4.3 |
||
|
|
6edf6e0b94 |
fix+release(v0.4.2): D75 — codex CLI v0.133.0 schema + per-hop model override (#47)
Patch release fixing 5 bugs caught by real-machine E2E testing on PI231 +
Mac mini (2026-05-26 session). Prior D-day reviewers + the post-v0.4.0
maintainer review all missed these because they reviewed against spec text
and against the local OLP install's cached codex CLI shape, not against a
fresh `npm install -g @openai/codex` on a remote operator host getting
v0.133.0 for the first time.
F1 — codex auth.json schema pin (lib/providers/codex.mjs readAuthArtifact)
Real codex CLI v0.133.0 nests the access token under `tokens.access_token`,
not at top-level access_token / token / accessToken. Pre-D75 readAuthArtifact
returned null → OLP reported "auth artifact missing" even for fully
logged-in users. Fix: prepend creds?.tokens?.access_token to the precedence
chain at both override + default branches. Legacy fields preserved as
fallback. Authority: codex CLI v0.133.0 on-disk auth.json shape verified
empirically on PI231 2026-05-26 E2E session.
F2 — codex spawn args + --skip-git-repo-check (lib/providers/codex.mjs irToCodex)
codex CLI v0.133.0 trusted-directory sandbox refuses with "Not inside a
trusted directory" outside git repos. OLP deploys typically outside a git
repo. Fix: add '--skip-git-repo-check' to args before '--model'. Authority:
codex CLI v0.133.0 reference (`codex exec --help` documents the flag).
F3 — codex NDJSON event shape pin (lib/providers/codex.mjs codexChunkToIR)
Real v0.133.0 stream: thread.started → turn.started → item.completed
(item.type='agent_message', item.text=<response>) → turn.completed.
D6 defensive parser only recognised top-level content/delta/text +
type:'stop'/done:true → every chunk silently dropped → response body had
content: null. Fix: add three new recognisers (item.completed → delta;
turn.completed → stop; turn.failed → error) before the legacy fallback
chain. Legacy recognisers preserved for backward/forward compat.
F4 — `olp status` reads body.stats.cache.size, not body.cache.entries
(bin/olp.mjs cmdStatus). Server payload nests stats under stats.cache;
CacheStore.stats() exposes {hits, misses, size, inflightCount} — there is
no `entries` field. D74 P2-3 fixed cmdUsage + cmdCache for the same bug
class but missed cmdStatus.
F7 — per-hop chain `model` overrides IR model in provider.spawn()
(server.mjs executeHopFn + streaming sourceFactory). Pre-D75 executeHopFn
used hopModel for cache key + audit ctx but passed the original irReq
(with irReq.model = user's request) to provider.spawn(). Chain config
[{anthropic, claude-X}, {openai, gpt-5.5}] would always spawn BOTH plugins
with --model claude-X — openai rejected the unknown model and the chain
died. This broke the core OLP value prop (cross-provider fallback with
provider-appropriate model substitution). Fix: build per-hop IR variant
with { ...irReq, model: hopModel } and pass to spawn. Conditional skips
clone when hopModel === irReq.model. Applied to BOTH buffered path AND
streaming path. Authority: ADR 0004 § Chain advancement step 1 (per-hop
config supplies provider AND model — contract always specified, code
didn't complete it).
Out of scope (deferred to Phase 5):
- F5 (server bind / OLP_BIND env) — needs anonymous-key trust review
- F6 (doctor client-vs-server-side limit) — needs trigger-taxonomy ADR
Test count: 704 → 714 (+10 Suite 36 D75 regression tests: 36i–36r).
Files touched: lib/providers/codex.mjs, bin/olp.mjs, server.mjs,
test-features.mjs, package.json, CHANGELOG.md.
Phase 5 process learning: every provider plugin D-day must include a
real-CLI E2E on a remote operator host before merging — not on the
maintainer workstation (which may have an older CLI cached from a prior
install). D6/D7 codex E2E was deferred and that deferral compounded across
3 layers. F7 reinforces a separate lesson: when a function signature takes
(provider, model, ir), reviewers must check that `model` is consumed
everywhere downstream — not just at the call site they happened to look at.
Authority: ADR 0002 (provider contract — codex plugin), ADR 0004 (fallback
engine — per-hop model contract), lib/providers/codex.mjs D6 assumption
A2/A3/A4 docstrings (which all said "D7 will pin" and D7 never did); codex
CLI v0.133.0 on-disk schema + `codex exec --help` output verified
empirically on PI231 (2026-05-26 E2E session); Iron Rule 第二律
evidence-over-should-work; CLAUDE.md release_kit.phase_rolling_mode
cross-Phase discipline ("hotfix to a shipped Phase N deliverable → bump
patch, tag, release before next push").
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.4.2
|
||
|
|
f3716a19fd |
fix+release(v0.4.1): D74 post-Phase-4 hotfix batch — 5 maintainer-review findings (#46)
* fix+release(v0.4.1): D74 post-Phase-4 hotfix batch — maintainer-review findings Patch release fixing 5 issues caught by maintainer post-v0.4.0 independent review (main / v0.4.0 / commitv0.4.1 |
||
|
|
ee4d9459aa |
release(phase-4-close): v0.4.0 — Operator + Client UX (D60 → D73) (#45)
Closes Phase 4. Maintainer triggered the close per CLAUDE.md
release_kit.phase_rolling_mode.phase_close_trigger ("explicit maintainer
action — not automated") this session, 2026-05-26.
## Scope
5 D-day groups, ~13 D-days, all merged to main with fresh-context opus
reviewer per Iron Rule 10:
- D60 (PR #40) — Phase 4 charter (ADR 0010) + default port 3456→4567
- D61-D63 (PR #41) — SSE heartbeat + recentErrors[20] + /v0/management/status
- D64-D67 (PR #42) — olp Node CLI + olp doctor + ADR 0002 Amendment 7
- D68-D70 (PR #43) — olp-connect + /health.anonymousKey + ADR 0011
- D71-D73 (PR #44) — olp-plugin/ Telegram+Discord + 6 IDE docs + README
Test count arc: 623 (v0.3.2) → 696 (v0.4.0). +73 tests.
## Strategic decision recorded in ADR 0010
Phase 4 explicitly DEFERS /v1/messages (Anthropic-shape entry surface).
Re-open strictly gated on ADR 0009 P0 success AND maintainer-named
family CC user. README posture: Claude Code listed as NOT supported as
an OLP client; recommended alternative "Cline + OLP" (same fallback
chain available, better cross-provider compatibility because OpenAI
tool schema is the multi-provider lingua franca; Anthropic tool_use /
cache_control / computer_use / thinking blocks lack clean cross-provider
mapping).
## What this commit actually changes
- package.json: 0.3.2 → 0.4.0
- CHANGELOG.md: Unreleased promoted to "## v0.4.0 — 2026-05-26" with
full D60-D73 entries (test counts, authority chains, reviewer P2
fold-ins documented). New Unreleased: "(empty — Phase 5 entries land
here once Phase 5 opens)"
- CLAUDE.md release_kit.phase_rolling_mode:
current_phase: Phase 4 → Phase 5
current_pre_release_identifier: "0.4.0-phase4" → "0.5.0-phase5"
## Phase 4 close checklist (per ADR 0010 § Exit gate)
- [x] All 5 D-day groups landed on main with reviewer APPROVE
- [x] CI green on every D-day merge + this release commit head
- [x] package.json bumped 0.3.2 → 0.4.0
- [x] CHANGELOG Unreleased promoted to v0.4.0 — 2026-05-26
- [x] CLAUDE.md release_kit phase_rolling_mode advanced
- [x] README § IDE Setup + § Telegram/Discord Usage + § Operator CLI surfaces
- [x] ADR 0010 + ADR 0011 + ADR 0002 Amendment 7 on disk
- [ ] Tag v0.4.0 pushed (next lifecycle step)
- [ ] release.yml triggers + GitHub Release auto-created on tag push
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.4.0
|
||
|
|
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> |
||
|
|
e6701ff698 |
feat+test: D61+D62+D63 — SSE heartbeat + recentErrors[20] + /v0/management/status (#41)
* feat+test: D61+D62+D63 — SSE heartbeat + recentErrors[20] ring + /v0/management/status
First substantive Phase 4 implementation. Bundle of 3 D-days per Iron Rule 11
IDR rationale: all three converge on the same observability surface (status
endpoint reads recentErrors + provider stats + heartbeat-related counters;
heartbeat shares the streaming branch with recentErrors emission; all live
in server.mjs).
## D61 — SSE heartbeat
Ported from OCP server.mjs:660-685 startHeartbeat() with the OCP db11105
"eager-headers-post-spawn" fix folded in from day one.
- New config field streaming.heartbeat_interval_ms in ~/.olp/config.json
(default 0 = disabled, matching OCP's safe default)
- When enabled (>0), streaming branch emits `: keepalive\n\n` SSE comment
every interval_ms ms during silent windows
- Timer resets on every real chunk written
- Cleanup on stream end / error / abort / client disconnect
- SSE_DEFAULT_HEADERS constant centralizes Content-Type / Cache-Control /
Connection / X-Accel-Buffering: no (the last was the missing OCP lesson
that broke long streams behind nginx 60s idle)
- Per-attached-client lifecycle (each tee output gets its own timer)
- One heartbeat_active log per stream on first fire; no per-fire log noise
Note: heartbeat NOT wired in the buffered-replay streaming branch because
that branch writes the burst synchronously into the socket buffer — no
silent windows exist there. Inline comment notes this.
## D62 — recentErrors[20] ring buffer
Module-scope bounded ring, surfaced via /v0/management/status at D63.
- _pushError({ error, provider, path, statusCode }) entry shape:
{ time (ISO8601), message (200-char cap), code, provider, path, status_code }
- Filter: only ProviderError OR statusCode >= 500 (401/403 brute-force noise
excluded — protects ring from auth-probe flooding)
- Path sanitization via .replace(/\/[\w./-]+/g, '[path]') ported from
OCP server.mjs:1395 — strips internal paths before they leave the proxy
- Wired into 5 server-side error paths: chain-exhausted, pre-first-chunk
streaming error, mid-stream IR error chunk, fallback-engine programming
error, router-level unhandled error
- In-memory only (not persisted across restart) per OCP precedent
- Test seam __clearRecentErrors / __snapshotRecentErrors
## D63 — /v0/management/status combined endpoint
OCP /status equivalent, OLP-namespaced per stricter discipline.
- New route GET /v0/management/status, owner-only_block (matches ADR 0007
§ 7 + ADR 0008 Phase 3 management endpoint gating pattern)
- Returns { ok, version, uptime_ms, uptime_human, started_at,
providers: {enabled, available, status},
stats: {total_requests, active_requests, cache: cacheStore.stats()},
recent_errors: [<ring>], generated_at }
- _totalRequests + _activeRequests module-scope counters incremented at top
of handleChatCompletions; _activeRequests decremented in res.on('close'/
'finish') with idempotent guard
- Counters NOT exposed via /health (owner-trim intentional there); only via
/v0/management/status (owner-only_block)
- Reuses _runOwnerOnlyManagementEndpoint helper from D50 Phase 3 work
## Test count
623 → 636 (+13 D61-D63 tests across Suites 29, 30, 31). All 636 pass locally.
## Scope discipline
server.mjs + test-features.mjs + lib/fallback/engine.mjs only (engine.mjs
touched only to extend loadFallbackConfigSync to surface the new
streaming.heartbeat_interval_ms field; no engine behavior change).
Untouched: provider plugins, IR, cache layer, dashboard.html, audit-query,
README, CHANGELOG, package.json. /health payload unchanged. None of the
existing 623 tests regressed.
## Authority
- ADR 0010 § Phase 4 D-day plan (D61-D63 line)
- OCP server.mjs:660-685 (startHeartbeat reference impl)
- OCP commit db11105 (eager-headers-post-spawn fix)
- OCP server.mjs:301, 354-358 (recentErrors ring pattern)
- OCP server.mjs:1151-1188 (/status combined endpoint pattern)
- OCP server.mjs:1395 (error path sanitization)
- ADR 0007 § 7 (identity classes — owner-only_block gating)
- ADR 0008 (management endpoints pattern reused)
- 2026-05-26 brainstorm (Top 5 OCP inheritance candidates, items 1 + 4)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: D61-D63 reviewer P2 fold-in — explicit 401/403 filter + null status_code for post-headers
Reviewer APPROVE WITH MINOR — 0 P0/P1, 2 P2 (both about _pushError filter
clarity / defense-in-depth).
P2-1 — explicit 401/403 reject at function level. The current call sites
never invoke _pushError from authenticate() failures (call-site discipline),
but a future contributor passing a ProviderError tagged statusCode=401
would slip past the isProviderError branch and flood the ring under
brute force. Added explicit `if (statusCode === 401 || statusCode === 403)
return;` as defense-in-depth.
P2-2 — pass `statusCode: null` for the two streaming-error-after-first-
chunk _pushError sites instead of `statusCode: 200`. Headers are already
sent so any numeric status is misleading; null + record-by-error-code is
the explicit intent. Avoids a future filter-refactor accidentally
dropping these entries because they look like 200-OK.
Test count unchanged 636/636 pass (filter behavior identical from
call-site perspective; the changes are defensive + intent-clarifying).
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> |
||
|
|
ba69a3c13b |
release(v0.3.2): patch release — post-Phase-3 cleanup batch #2 (streaming-path singleflight + TOCTOU close, D57+D58+D59) (#39)
Patch release closing v1.x roadmap #1 end-to-end. ADR 0005 Amendment 8 §§1-14 implemented across 3 D-days (PR #36, #37, #38, all merged): - D57 (PR #36): cache layer — cacheStore.getOrComputeStreaming(...) + tee fan-out + late-joiner replay + per-client backpressure + AbortController propagation. New STREAM_BACKPRESSURE error code (not a hard trigger). Suite 27 = 12 unit tests. - D58 (PR #37): server wiring — streaming branch swap; tryAcquireSpawn moved into sourceFactory; X-OLP-Streaming-Inflight: source|attached header; cache_status: 'streaming_attached' audit value; audit-query gauge reconciliation. Suite 28 = 8 HTTP integration tests. - D59 (PR #38): docs polish — README § Known limitations inverted; v1.x roadmap #1 closed; issue #16 closed. Test count: 603 (v0.3.1) → 623 (v0.3.2). +20 tests across the SF arc. Patch-release classification per release_kit.phase_rolling_mode + maintainer release-cut decision (this session, 2026-05-25): the new wire surface (X-OLP-Streaming-Inflight header + streaming_attached cache_status) is semver-wise a minor bump, but this is roadmap-cleanup work — NOT Phase 4 product scope. The reserved 0.4.0 identifier stays for the formal Phase 4 close. v0.3.2 ships as a patch under the Phase 4 pre-release banner. Authority: ADR 0005 Amendment 8 (design ratified at D42 2026-05-25; implementation gated on maintainer "go" — fired 2026-05-25 post-v0.3.1). docs/v1x-roadmap.md #1 (closed). GitHub issue #16 (closed). ADR 0002 Amendment 6 (D38 tryAcquireSpawn/releaseSpawn semantics, now invoked from sourceFactory closure). Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>v0.3.2 |
||
|
|
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> |
||
|
|
9b66326e72 |
feat+test: D58 — server.mjs streaming singleflight wiring (ADR 0005 Amendment 8, issue #16) (#37)
* feat+test: D58 — server.mjs streaming singleflight wiring (ADR 0005 Amendment 8 §§7,11,12, issue #16) Second of three D-days for v1.x roadmap #1. D57 landed the cache-layer primitive (cacheStore.getOrComputeStreaming); D58 wires it into the streaming branch of server.mjs and adds the X-OLP-Streaming-Inflight header. D59 closes issue #16 + polishes README known-limitations. ## What server.mjs — replaced the streaming branch (formerly the peek+spawn pattern at lines 1138-1327) with cacheStore.getOrComputeStreaming(...): - tryAcquireSpawn moves INSIDE sourceFactory closure (§7). Only first caller acquires; attached joiners share the slot. releaseSpawn lives in the source generator try/finally so it fires once on source completion / error / abort. - CONCURRENCY_LIMIT thrown by the factory triggers fallthrough to the buffered path (preserving today's behaviour); any other pre-stream factory error surfaces a 502. - X-OLP-Streaming-Inflight: source | attached header per §11 (cache_hit role omits — X-OLP-Cache: hit already says it). The §11 'solo' value is deferred to a future amendment — observable only post-stream via the streaming_inflight_source_done log event's attached_count. - auditCtx.cache_status: 'miss' for source, 'streaming_attached' for joiners, 'hit' for the TTL-race cache_hit branch. - res.on('close', () => stream.return?.()) propagates client disconnect into the tee's attachedClients accounting (§9). Note: Node 25 emits 'close' on ServerResponse, NOT on IncomingMessage — empirically verified in test 28g. - Cache writes now happen inside the cache layer's tee task on source completion (§4). Server still issues cacheStore.delete on stop-less exhaustion to preserve D16 truncated-not-cached invariant — the cache layer is IR-agnostic and writes accumulatedChunks unconditionally; the IR-aware server deletes the entry if no stop chunk was observed. - The pre-cache-store-acquire and matching releaseSpawn-on-503 branches are gone — they were vestigial once the factory owns acquire+release. lib/audit.mjs — JSDoc cache_status enum extended with 'streaming_attached'. Free-form string at the wire (no schema validator on append); the JSDoc is the source of truth for the consumer enum. test-features.mjs Suite 28 — 7 HTTP integration tests: - 28a single SSE request (source role + cache populated + second request → X-OLP-Cache: hit) - 28b 2 concurrent identical SSE → one spawn, source + attached roles, identical chunk sequences delivered - 28c TOCTOU regression — pre-populated cache + 2 concurrent → both hit buffered replay path, no streaming branch entry, no X-OLP-Streaming-Inflight header - 28d mid-stream join — late joiner receives accumulated burst + live tail - 28f one-of-N disconnect — source NOT aborted; survivor completes - 28g ALL clients disconnect → source aborted; no cache write; subsequent request gets fresh source spawn - 28h CONCURRENCY_LIMIT fallthrough — factory throws at maxConcurrent=1; buffered path's chain-exhausted 502 surfaces (28e backpressure deferred to Suite 27g unit-level coverage.) ## Scope server.mjs + lib/audit.mjs + test-features.mjs. Untouched: cache/store.mjs (D57 frozen), provider plugins, fallback engine, IR. CHANGELOG and package.json bump fires at D59 close. ## Authority - docs/adr/0005-cache-cross-provider.md Amendment 8 §§7, 8, 9, 11, 12 - docs/v1x-roadmap.md #1 - GitHub issue #16 (TOCTOU window; closed in D59) - ADR 0002 Amendment 6 (D38 tryAcquireSpawn/releaseSpawn semantics that §7 now invokes inside the sourceFactory closure) ## Test count 615 → 622 (+7 D58 integration tests). Local: 622/622 pass. ## Iron Rule 10 follow-up notes for the reviewer - res.on('close') vs req.on('close'): switched to res after empirical verification (28g fails on req under Node 25). Comment in code. - Cache-layer write + server-layer delete for stop-less exhaustion is cosmetically inconsistent with streaming_inflight_source_done's cache_written: true log. Functionally correct; flagged for future amendment. - 'solo' header value deferred — would need trailer mechanics or post-stream emission. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: D58 reviewer follow-ups — P2-1 (audit-query gauge drift) + P2-2 (stop-less HTTP test) Fold-in for D58 PR #37 fresh-context opus reviewer findings (APPROVE WITH MINOR — 0 P0/P1, 4 P2). P2-3 (`isFirst` unused) and P2-4 (`solo` not emitted) are design-acceptable per ADR 0005 Amendment 8 §11 and left as-is. P2-1 — `lib/audit-query.mjs` gauge reconciliation. `aggregateRequests` and `cacheHitRateWindow` previously counted `streaming_attached` rows in `total` / `pe.total` without contributing to hit/miss/bypass numerators, breaking the invariant that the cache_status breakdown should sum to the total. Added an explicit `streaming_attached` field to both the global return shape and the `by_provider` shape; the counter is excluded from `hit_rate` numerator AND denominator (joiners did not hit a literal cache so they don't belong in either side of the ratio). Test count is unchanged for D49 suites — they only assert presence + non-negative + reconciliation invariants that the new field preserves; if a test asserted exact value equality on a fixture with NO streaming_attached rows, the new field defaults to 0 and the assertion still passes. P2-2 — Suite 28 stop-less HTTP coverage gap. Test 28i fires an SSE request to a fake provider whose source generator returns WITHOUT a {type:"stop"} chunk; asserts (a) the synthetic truncation marker appears in the body (D26 F19 in-band signal), (b) [DONE] terminator follows, (c) a subsequent identical request triggers a fresh spawn (cache was NOT populated by the truncated stream). Pins the D58 `cacheStore.delete` path at server.mjs:1344-1346 end-to-end. 622 → 623 (+1 D58 follow-up test). 623/623 pass locally. 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> |