mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
v0.4.2
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
408d5a839a |
feat+test+docs: D52 — daily audit rotation (lib/audit.mjs + bin/olp-audit-rotate.mjs) (#29)
Fifth Phase 3 D-day. Adds daily UTC-aware rotation to lib/audit.mjs
per ADR 0008 § 5 + ships an external cron tool. Rotation is
SYNCHRONOUS at v0.3.0 — synchronous design eliminates the race that
an async wrapper would create between date-change-detection and the
append.
lib/audit.mjs EXTENSIONS:
- New _maybeRotateAudit({ olpHome, logEvent }) (sync): probes live
audit.ndjson; if it holds events from past UTC date, renames it
to audit-YYYY-MM-DD.ndjson. Idempotent. If target file exists
(cron beat in-server check), logs warn + skips per § 5.3.
- appendAuditEvent extended: cheap fast-path date check via module-
cached _lastSeenUtcDate. On date change, calls _maybeRotateAudit
synchronously BEFORE appendFileSync — so old-date events land in
the rotated file and new-date events land in the fresh live file.
No event straddles the boundary.
- Why SYNCHRONOUS: an async wrapper would let the sync
appendFileSync race the not-yet-completed renameSync, landing
today's event in the about-to-be-renamed file. Sync rotation is
the only correct ordering at the append-fired-from-many-routes
scale OLP runs. (Test 26b-1 caught this during local run; the
initial async-wrapper implementation failed because the live
file at assertion time didn't exist.)
- New exports: _maybeRotateAudit (sync), getAuditRotateCount,
getAuditRotateFailCount, __resetAuditRotateState,
__setLastSeenUtcDateForTesting.
- First-event-date discovery: when probing the live file's date,
reads only the first ndjson line + parses its ts. Falls back to
file mtime if events absent (corrupt/empty edge).
bin/olp-audit-rotate.mjs (~95 lines): external cron tool per § 5.2.
Calls _maybeRotateAudit once + reports outcome. Exit codes 0
(success or no-op), 1 (bad usage), 2 (rotation failed). Installed
via package.json bin so `npx olp-audit-rotate [--olp-home=<path>]`
works. Example cron line in file header.
CONCURRENT-SAFETY (§ 5.3):
In-process sequential appends after the first date-change detection
short-circuit via the updated _lastSeenUtcDate cache → exactly 1
rename even under N sequential appends. Cross-process (cron + server)
coexistence handled by the "target already exists → skip + warn"
branch.
TESTS — Suite 26, +12 (588 → 600):
26a-1..5: _maybeRotateAudit (no live file / today already /
yesterday→rotate / idempotent re-call / cron-race target-exists
warn)
26b-1: appendAuditEvent past UTC date change triggers sync rotation
+ append lands in fresh live file
26c-1: 10 sequential appendAuditEvent across date change → exactly
1 rotation + all 10 events in new live file
26d-1..4: bin/olp-audit-rotate.mjs CLI (--help / no-live-file /
yesterday-file-rotates / unknown-flag exit 1)
26e-1: rotated files queryable via lib/audit-query.mjs
discoverAuditFiles + readAuditWindow cross-file read
package.json: bin.olp-audit-rotate + scripts.olp-audit-rotate entries
added.
DOCUMENTATION:
- AGENTS.md: lib/audit.mjs marker promoted ✅ (D45 append + D52
rotation both shipped); new bin/olp-audit-rotate.mjs entry.
- CHANGELOG.md: D52 entry under Unreleased per release_kit overlay.
NOT IN D52:
- tried_providers schema fix (D53; D45 P2 deferral)
- E2E + docs polish (D54)
- Phase 3 close → v0.3.0 (D55; maintainer-triggered)
Test count: 588 → 600 (+12). Verified locally via npm test.
AUTHORITY:
- ADR 0008 § 5.1 (first-append-after-UTC-midnight trigger).
- ADR 0008 § 5.2 (external cron alternative).
- ADR 0008 § 5.3 (concurrent-rotation safety + cron-coexistence).
- ADR 0008 § 5.4 (renamed-file query path consumed by D49 lib/
audit-query.mjs).
- CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased.
- Standing autopilot grant.
ALIGNMENT.md scope check: extends lib/audit.mjs (a Phase 2 internal
module) + adds new bin/ CLI + small package.json bin/scripts entries.
No provider plugin / entry surface / IR change.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
251b578114 |
feat+test+docs: D51 — dashboard.html full multi-panel UI (Phase 3) (#28)
* feat+test+docs: D51 — dashboard.html full multi-panel UI (Phase 3)
Fourth Phase 3 D-day. Replaces the D50 dashboard.html placeholder with
the full 4-panel UI per ADR 0008 § 6. Vanilla HTML + JS + fetch — no
build step, no framework, no CDN (Lane 1 = A). 30s page poll with
document.visibilityState pause/resume (Lane 4 = A).
4 PANELS (all rendered from /v0/management/dashboard-data — single
backing endpoint per Lane 2 in-memory query model):
Panel 1 — Per-provider quota
Table: { Provider | Available | Status }. Null available → "n/a"
pill; provider.quotaStatus() error → red status pill (graceful
degradation per ADR § 9).
Panel 2 — Last 24h: request count + cache hit + fallback rate
Per-provider row: { Requests | Cache hit % | Fallback rate % }.
Cache hit from cache_hit_24h.by_provider[p].hit_rate; fallback
rate computed from window_24h.by_provider[p].fallback_count/count.
Panel 3 — Request count last 30 days (SVG sparkline)
Vanilla SVG bar chart with <title> tooltips showing per-day per-
provider breakdown. Y-axis: requests per day (max-scaled). X-axis:
30 daily UTC buckets.
Panel 4 — Top fallback chains (last 24h)
Numbered table: { # | Chain (monospace, arrow-joined) | Count |
First seen | Last seen }.
POLL + VISIBILITYCHANGE PAUSE (ADR 0008 § 6.5):
- setInterval(refresh, 30000) after initial fetch.
- document.addEventListener('visibilitychange') → stopPolling() on
hidden / refresh()+startPolling() on visible.
- Prevents 2880 background polls/day per owner when tab hidden.
ERROR HANDLING:
- 401 from dashboard-data → in-page error banner explains owner-tier
requirement + suggests SSH-tunnel + header-injection workaround.
- Other HTTP errors → generic "HTTP <code>" banner; console.warn
for operator debugging.
- Per-panel empty states ("Loading…", "No requests in window.",
"No fallback chains triggered.").
CRITICAL CORRECTNESS INVARIANTS (ADR 0008 § 6 + Lane 1 = A):
- No <script src> — entire JS inline (Suite 25d asserts).
- No <link rel="stylesheet" href> — all CSS in <style> (25d).
- Only one backing endpoint hit: /v0/management/dashboard-data
(Suite 25e asserts).
- 401 path keeps panels in last-good state rather than clearing —
operator sees the error banner + can debug.
TESTS — Suite 25, +6 (582 → 588):
25a: owner /dashboard response contains all 4 panel container IDs
25b: JS declares POLL_INTERVAL_MS = 30000 + setInterval/clearInterval
25c: visibilitychange listener + document.visibilityState check
25d: NO external script src / NO external stylesheet href (Lane 1 = A
pinning)
25e: dashboard JS fetches /v0/management/dashboard-data only
25f: 401 in-page error banner mentions owner-tier guidance
MANUAL SMOKE (ADR 0008 § 10 #12):
Dashboard renders without console errors in a real browser when
served by a running OLP instance + owner-tier Bearer via SSH-tunnel
+ header-injection extension. Not automated at Phase 3.
DOCUMENTATION:
- AGENTS.md: dashboard.html marker promoted 🟡 D50 placeholder → ✅
D51 full UI.
- CHANGELOG.md: D51 entry under Unreleased per release_kit overlay.
NOT IN D51:
- Daily audit rotation (D52)
- tried_providers schema fix (D53; D45 P2 deferral)
- Phase 3 close (D55; v0.3.0; maintainer-triggered)
Test count: 582 → 588 (+6). Verified locally via npm test.
AUTHORITY:
- ADR 0008 § 6 (panels + refresh + localhost) + § 6.5 (poll +
visibilityState pause) + Lane 1 = A (no build step) + Lane 4 = A
(30s poll) + Lane 5 = B (full 4-panel scope).
- ADR 0008 § 9 (graceful degradation surfaced in Panel 1).
- ADR 0008 § 10 #12 (HTML smoke criterion satisfied at server-side
level).
- CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased.
- Standing autopilot grant.
ALIGNMENT.md scope check: this PR replaces an existing entry-surface
static file (dashboard.html). Per Rule 5: management surface, not
OpenAI-spec-compatible — outside /v1/* spec scope. No code change in
server.mjs / lib/ / providers / IR / models-registry.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: D51 fold-in — dashboard.html cosmetic polish (opus P3)
Fresh-context opus reviewer (PR #28) flagged 4 P3 cosmetic findings;
addressing the 2 trivial ones inline. The other 2 (visibilitychange
race + defensive date null-check) are negligible at family-scale per
reviewer; deferred to Phase 4 if UX feedback warrants.
- Line ~193: deleted orphan empty <text> SVG element (no textContent,
rendered nothing — debris from initial pass).
- Line ~200 comment: was "Date labels (first / mid / last)" but only
first + last rendered. Tightened to clarify intent + note mid label
deferred to Phase 4 if needed.
No behavior change. 588/588 tests pass.
Authority: PR #28 fresh-context opus reviewer P3 findings (2 of 4
addressed; remaining 2 documented as negligible).
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>
|
||
|
|
f9f2eaa059 |
feat+test+docs: D50 — server.mjs management endpoints (Phase 3 dashboard wire-up) (#27)
* feat+test+docs: D50 — server.mjs management endpoints (Phase 3 dashboard wire-up)
Third Phase 3 D-day. Wires the D49 lib/audit-query.mjs aggregate query
layer into 4 owner_only_block HTTP endpoints per ADR 0008 §§ 7-8.
Ships a placeholder dashboard.html at repo root (D51 lands the full
multi-panel UI). All endpoints follow the Phase 2 / D45 auth + audit
+ touchLastUsed pattern.
4 NEW ENDPOINTS — all owner_only_block per ADR 0008 § 8:
GET /dashboard
Serves dashboard.html (text/html). D50 stub explains state +
lists backing endpoints. D51 replaces with full UI.
GET /v0/management/dashboard-data
Full aggregate per § 7.2:
{ generated_at, window_24h, cache_hit_24h, quota,
spend_trend_30d, top_fallback_chains_24h, cache_stats }
GET /v0/management/quota
Quota subset only (per-provider provider.quotaStatus + error
capture per § 9 graceful degradation).
GET /cache/stats
Live in-memory cacheStore.stats() with generated_at wrapper.
HELPER:
_runOwnerOnlyManagementEndpoint(req, res, method, path, inner)
Factors common auth + audit ctx + owner-block + res.on('finish')
wire. inner is async (req, res, olpIdentity, auditCtx) → void.
Eliminates 4× boilerplate.
OWNER_ONLY_BLOCK MODE (ADR 0008 § 8 D48-fold-in):
authenticate → if owner_tier !== 'owner' → 401 owner_required.
Distinct from owner_only_trim (Phase 2 /health pattern). Anonymous
identity (when allow_anonymous: true) REACHES the handler and is
401'd by the owner check (Suite 24c). Allow_anonymous: false + no
header → 401 auth_required at middleware (Suite 24d).
PROVIDER QUOTASTATUS ERROR CAPTURE:
Dashboard-data + quota endpoints catch per-provider throws and
surface { provider, error, available: null } so one bad provider
doesn't fail the whole panel (ADR 0008 § 9 graceful degradation).
DASHBOARD.HTML PLACEHOLDER (~50 lines at repo root):
Explains D50 state, lists backing endpoints with curl example.
Cached in memory at first /dashboard request via _loadDashboardHtml
with module-scope _dashboardHtmlCache; falls back to in-memory stub
if file missing (defensive for test imports from non-repo cwd).
AUDIT ON MANAGEMENT ENDPOINTS (ADR 0008 § 7.5):
Every management request appends audit row including 401 paths
(verified by Suite 24j). Touch wire skips anonymous + env-owner
identities (matches Phase 2 pattern).
TESTS — Suite 24, +11 (571 → 582):
24a-d: /dashboard owner_only_block matrix (owner 200 / guest 401
/ anonymous-with-allow_anonymous=true 401 / no-auth-with-
allow_anonymous=false 401)
24e: dashboard-data owner → 200 JSON with all § 7.2 fields
(asserts spend_trend_30d.length === 30)
24f: dashboard-data guest → 401 owner_required
24g: quota owner → 200 JSON with quota array
24h: cache/stats owner → 200 JSON shape
24h-401: cache/stats guest → 401
24i: successful dashboard-data appends audit row with status 200
+ key_id + correct path
24j: 401 (guest blocked) dashboard-data appends audit row with
error_code: 'owner_required' + owner_tier: 'guest'
DOCUMENTATION:
- AGENTS.md: dashboard.html new entry (D50 placeholder); lib/audit-
query.mjs marker note unchanged.
- CHANGELOG.md: D50 entry under Unreleased per release_kit overlay.
NOT IN D50 scope:
- Full dashboard UI (D51 — replaces dashboard.html with the real
4-panel layout + 30s poll JS)
- Daily audit rotation (D52)
- tried_providers schema fix (D53)
- Phase 3 close (D55; v0.3.0; maintainer-triggered)
Test count: 571 → 582 (+11). Verified locally via npm test.
AUTHORITY:
- ADR 0008 § 7 (endpoint definitions) + § 8 (owner_only_block
mode) + § 9 (graceful degradation) + § 7.5 (audit on management
endpoints).
- ADR 0007 § 7 (auth model reused).
- ADR 0002 § Provider contract (quotaStatus).
- ADR 0005 (cacheStore.stats source of truth).
- CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased.
- Standing autopilot grant.
ALIGNMENT.md scope check: this PR adds 4 new entry-surface endpoints
under owner-only_block gating + a new lib/audit-query consumer surface.
Per Rule 5: management endpoints are owner-only operational surface,
not OpenAI-spec-compatible — they exist outside the /v1/chat/completions
+ /v1/models spec scope. No provider plugin / IR change.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: D50 fold-in — AGENTS.md dashboard.html duplicate (opus P3)
Fresh-context opus reviewer (PR #27) flagged a duplicate dashboard.html
entry: my D50 addition was added directly above a stale
"Planned (Phase 6) — not yet authored" line that should have been
removed. The file contradicted itself.
Fix: merge into single entry — keep the original line phrasing and
attach the D50 status update.
No code change, no test change.
Authority: PR #27 fresh-context opus reviewer P3 finding.
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>
|
||
|
|
686794e316 |
feat+test+docs: D49 — lib/audit-query.mjs (Phase 3 audit aggregate query layer) (#26)
Second Phase 3 D-day. Implements ADR 0008 § 4 query API. Pure in-memory
ndjson scan; cross-file walk over audit.ndjson (live) +
audit-YYYY-MM-DD.ndjson (rotated). No server.mjs integration in this
D-day (D50 wires the consuming endpoints).
NEW lib/audit-query.mjs (~370 lines): 5 public API functions per
ADR 0008 § 4.1:
- discoverAuditFiles({ olpHome }): filesystem scan; returns
Map<date|'live', path>.
- readAuditWindow({ startMs, endMs, olpHome, logEvent }): generator
over events in half-open window [startMs, endMs). Walks rotated
date files + live file. Skips malformed lines + logs warn.
- aggregateRequests({ windowMs, olpHome }): counts + status buckets
+ by_provider + by_owner_tier + by_path + median/p95 latency over
rolling window.
- topFallbackChains({ windowMs, limit, olpHome }): top-N chains by
trigger count from events with fallback_hops > 0. Tied-count
tiebreak: ascending first_seen.
- spendTrendDaily({ days, olpHome }): daily series ending today
with sparse-fill for zero-request days. Per-day request_count +
median latency + by_provider breakdown.
- cacheHitRateWindow({ windowMs, olpHome }): audit-derived cache
hit rate (bypass excluded from denominator); per-provider + overall.
PII discipline (ADR 0008 § 4.3): every aggregate function relays only
schema fields; never message content. Suite 23g actively asserts the
absence of content/message/messages/prompt/response/body keys in every
aggregate output.
Cross-file walk semantics (ADR 0008 § 4.2): half-open window
[startMs, endMs); date-range computed once from window bounds; each
rotated date file checked; live audit.ndjson always checked (it
covers today regardless of whether the window endpoint is past
midnight).
spendTrendDaily calendar-date semantics:
days: N returns "last N calendar UTC dates ending today" — NOT
"events within a rolling N*86400-ms window" (which would span N+1
distinct UTC dates and produce off-by-one buckets at non-midnight
call times). Computed via:
for (let i = days-1; i >= 0; i--)
dates.push(_utcDateFromMs(now - i*86400*1000));
cacheHitRateWindow denominator: hit_rate = hit / (hit + miss).
Bypass is intentional non-cacheable (Anthropic cache_control marker),
NOT a cache miss; excluding it from the denominator gives a clean
cache-effectiveness signal.
TESTS — Suite 23, +27 (544 → 571):
23a-1..4: discoverAuditFiles (empty dir / live only / live+rotated /
non-audit files ignored)
23b-1..6: readAuditWindow (all-coverage / single-day / half-open
exclusivity / empty window / missing files / malformed-skip with
warn)
23c-1..4: aggregateRequests (counts + status buckets + by_provider;
by_owner_tier; median+p95 latency over realistic distribution;
invalid windowMs rejection)
23d-1..4: topFallbackChains (sort desc by count; limit truncation;
fallback_hops=0 excluded; first_seen/last_seen carried)
23e-1..3: spendTrendDaily (N-day range correctness — caught off-by-
one during local run; populated day breakdown; empty day sparse-
fill)
23f-1..3: cacheHitRateWindow (overall + per-provider hit_rate;
bypass not in denominator; cache_status=null events excluded)
23g-1..3: PII guard for aggregateRequests / spendTrendDaily /
topFallbackChains + cacheHitRateWindow — every output JSON-
stringified + scanned for forbidden PII keys
DOCUMENTATION:
- AGENTS.md: lib/audit-query.mjs new entry; lib/audit.mjs note added
that D52 extends with daily rotation.
NOT IN D49 scope:
- server.mjs endpoints consuming these queries (D50)
- dashboard.html (D51)
- lib/audit.mjs rotation extension + bin/olp-audit-rotate.mjs (D52)
- tried_providers schema fix (D53; D45 P2 deferral)
- Phase 3 close → v0.3.0 (D55; maintainer-triggered)
Test count: 544 → 571 (+27). Verified locally via npm test.
AUTHORITY:
- ADR 0008 § 4 (query API surface) + § 5 (rotation file naming
pattern) + § 3 (storage layout).
- ADR 0007 § 8 (audit ndjson event schema — input data).
- CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased.
- Standing autopilot grant.
ALIGNMENT.md scope check: this PR adds a new lib/ module. No provider
plugin / entry surface / IR change. Rule 5 commit-citation requirements
for those scopes do not apply.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
939f3e6bd9 |
feat+test+docs: D47 — bin/olp-keys.mjs keygen CLI (Phase 2 functional scope closes) (#23)
Fourth Phase 2 implementation D-day. Closes ADR 0007 § 10 acceptance criterion #9 (bootstrap workflow must be reproducible without manual file editing) by shipping a minimal keygen CLI per § 9.1. Phase 2 functional scope is complete with this D-day — remaining work is Phase 2 close → v0.2.0 (maintainer-triggered, explicit per CLAUDE.md release_kit.phase_close_trigger). NEW bin/olp-keys.mjs (~250 lines): subcommand CLI keygen [--owner|--name=X|--tier=guest|owner|--providers=csv|--force] Creates a key + prints plaintext token to stdout ONCE; manifest stores only SHA-256 hash. --force revokes existing owner keys before creating the new owner (ADR § 9.3 recovery flow). list [--owner-only|--include-revoked] Lists keys with token_hash redacted. revoke --id=<key-id> Marks the key's revoked_at; idempotent (already-revoked → no-op + status message); missing id → exit 2. Common flag: --olp-home=<path> overrides ~/.olp/ (defaults to OLP_HOME env then ~/.olp/). package.json bin field "bin": { "olp-keys": "./bin/olp-keys.mjs" } so npx olp-keys ... resolves. Also "scripts": { "olp-keys": "node bin/olp-keys.mjs" } for npm run. Module shape (testability) Exports runCli(argv, { out, err }) so tests invoke with synthetic argv + IO writers (no process spawn). Main guard auto-runs when invoked as entrypoint. Plaintext token discipline (ADR § 5 + § 9.1) Plaintext printed exactly once on stdout. Never logged, never written to manifest, never written to audit. Operators capture immediately; lost → --force revoke + regenerate. --force async correctness cmdKeygen is async and awaits each revokeKey (which is async — acquires per-key write lock per § 6.4). Sequence: revoke each existing owner manifest atomically → then createKey for new owner. Avoids race where create-new runs before revoke-old completes. TESTS — Suite 22, +20 (524 → 544): 22a-1..5: parseArgv unit (--flag=value, --flag value, boolean, mixed positional) 22b-1..5: keygen (owner default, name+providers, missing-name error, invalid-tier error, --force revoke-then-create with isolation tmpdir) 22c-1..3: list (empty, populated with token_hash-redaction check, --owner-only filter) 22d-1..4: revoke (valid id, idempotent re-revoke, missing-id error, nonexistent-id error) 22e-1..3: top-level CLI (--help / no args / unknown subcommand exit codes) DOCUMENTATION: - AGENTS.md: lib/keys.mjs marker promoted to ✅; new bin/olp-keys.mjs entry. Implementation-status-note + shipped-set updated. - README.md: Implementation Status row added for bin/olp-keys.mjs; Known limitations note rewritten to "Phase 2 functional scope complete; close pending"; new Bootstrap workflow section with copy-pasteable npx commands + recovery flow. - CHANGELOG.md: D47 entry under Unreleased per release_kit overlay. AUTHORITY: - ADR 0007 — § 5 token format, § 9.1 minimal keygen command surface, § 9.3 recovery, § 10 acceptance criterion #9 covered. - CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased. - Standing autopilot grant. Verified: 544/544 pass via npm test (no regression in 524 pre-D47 tests; 20 new Suite 22 tests all green). Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
06f619120d |
feat+test+docs: D46 — owner-vs-guest gating for /health + X-OLP-Fallback-Detail (Phase 2) (#22)
* feat+test+docs: D46 — owner-vs-guest gating for /health + X-OLP-Fallback-Detail (Phase 2) Third Phase 2 implementation D-day. Closes ADR 0007 § 10 acceptance criteria #4 (/health payload trimming for non-owner) + #5 (X-OLP-Fallback-Detail emission gating per fallback_detail_header_policy). Phase 2 server surface now fully gated end-to-end; remaining D-days are keygen CLI surface (D47+) and Phase 2 close (v0.2.0, maintainer- triggered). server.mjs handleHealth identity-aware payload per § 7.1: - Auth gate at top — 401 for unauth + allow_anonymous=false; 200 with trimmed { ok, version } for non-owner; 200 with full payload for owner. - Trim controlled by _authConfig.owner_only_endpoints — operator removing /health from the list reverts to v0.1.1 full-payload-to- everyone (opt-out knob). - touchLastUsed fires on res.on('finish') for filesystem identities; no audit row on /health (high-volume monitoring; out of scope at Phase 2 per § 8). server.mjs withFallbackDetailHeader identity-aware emission per § 7.2: - New shouldEmitFallbackDetailHeader(olpIdentity) helper reads _authConfig.fallback_detail_header_policy: 'owner_only' (default) → emit only to owner 'all' → emit unconditionally (v0.1.1 opt-back-in) 'none' → suppress unconditionally - olpIdentity null on pre-auth paths → emit (preserves D40 v0.1.1 behaviour for pre-auth errors where identity is unknown). - withFallbackDetailHeader signature gains 3rd `olpIdentity` arg; both call sites in handleChatCompletions updated. Test surface — Suite 21, +9 tests; +1 in Suite 20 (20m); 515 → 524: 20m: /health with no auth + allow_anonymous=false → 401 (consistency with /v1/*) 21a-d: /health payload trimming (criterion #4): anonymous trimmed; guest trimmed; owner full; owner_only_endpoints: [] opts out 21e-h: X-OLP-Fallback-Detail emission gating (criterion #5): owner_only + guest → header absent owner_only + owner → header present + valid JSON 'all' + guest → header present (v0.1.1 opt-back) 'none' + owner → header absent (full suppression) Tests use 2-hop chain anthropic→openai with anthropic primary failing to produce non-empty fallbackDetail for header content. Test-mode setup updated: Global __setAuthConfig({ allow_anonymous: true }) extended to also pass owner_only_endpoints: [] + fallback_detail_header_policy: 'all' so pre-D46 tests (Suite 18, F5 /health tests, D40 fallback-detail tests, etc.) continue to pass; Suite 21 overrides per-case. DOCS: - AGENTS.md: lib/keys.mjs marker updated to reflect D46 ship; impl- status-note + shipped-set updated. - README.md: Implementation Status row + Known limitations "Multi-key auth" note rewritten to reflect D46 ship + remaining keygen CLI. - CHANGELOG.md: D46 entry under Unreleased per release_kit overlay. AUTHORITY: - ADR 0007 §§ 7.1 + 7.2 implementation contracts + § 10 criteria #4 + #5 covered. - ADR 0004 Amendment 5 (D40 — "Phase 2 will re-introduce owner-vs- non-owner gating when lib/keys.mjs lands"): this D-day fulfils the deferral. - CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased. - Standing autopilot grant (~/.cc-rules/memory/auto/ standing_autopilot_phase_2.md in cc-rules bf0ed9a). Verified: 524/524 pass via npm test (no regression in 515 pre-D46 tests; 9 new Suite 21 tests + 1 new Suite 20m test all green). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs: D46 fold-in — opus reviewer P3 polish (constant import + comment tighten) Fresh-context opus reviewer (PR #22) returned APPROVE_WITH_MINOR with 2 P3 findings, both trivial polish. - server.mjs imports gain ENV_OWNER_KEY_ID from lib/keys.mjs (already used the namesake ANONYMOUS_KEY_ID import). handleHealth touchLastUsed guard now uses the imported constant for SPOT discipline. - handleHealth audit-deferral comment tightened: removed the "§ 8 schema doesn't mandate auditing" phrasing (overstates the ADR — § 8 doesn't enumerate paths); replaced with the operational rationale (high-volume noise, no observability value until Phase 3 Dashboard). No behaviour change. 524/524 tests pass (verified locally). Authority: PR #22 fresh-context opus reviewer findings. 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> |
||
|
|
40064955ab |
feat+test+docs: D45 — server.mjs auth integration + lib/audit.mjs (#21)
* feat+test+docs: D45 — server.mjs auth integration + lib/audit.mjs (Phase 2 wire-up) Second Phase 2 implementation D-day. Wires the D44 lib/keys.mjs identity layer into the request flow + lands lib/audit.mjs per ADR 0007 § 6.2 + § 8. Closes ADR § 10 acceptance criteria #1 (per-key cache isolation), #2 (anonymous prod-default off), #3 (anonymous dev-mode on), #6 (post-revoke 401 within next request — full coverage with D45), #8 (audit ndjson round-trip), #10 (OLP_OWNER_TOKEN env override — full server-side coverage), #11 (providers_enabled 403 scope). Owner-vs-guest gating for /health + X-OLP-Fallback-Detail (criteria #4, #5) remains in D46 scope. NEW lib/audit.mjs (~110 lines): - appendAuditEvent(event, opts): one JSON event per line to ~/.olp/logs/audit.ndjson (file 0600, dir 0700). § 6.2 retry: warn + 1 retry; per-process drop counter + warn on second failure; NEVER throws. Per-call OLP_HOME env resolution (matches lib/keys.mjs). - getAuditDropCount(): for future /health surface. lib/keys.mjs extended: - loadAuthConfigSync({ olpHome }): reads auth block from ~/.olp/config.json with ADR § 7.2 defaults (allow_anonymous: false, owner_only_endpoints: ['/health'], fallback_detail_header_policy: 'owner_only'). Never throws; missing file / malformed JSON falls back to defaults. - _resolveOlpHome(opts): precedence opts.olpHome → process.env.OLP_HOME → ~/.olp. Per-call resolution so tests + operator deployments can redirect without code edits. server.mjs auth middleware integration: - extractToken(req): parses Authorization Bearer / x-api-key. - authenticate(req): validateKey + 401 paths (auth_required vs invalid_or_revoked_key). - isProviderEnabled(olpIdentity, providerKey): '*' = all; else array allowlist. - _authConfig loaded at startup; warn auth_allow_anonymous_enabled when true. Test seams __setAuthConfig / __resetAuthConfig. - handleChatCompletions + handleModels both gated by authenticate at top. Audit ctx built throughout; res.on('finish') appends row + fires touchLastUsed async. - IDENTITY-VS-CREDENTIALS SEPARATION: olpIdentity (new validated identity) consumed for cache namespacing + providers_enabled + audit; authContext passed to provider.spawn() REMAINS null so providers continue their own credential discovery (env / keychain / file). Per-provider per-key credential mapping is Phase 3+ per ADR § 12. - handleChatCompletions chain filtered by isProviderEnabled; empty result returns 403 key_no_provider_access. - keyId = olpIdentity.keyId (replacing hardcoded '__anonymous__'). - Audit captures fields throughout: post-auth, post-IR, post-chain (success or exhausted). Status + latency populated on res.on('finish'). TESTS — Suite 20, +15 (499 → 514): 20a-d: header parsing + valid key happy paths (Bearer / x-api-key / invalid → 401) 20e: revoked key 401 (criterion #6 end-to-end) 20f: OLP_OWNER_TOKEN env override returns 200 (criterion #10 full) 20g: allow_anonymous=true + no header returns 200 (criterion #3) 20h + 20h-extra: providers_enabled=['mistral'] for anthropic model → 403; '*' baseline returns 200 (criterion #11) 20i: per-key cache namespace isolation (criterion #1 end-to-end) 20j + 20j-401: audit.ndjson written with § 8 schema fields + PII guard; 401 path also appends (criterion #8) 20k: filesystem key last_used_at populated post-request (D45 touch wire) 20l + 20l-200: /v1/models also enforces auth TEST-MODE SETUP (test-features.mjs): - process.env.OLP_HOME = mkdtempSync(...) at module load so audit + key writes don't pollute ~/.olp/. - __setAuthConfig({ allow_anonymous: true }) after server.mjs imports so pre-D45 HTTP integration tests (Suite 18 etc.) continue to pass. - Suite 20 explicitly overrides __setAuthConfig per-case to exercise production-default-off coverage. DOCUMENTATION: - AGENTS.md: lib/keys.mjs 🟡 marker updated + NEW lib/audit.mjs entry; Implementation-status-note + shipped-set updated. - README.md: Implementation Status table gains lib/audit.mjs row + lib/keys.mjs row updated; Known limitations Multi-key auth note rewritten to reflect D45 ship + D46 follow-up; new env-vars (OLP_HOME, OLP_OWNER_TOKEN) and auth config block surfaced. - CHANGELOG.md: D45 entry under Unreleased per release_kit overlay phase_rolling_mode discipline. AUTHORITY: - ADR 0007 (multi-key auth — §§ 5/6.2/7/9.4 implementation contracts + § 10 criteria #1/#2/#3/#6/#8/#10/#11 covered). - CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased. - Phase 2 kickoff handoff (~/.cc-rules/memory/handoffs/ 2026-05-25-phase-2-kickoff.md in cc-rules d9da966). - Standing autopilot grant (~/.cc-rules/memory/auto/ standing_autopilot_phase_2.md in cc-rules bf0ed9a). Verified: 514/514 pass via npm test (no regression in 499 existing tests; 15 new Suite 20 tests all green). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix+test+docs: D45 fold-in — CI fail recovery + opus reviewer P1/P2/P3 Fresh-context opus reviewer (PR #21) returned APPROVE_WITH_MINOR with 4 findings; CI Node 24 separately reported 9 Suite 20 failures (all 200-expecting tests). Root cause of CI: Suite 20 setup did not stub CLAUDE_CODE_OAUTH_TOKEN before mock spawn, so anthropic.mjs AUTH_MISSING pre-check fired and tests 502'd. (Local Node 22 had the env from the maintainer's claude install — masked the gap.) CI FIX — Suite 20 OAuth env stub Added ensureSuite20FakeOAuth / restoreSuite20OAuth helpers in makeSuite20Server / teardownSuite20. Matches the existing pattern in Suite 9 line ~2154 (test-fake-oauth-token-for-cache-tests). P1 — Real-streaming path audit fidelity Single-hop streaming success (server.mjs ~L1050, the most common deployed shape) did not populate auditCtx.provider / tried_providers / cache_status. Audit rows for streaming requests carried provider: null. Fixed by stamping these at the top of the streaming branch and amending error_code on the two streaming failure exit paths (streaming_error_after_first_chunk + streaming_error_before_first_chunk). New regression test 20j-stream: streaming request asserts the audit row's provider, cache_status, and tried_providers fields are populated. P2 — Global test tmpdir cleanup process.env.OLP_HOME = mkdtempSync(...) at test-features.mjs module load left /var/folders/.../olp-test-home-* leak per npm test run. Fixed by process.on('exit', () => rmSync(_GLOBAL_TEST_OLP_HOME)). Best-effort; swallows errors so exit handler never throws. P3 — handleModels 401 lacks OLP diagnostic headers handleChatCompletions 401 passes olpErrorHeaders({ startMs }); handleModels 401 did not. Aligned. DEFERRED — P2 tried_providers semantics on 403 Reviewer noted that key_no_provider_access 403 stamps original chain in tried_providers, but the field name implies hops actually dispatched. Either ADR § 8 amendment or D46+ semantic fix. Marked in CHANGELOG; not in this fold-in scope. Test count: 514 → 515 (+1 streaming-audit regression test 20j-stream; 14 existing Suite 20 tests still pass). Verified locally via npm test. CI Node 24 recovery via the OAuth env stub. Authority: PR #21 fresh-context opus reviewer findings; CI Node 24 run 26382758946 failure logs; CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4b9916341b |
feat+test+docs: D44 — lib/keys.mjs core landed (multi-key auth, no server wire-up yet) (#20)
* feat+test+docs: D44 — lib/keys.mjs core landed (multi-key auth, no server wire-up yet)
First Phase 2 implementation D-day. Lands the lib/keys.mjs module per
ADR 0007 §§ 5 / 6.1 / 6.3 / 6.3.5 / 6.4 / 9.4. Identity / lifecycle
layer for OLP API keys is now in-tree; server.mjs integration is
scheduled D45 (until then, requests still use the hardcoded
'__anonymous__' cache namespace — no behavioural change at v0.1.1 / D44).
NEW FILE lib/keys.mjs (~437 lines, public API):
- createKey({ name, owner_tier, providers_enabled, notes, olpHome })
Generates opaque 'olp_<32-byte base64url>' token (47-char total),
SHA-256 hashes for manifest storage, atomically writes
keys/<id>/manifest.json (file 0600, dir 0700). Returns
{ id, plaintext_token, manifest } — plaintext printed once, never
persisted.
- validateKey(plaintext, { allowAnonymous, olpHome })
Three-tier resolution per § 5 / § 7 / § 9.4: env override
(OLP_OWNER_TOKEN -> __env_owner__) -> anonymous (only when
allowAnonymous: true, returns __anonymous__) -> filesystem
manifest lookup (constant-time hash compare via timingSafeEqual).
Revoked manifests return null (caller produces 401). Per § 6.3.5:
MUST hit manifest every request; no in-process validation cache.
- revokeKey({ id, olpHome })
Idempotent; sets revoked_at via atomic write inside per-key lock.
- listKeys({ olpHome })
Returns manifest objects with token_hash redacted.
- touchLastUsed(id, { olpHome })
Async best-effort lazy update per § 6.3 revoke-dominates-touch:
re-reads latest manifest inside per-key lock, NO-OPs if revoked_at
is non-null, otherwise merges last_used_at preserving all other
fields. Failure logs warn and never throws.
Plus internal helpers: hashToken (SHA-256 hex), generateToken,
generateKeyId, validateManifest (§ 4 schema validation), readManifest,
writeManifestAtomic (tmpfile + fsync + rename + chmod), _withKeyLock
(§ 6.4 in-process per-key write-lock chain), _safeHexCompare
(timing-safe).
Test-only hooks: __setTouchInterleaveHook (inject deterministic pause
for race tests), __resetWriteLocks (test cleanup).
NOT IN D44 (split per ADR §§ 6.2 / 9.1 separation):
- audit ndjson append (§ 6.2) — request-layer concern; D45 server glue
- keygen CLI bootstrap surface (§ 9.1) — D45+ separate command entry
- server.mjs integration replacing hardcoded '__anonymous__' at
server.mjs:502, :531 — D45
- owner-vs-guest gating for /health (server.mjs:392) + X-OLP-Fallback-
Detail (server.mjs:1072, :1101) — D46
TEST COUNT: 468 -> 496 (+28 tests in new Suite 19):
- 19a-d: token generation (§ 5)
- 19e-j: manifest write+read + chmod 0600/0700 + schema validation
(§ 4, § 6.1)
- 19k-p: validateKey (filesystem / wrong / missing / anonymous /
revoked / env override) (§ 5, § 6.3.5, § 9.4)
- 19q-r: revokeKey idempotency + non-existent id
- 19s-t: listKeys empty + redaction
- 19u-x: touchLastUsed updates + NO-OP on revoked + NO-OP on
anonymous/env identities + best-effort failure
- 19y-1 to 19y-4: ACCEPTANCE CRITERION #7 — concurrent revoke + touch
race tests:
19y-1 revoke -> touch (revoked_at survives)
19y-2 touch -> revoke (revoked_at + last_used_at both present)
19y-3 interleaved external-revoke via __setTouchInterleaveHook
(deterministically reproduces the § 6.3 race the
maintainer's D43-B text review caught — confirms our impl
observes the revoke and NO-OPs)
19y-4 30-iteration concurrent Promise.all stress
DOCS UPDATED IN THIS COMMIT:
- AGENTS.md: lib/keys.mjs marker 📋 -> 🟡 'core landed at D44';
Implementation-status-note + shipped-set updated.
- README.md: Implementation Status row + Known limitations
'Multi-key auth' note updated to 'core landed, server integration
pending D45'.
- CHANGELOG.md: D44 entry under Unreleased per release_kit overlay
phase_rolling_mode discipline.
AUTHORITY:
- ADR 0007 (multi-key auth) — Decision: Option 2 filesystem manifest +
opaque token; §§ 5/6.1/6.3/6.3.5/6.4/9.4 implementation contracts;
§ 10 acceptance criteria #6/#7 partially covered by D44 tests
(#7 fully covered; #6 partially covered — full coverage requires
D45+ server integration).
- CLAUDE.md release_kit overlay phase_rolling_mode — under Unreleased.
- Phase 2 kickoff handoff:
~/.cc-rules/memory/handoffs/2026-05-25-phase-2-kickoff.md
(cc-rules d9da966).
- CC 开发铁律 v1.6 § 10 — fresh-context opus reviewer required.
Verified: 496/496 pass via npm test before commit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat+test+docs: D44 fold-in — opus reviewer findings (2 P2 correctness + 2 P3 polish)
Fresh-context opus reviewer (PR #20) returned APPROVE_WITH_MINOR with 4
findings — 2 P2 real correctness gaps + 2 P3 polish. All accepted; the 2
P2 fixes ship with new regression tests.
P2 #1 — lib/keys.mjs _withKeyLock lock-map cleanup
Prior version stored `prev.then(() => next)` as the Map tail, but the
cleanup-identity check `_writeLocks.get(id) === next` could never match
the derived promise. Result: Map entries leaked one-per-unique-key-id
forever. Bounded impact at family scale (~5–10 entries) but a real
correctness bug uncovered by reviewer empirical reproduction
("CLEANUP SKIPPED every call").
Fix: store `next` directly as the Map tail. The chain still works
because new callers chain off `_writeLocks.get(id)` (the prior caller's
`next`); compare-and-delete by identity correctly cleans up when the
current caller is the last in queue.
Regression tests:
- 19x-extra: after 5 sequential touchLastUsed calls, __writeLockSize()
must be 0.
- 19x-extra-2: after 9 concurrent touch calls across 3 keys (3 per
key), __writeLockSize() must drain to 0.
P2 #2 — lib/keys.mjs validateKey non-string defensive coding
Prior version threw TypeError when called with a non-string truthy
plaintext (validateKey(42), validateKey({}), etc.), reaching
hashToken(<non-string>) which calls createHash().update(<non-string>)
which throws. Q2 (defensive-coding acceptance criterion) promised
"bad inputs return null."
Fix: top-of-function guard
`if (plaintextToken != null && typeof plaintextToken !== 'string') return null;`
Falls through to null path for non-string truthy; preserves existing
null / undefined / '' handling.
Regression test 19m-extra: validateKey(42), validateKey({}),
validateKey([]), validateKey({ token: 'olp_xxx' }), and the same with
allowAnonymous: true — all must return null without throwing.
P3 #3 — 19y-3 test scope comment clarification
Reviewer noted that 19y-3 simulates external revoke landing BEFORE
touch's read (not BETWEEN touch's read and write — currently
unreachable due to synchronous read→write in touchLastUsed). Added
explanatory comment documenting:
- The scenario this test does cover (pre-read external revoke).
- The scenario this test does NOT cover (between-read-and-write).
- Why scenario 3 is unreachable in the current impl (no await between
readManifest and writeManifestAtomic).
- The trigger for adding a post-read hook (any future refactor that
introduces an await between read and write).
P3 #4 — CHANGELOG line-count corrections
D44 entry said ~330 lines (initial estimate); actual is 462 lines
after fold-in. Test count claim updated from "+28 tests" to
"+31 tests" (28 initial + 3 fold-in regression).
New module-level export: __writeLockSize (test-only) — reports current
size of in-process write-lock Map for the regression tests above. Not
intended for production callers.
Test count: 496 → 499 (+3 fold-in regression tests; +31 total from
D44 inclusive of initial Suite 19). Verified locally via npm test.
Authority: PR #20 fresh-context opus reviewer findings; CLAUDE.md
release_kit overlay phase_rolling_mode — under Unreleased.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: D44 fold-in #2 — CHANGELOG line-count consistency (trivial)
Delta opus reviewer flagged internal CHANGELOG inconsistency: header
bullet correctly stated `~462 lines` but the P3 #4 self-description
bullet still said the prior fold-in corrected to `~445 lines`. Both now
agree on 462 (matches `wc -l lib/keys.mjs`).
No code change. Test count: 499 / 499 pass (unchanged).
Authority: PR #20 delta opus reviewer trivial inconsistency note.
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>
|
||
|
|
68851fe3d7 |
docs: D43-A — Phase 2 doc alignment (no code change) (#18)
* docs: D43-A — Phase 2 doc alignment (no code change)
Phase 1 was closed at v0.1.1 (multi-provider proxy core + pre-Phase-2
cleanup, D35-D42). This commit aligns documentation surfaces to the
Phase 2 reality before D43-B (ADR 0007 multi-key auth design draft) lands.
Pure doc cleanup — no .mjs / no tests / 4 files touched.
- CLAUDE.md release_kit.phase_rolling_mode:
* current_phase: Phase 1 → Phase 2
* current_pre_release_identifier: "0.1.0-bootstrap" → "0.2.0-phase2"
- README.md:
* Status header now reads "v0.1.1 shipped (2026-05-25); Phase 2 in progress"
* Implementation Status dated 2026-05-25; intro paragraph reflects Phase 1
close + Phase 2 active
* lib/keys.mjs row: "📋 Planned (Phase 2)" → "📋 Phase 2 active per ADR 0007
(drafting at D43-B)"
* Known limitations "Multi-key auth not yet implemented" note updated
* Phase plan rewritten end-to-end: the original v0.1 spec planned one
plugin per phase, but actual execution bundled the three Tier-D plugins
+ cache + fallback into a single Phase 1 milestone (v0.1.0+v0.1.1).
New plan: Phase 0 ✅ / Phase 1 ✅ / Phase 2 multi-key auth (current) /
Phase 3 dashboard / Phase 4+ v1.x roadmap / Phase N tier-2 opt-in.
- AGENTS.md § Key files to know:
* lib/keys.mjs marker updated
* Implementation-status-note dated 2026-05-25; reflects v0.1.1 close +
Phase 2 active scope
- CHANGELOG.md Unreleased: D43-A entry recording the alignment per
CLAUDE.md release_kit overlay phase_rolling_mode discipline.
Authority: CLAUDE.md release_kit overlay phase_rolling_mode — under
Unreleased; Phase 2 kickoff handoff at
~/.cc-rules/memory/handoffs/2026-05-25-phase-2-kickoff.md (committed in
cc-rules d9da966); ADR 0007 forthcoming at D43-B.
Test count: 468 → 468 (npm test verified locally before commit).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs: D43-A fold-in — ALIGNMENT.md phase terminology note (reviewer P2)
Fresh-context sonnet reviewer (PR #18) flagged P2: ALIGNMENT.md uses
"Phase 2"/"Phase 3" at lines ~58/~143-144/~179 with the original
per-plugin enablement meaning (Phase 2 = Codex enable, Phase 3 = Mistral
enable), conflicting with the new README phase plan rewritten by D43-A
where Phase 2 means multi-key auth.
Reviewer's recommended minimal fix (B1): add a clarifying note in
ALIGNMENT.md § Provider Inventory header explaining the dual usage,
rather than amend the tables or audit trigger wording. This keeps D43-A
within "pure doc cleanup" scope.
- ALIGNMENT.md § Provider Inventory: one-paragraph "Note on phase
terminology" inserted between the v0.1 zero-Enabled-Providers
rationale and the Enabled Providers table. No Speculative-Candidate
table change, no audit-trigger wording change, no governance-text
change.
- CHANGELOG.md Unreleased D43-A entry: ALIGNMENT.md added to the file
list with a one-line explanation referencing the reviewer-P2 fold-in.
Test count: 468 → 468 (npm test verified locally after fold-in; no test
file touched).
Authority: PR #18 fresh-context reviewer finding P2; CLAUDE.md release_kit
overlay phase_rolling_mode — under Unreleased.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d85a2dcf71 |
docs: D20 — honest forward-reference annotations across README/ADRs (Finding 9)
cold-audit catch from 2026-05-23
Cold-audit Finding 9 (P3 drift): multiple docs referenced files / paths
/ directories that don't exist at the current implementation state. A
reader following AGENTS.md § "Key files to know" to read lib/keys.mjs
would find no such file; following README architecture to dashboard.html
same; ADR 0005 § D1 described a `~/.olp/cache/<key>/<prefix>/<hash>.json`
layout that doesn't exist (current impl is in-memory Map per
lib/cache/store.mjs). D20 doesn't gut the designs — it adds honest
status callouts so the gap is obvious within seconds.
Files changed (8, docs only, +46 / -14):
1. README.md (+32 / -3):
- New H2 section "Implementation status (as of 2026-05-24)" with a
10-row table distinguishing ✅ Shipped vs 📋 Planned, including phase
numbers (Phase 2 / 6 / 7) matching the existing §"Phase plan"
- Inline status annotation on the multi-key auth bullet
(lib/keys.mjs marked planned for Phase 2)
- Inline status on the cache layer bullet (file-backed storage marked
Phase 2 — current is in-memory Map)
- Migration section's Phase 7 placeholder annotated explicitly
2. AGENTS.md (+8 / -3):
- Inline `📋 Planned (Phase N) — not yet authored` markers on
lib/keys.mjs and dashboard.html in the "Key files" bullet list
- Inline marker on setup.mjs reference in the
"Project-specific constraints" section
- New "Implementation status note" paragraph at the end of the Key
files block pointing to README's status table for the full picture
3. ALIGNMENT.md (+6 / -3):
- docs/openai-spec-pin.md references (Authority 2 + audits section)
tightened from "deferred" to "deferred, not yet authored; must be
created before first annual audit (target: v1.0)"
- docs/alignment-audits/ directory annotated as "directory does not
exist yet; it is created when the first audit is conducted"
4. CLAUDE.md (+2):
- release_kit.bootstrap_quirk_policy YAML retains the
scripts/migrate-from-ocp.mjs reference (forward-looking spec
compliance) and adds an inline YAML comment explicitly noting
"is planned (Phase 7), not yet authored. The scripts/ directory
does not currently exist. References here are forward-looking;
do not attempt to run this script."
5-8. ADR amendments (status notes only — no Amendment blocks, since
these are clarifications about implementation state, NOT decision
changes per se):
- ADR 0001 (Consequences/Negative): scripts/migrate-from-ocp.mjs
annotated as Phase 7 planned
- ADR 0003 § Decision (lossy-translation paragraph): inline note that
docs/provider-caveats.md is planned; until it exists, lossy edges
are recorded only in plugin headers. Plus a status mention in
Consequences/Positive
- ADR 0004 Mitigations: docs/provider-caveats.md annotated as planned
- ADR 0005 § Decision (D1 paragraph): the file-backed layout block
reframed from "Cache directory structure:" to "Designed file-backed
layout (target for Phase 2 storage adapter):". Added a status
blockquote explicitly noting "v0.1 implementation in lib/cache/store.mjs
uses an in-memory Map; no files written to ~/.olp/cache/. The
file-backed layout described above is the designed shape; it
transitions in via a Phase 2 storage adapter. Per-key isolation and
singleflight (D4) are live; file persistence is not."
Note on ADR Decision-section edits (corrected from initial writeup):
two ADR amendments DO touch Decision-section text (ADR 0003 lossy-
translation paragraph, ADR 0005 D1 cache layout). Both edits are
conservative — status caveats that preserve the original prose verbatim
(0003) or reframe section headings without changing the path-shape spec
(0005). No design content is gutted. No Amendment block was added
because the decisions themselves aren't changing; these are clarifications
about what's live today vs designed. The discipline boundary here:
inline-status-note ≠ decision-amendment.
Tests: 328/328 unchanged (sanity check; docs-only changes).
7/7 Finding 9 forward references annotated (confirmed by reviewer
running independent `ls` on each path):
- lib/keys.mjs ❌ doesn't exist → annotated
- dashboard.html ❌ doesn't exist → annotated
- docs/provider-caveats.md ❌ → annotated (2 ADR sites + Consequences)
- docs/openai-spec-pin.md ❌ → annotated (ALIGNMENT.md Authority 2)
- docs/alignment-audits/ ❌ → annotated
- scripts/migrate-from-ocp.mjs ❌ → annotated (README + ADR 0001 + CLAUDE.md release_kit)
- setup.mjs ❌ → annotated (AGENTS.md)
Authority:
- README/AGENTS.md/ADR/ALIGNMENT.md/CLAUDE.md self — the doc set is
its own authority for what it documents; D20 brings each statement
into honest agreement with the current implementation
- CC 开发铁律 v1.6 § 10.x — Cold Audit Finding 9
Reviewer (Iron Rule v1.6 § 10.x Mode A, fresh-context opus, independent
of drafter): APPROVE_WITH_MINOR. Verified each Finding 9 path doesn't
exist via independent `ls`; cross-checked phase numbers match the
existing §"Phase plan" listing; ran `npm test` to confirm 328/328
unchanged. Two non-blocking minors:
1. Implementer's initial writeup overstated "ADR decision text NOT
edited" — reality is two Decision-section paragraphs got inline
status caveats. Commit message above is corrected.
2. Reviewer found an additional drift D20 didn't address: ADR 0002 §
Decision filesystem layout lists `vibe.mjs` for the Mistral plugin,
but the shipped file is `mistral.mjs` (the binary is `vibe`, the
plugin file is `mistral`). Different drift class from Finding 9
(file exists, just named differently in the ADR). Filed as
follow-up issue.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
0041fb1017 |
chore: bootstrap OLP v0.1 — multi-provider LLM proxy
Initial release. OLP (Open LLM Proxy) is a personal- and family-scale
multi-provider LLM proxy that supersedes OCP (Open Claude Proxy).
Trigger: Anthropic's 2026-05-14 announcement (effective 2026-06-15)
moves `claude -p` / Agent SDK / third-party agent traffic out of the
Pro/Max subscription pool into a separate fixed monthly Agent SDK
Credit pool. OCP's foundational assumption ("subscription = unlimited
within rate limits") breaks for Anthropic on that date. Spreading
risk across multiple providers is the structural response.
Phase 0 lands:
- ALIGNMENT.md (constitution: 5 Rules, 3 Authorities, 4-tier Risk
Framework, 8-provider inventory)
- AGENTS.md (multi-tool agent guidelines; inherits cc-rules)
- CLAUDE.md (Claude-Code session instructions + release_kit overlay)
- README.md (phase-aware skeleton)
- docs/adr/0001-0006 (Founding ADRs: project founding / plugin
architecture / IR design / fallback engine / cross-provider cache /
provider inclusion + risk-tier framework)
- .github/PULL_REQUEST_TEMPLATE.md (8-radio Change Type + per-type
Authority Evidence + Iron Rule 10 reviewer checklist)
- .github/workflows/alignment.yml (blacklist + Antigravity exclusion
enforcement + models-registry validator + commit-citation soft check)
- .github/workflows/release.yml (auto-release on tag with version
match check per Iron Rule 5)
- .github/workflows/test.yml (Node 20/24 matrix, bootstrap-tolerant)
- package.json, .gitignore, LICENSE (MIT), CHANGELOG.md
Provider inventory at bootstrap:
Tier D (default-enabled): anthropic, openai, mistral
Tier C (opt-in): grok, kimi
Tier B (opt-in + consent): minimax, glm, qwen
Tier A (permanently excluded): google-antigravity
Supersedes OCP ADR 0005 (No Multi-Provider) per OLP ADR 0001. OCP
will enter maintenance mode when OLP v0.1 ships per Phase 7 plan.
Iron Rule 10 gate: fresh-context independent opus reviewer audited
all 15 governance files against OLP v0.1 spec + OCP precedent.
Verdict: APPROVE_WITH_MINOR. Two minor findings folded in:
1. alignment.yml heredoc EOF moved to column 0 (was indented;
bash parse failed silently on real blacklist hits, printing
a cryptic "syntax error" instead of the structured ALIGNMENT
GUARDRAIL FAILURE banner).
2. AGENTS.md clarified that the SPOT discipline for
models-registry.json will be codified by a Phase-1 ADR (OLP
ADR 0003 is currently the IR design, not a SPOT codification;
OCP's ADR 0003 is the precedent but OLP's registry shape
differs and warrants its own ADR).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|