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 / commit ee4d945). All five are real runtime bugs
the per-D-day fresh-context opus reviewers missed because they checked
spec text instead of runtime contracts (default auth.allow_anonymous:
false, real /health payload shape, real /cache/stats payload shape, real
/v0/management/dashboard-data payload shape).

Phase 4 process learning: every implementation D-day MUST include at
least one test that boots the server with default production config and
exercises the new feature end-to-end. D74 Suite 36 pins the wire-
contract shapes so a future D-day refactor can't silently re-break the
CLI / plugin / docs.

## P1-1 — olp doctor false-negative on auth-required /health

lib/doctor.mjs: buildBuiltinChecks() accepts opts.authHeaders and passes
to httpGet for both server.running + server.version probes. The
server.running check now distinguishes 401/403 ("server up, bearer token
missing or invalid — set OLP_API_KEY") from "server unreachable" so the
kind discriminator routes to a clean fix-auth path instead of fix_server
when operator just forgot to export the env var.

bin/olp.mjs cmdDoctor: threads authHeaders() through to runDoctor.

## P1-2 — olp-connect token validation + shell-quoting

bin/olp-connect: validate_olp_token() enforces ^olp_[A-Za-z0-9_-]{43}$
(per ADR 0007 § 3 token format) at THREE input sites: --key arg,
/health.anonymousKey server-advertised consumption, interactive prompt
fallback. shell_quote() POSIX-single-quote-wraps with embedded-quote
escape per:
  foo'bar  →  'foo'\''bar'
Applied to all rc-file writes + dry-run output. systemd
environment.d/olp.conf write additionally rejects embedded newlines.
Hostile or malformed keys can no longer persist as shell startup
injection.

## P2-3 — olp usage + olp cache human formatter wire-contract fix

bin/olp.mjs cmdUsage previously read body.usage_24h.requests /
body.providers / body.top_fallback_chains — none of which exist in the
real server payload (server.mjs:2027 + lib/audit-query.mjs). Users saw
"requests: ?" + missing per-provider quota + missing top-chains. Now
reads body.window_24h.request_count / body.cache_hit_24h.hit_rate /
body.quota / body.top_fallback_chains_24h.

bin/olp.mjs cmdCache previously read body.entries / body.bytes /
body.maxBytes (OCP-era field names). Real CacheStore.stats() returns
{hits, misses, size, inflightCount}. Now reads body.size /
body.inflightCount + computes hit rate from hits/(hits+misses).

## P2-4 — olp-plugin/ fmtHealth iterates providers.status

olp-plugin/index.js: previously walked Object.entries(body.providers)
which surfaced `enabled` / `available` / `status` as pseudo-providers
(chat showed "🟢 status" instead of "🟢 anthropic"). Now extracts the
real provider map from body.providers.status, renders enabled/available
counts in a header line, lists per-provider names with activeSpawns when
present. Falls back to flat body.providers.* for older OCP shape
(backwards compat).

## P3-5 — stale v0.3.0-era doc strings updated

README.md: header status line + Implementation Status § now reflect
v0.4.0 shipped + Phase 5 open. Known-limitations Phase 3 line moved out
of "pending v0.3.0" state; new Phase 4 line added with full deliverable
list.

server.mjs: startup banner no longer hardcodes "Phase 1 in progress"
(now just lists version + provider count). Banner derives state from
VERSION so future Phase boundaries don't need touch-ups here.

## Test count

696 (v0.4.0) → 704 (v0.4.1). +8 D74 regression tests in Suite 36:

- 36a: runDoctor accepts authHeaders + threads to checks
- 36b: server.running distinguishes 401 (auth) from "server down"
- 36c: olp-connect rejects malformed --key (validator fires before rc write)
- 36d: olp-connect accepts properly-formed olp_ token
- 36e: CacheStore.stats() shape pin + cmdCache source pin
- 36f: dashboard-data payload shape pin + cmdUsage source pin
- 36g: olp-plugin fmtHealth iterates providers.status not providers.*
- 36h: server.mjs banner doesn't hardcode stale phase

## Authority

- Maintainer independent review of main / v0.4.0 / commit ee4d945
  (2026-05-26 session — 5 findings P1×2 + P2×2 + P3×1)
- Iron Rule 第二律 (evidence over "should work") — runtime smoke
  against default production config now mandatory per Suite 36 pattern
- CLAUDE.md release_kit.phase_rolling_mode cross-Phase discipline
  ("hotfix to a shipped Phase N deliverable → bump patch, tag, release
  before next push")
- ADR 0007 § 3 (token format ^olp_[A-Za-z0-9_-]{43}$) — D74 P1-2
  validator authority

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

* fix: Suite 36 paths use import.meta.dirname for CI portability (was hardcoded /Users/taodeng/olp/)

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
dtzp555-max
2026-05-26 10:43:12 +10:00
committed by GitHub
co-authored by taodeng Claude Opus 4.7
parent ee4d9459aa
commit f3716a19fd
9 changed files with 390 additions and 35 deletions
+59 -5
View File
@@ -114,6 +114,35 @@ key_display() {
fi
}
# D74 P1-2: validate OLP API key format. Per ADR 0007 § 3, tokens are
# `olp_` + 32 random bytes base64url-encoded (43 chars, no padding). This
# regex pins the on-the-wire shape so a malformed or hostile `--key` /
# server-advertised `anonymousKey` never gets persisted into a shell rc.
# Returns 0 on valid, 1 on invalid (with diagnostic to stderr).
validate_olp_token() {
local k="$1" source="$2"
if [[ ! "$k" =~ ^olp_[A-Za-z0-9_-]{43}$ ]]; then
log_err "Rejected $source: token format does not match ^olp_[A-Za-z0-9_-]{43}$ (ADR 0007 § 3)."
log_err " Got ${#k}-char value starting with '$(echo "$k" | cut -c1-8)...'"
log_err " Expected: olp_ followed by 43 base64url chars. Run 'npx olp-keys list' on the server"
log_err " to confirm the key format, or have the operator regenerate with 'npx olp-keys keygen'."
return 1
fi
return 0
}
# D74 P1-2: POSIX shell-quote a value before interpolating into a shell rc
# write. Wraps in single quotes + escapes embedded single quotes per:
# foo'bar → 'foo'\''bar'
# Even with the validator above, this is defense-in-depth: any non-token
# string that slips through (e.g., environment.d KEY=VALUE writes) MUST be
# safe to source. Same helper pattern as lib/doctor.mjs _shellQuote.
shell_quote() {
local s="$1"
# Escape any single quotes: ' → '\''
printf "'%s'" "${s//\'/\'\\\'\'}"
}
# Detect Claude Code and print warn-only message. Per ADR 0010 § Out of
# Phase 4 scope, OLP does NOT ship /v1/messages and CC is not a supported
# client. The user is steered toward Cline + OLP.
@@ -297,17 +326,20 @@ append_olp_block() {
if $DRY_RUN; then
log_change "[dry-run] would append OLP block to $rc_file:"
log_change " # OLP LAN (added by olp-connect)"
log_change " export OPENAI_BASE_URL=$base_url/v1"
[[ -n "$key" ]] && log_change " export OPENAI_API_KEY=$(key_display "$key")"
log_change " export OPENAI_BASE_URL=$(shell_quote "$base_url/v1")"
[[ -n "$key" ]] && log_change " export OPENAI_API_KEY=$(shell_quote "$(key_display "$key")")"
log_change " # /OLP LAN"
return 0
fi
# D74 P1-2: shell-quote values before writing to rc files. Defense-in-depth
# alongside validate_olp_token — even if a future code path bypasses the
# validator, the rc file remains safe to source.
{
echo ""
echo "# OLP LAN (added by olp-connect)"
echo "export OPENAI_BASE_URL=$base_url/v1"
echo "export OPENAI_BASE_URL=$(shell_quote "$base_url/v1")"
if [[ -n "$key" ]]; then
echo "export OPENAI_API_KEY=$key"
echo "export OPENAI_API_KEY=$(shell_quote "$key")"
fi
echo "# /OLP LAN"
} >> "$rc_file"
@@ -341,6 +373,14 @@ set_system_env() {
return 0
fi
mkdir -p "$env_dir" 2>/dev/null
# D74 P1-2: systemd environment.d format is KEY=VALUE per line. While
# systemd does its own parsing (no shell sourcing), reject embedded
# newlines defensively — validate_olp_token already enforces the
# restricted charset for the API key, so this is belt-and-braces.
if [[ "$base_url" == *$'\n'* || "$key" == *$'\n'* ]]; then
log_err "Refusing to write environment.d entry: value contains newline."
return 2
fi
{
echo "OPENAI_BASE_URL=$base_url/v1"
if [[ -n "$key" ]]; then
@@ -363,8 +403,13 @@ main() {
--port=*) port="${1#*=}"; shift ;;
--key) key="${2:?--key requires a value}"
[[ -z "$key" ]] && { log_err "--key cannot be empty (omit --key for zero-config / auto-discovery)"; exit 1; }
# D74 P1-2: reject malformed --key before it ever reaches an rc write.
validate_olp_token "$key" "--key flag" || exit 1
shift 2 ;;
--key=*) key="${1#*=}"; shift ;;
--key=*) key="${1#*=}"
[[ -z "$key" ]] && { log_err "--key cannot be empty (omit --key for zero-config / auto-discovery)"; exit 1; }
validate_olp_token "$key" "--key flag" || exit 1
shift ;;
--no-system-env) NO_SYSTEM_ENV=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
--version) show_version; exit 0 ;;
@@ -457,6 +502,13 @@ try:
print(k if isinstance(k, str) and k else '')
except: print('')" 2>/dev/null || echo "")
if [[ -n "$anon_key" ]]; then
# D74 P1-2: validate server-advertised token shape before consuming.
# A hostile or misconfigured server could otherwise inject arbitrary
# strings into the user's rc file via the `anonymousKey` field.
if ! validate_olp_token "$anon_key" "/health.anonymousKey from $remote_host"; then
log_err "Refusing to consume malformed advertised key. Use --key explicitly or contact the OLP operator."
exit 2
fi
key="$anon_key"
log_ok "Using server-advertised anonymous key: $(key_display "$key")"
log_info " (set by remote via auth.advertise_anonymous_key=true; see ADR 0011 for"
@@ -479,6 +531,8 @@ except: print('')" 2>/dev/null || echo "")
log_err " Re-run with: olp-connect $host --key olp_..."
exit 2
fi
# D74 P1-2: also validate the interactively-prompted key.
validate_olp_token "$key" "interactive prompt" || exit 1
fi
fi
fi
+51 -17
View File
@@ -301,29 +301,50 @@ async function cmdUsage(flags, io) {
try { body = JSON.parse(res.body); }
catch { io.errln('Error: server returned non-JSON body'); return 2; }
if (io.wantJson) { io.emitJson(body); return 0; }
// D74 P2-3 fix: server payload shape is { generated_at, window_24h: { request_count, status_2xx,
// status_4xx, status_5xx, by_provider, by_owner_tier, by_path, median_latency_ms, p95_latency_ms },
// cache_hit_24h: { total, hit, miss, bypass, streaming_attached, hit_rate, by_provider }, quota: [{provider, ...}],
// spend_trend_30d: [{date, request_count, by_provider}], top_fallback_chains_24h: [{chain, count, ...}],
// cache_stats: { hits, misses, size, inflightCount } } per server.mjs:2027 + lib/audit-query.mjs.
io.log(colorize('OLP usage (24h)', ANSI.bold, io.useColor));
io.log('─'.repeat(60));
const u24 = body.usage_24h ?? body.usage24h ?? body['24h'] ?? {};
if (typeof u24 === 'object' && Object.keys(u24).length > 0) {
io.log(` requests: ${u24.requests ?? '?'}`);
io.log(` cache hits: ${u24.cache_hits ?? '?'}`);
io.log(` fallbacks: ${u24.fallbacks ?? '?'}`);
const w24 = body.window_24h ?? {};
const cache24 = body.cache_hit_24h ?? {};
if (typeof w24 === 'object' && (w24.request_count ?? 0) > 0) {
io.log(` requests: ${w24.request_count}`);
io.log(` 2xx / 4xx / 5xx: ${w24.status_2xx ?? 0} / ${w24.status_4xx ?? 0} / ${w24.status_5xx ?? 0}`);
if (typeof w24.median_latency_ms === 'number') {
io.log(` latency p50/p95: ${w24.median_latency_ms}ms / ${w24.p95_latency_ms ?? 0}ms`);
}
if (typeof cache24.hit_rate === 'number') {
const pct = (cache24.hit_rate * 100).toFixed(1);
io.log(` cache hit rate: ${pct}% (hit=${cache24.hit ?? 0} miss=${cache24.miss ?? 0}${cache24.streaming_attached ? ` streaming_attached=${cache24.streaming_attached}` : ''})`);
}
} else {
io.log(' (no 24h usage data — server may not have processed any requests yet)');
}
if (Array.isArray(body.providers)) {
if (Array.isArray(body.quota) && body.quota.length > 0) {
io.log('');
io.log(colorize('Per-provider quota', ANSI.bold, io.useColor));
io.log('─'.repeat(60));
for (const p of body.providers) {
io.log(` ${String(p.name ?? '?').padEnd(12)} ${p.percent_used != null ? `${p.percent_used}% used` : 'no quota api'}`);
for (const p of body.quota) {
const label = String(p.provider ?? '?').padEnd(12);
if (p.error) {
io.log(` ${label} error: ${p.error}`);
} else if (typeof p.percent_used === 'number') {
io.log(` ${label} ${p.percent_used}% used${p.resets_in_human ? ` (resets in ${p.resets_in_human})` : ''}`);
} else if (p.available === false) {
io.log(` ${label} unavailable`);
} else {
io.log(` ${label} no quota api`);
}
}
}
if (Array.isArray(body.top_fallback_chains)) {
if (Array.isArray(body.top_fallback_chains_24h) && body.top_fallback_chains_24h.length > 0) {
io.log('');
io.log(colorize('Top fallback chains', ANSI.bold, io.useColor));
io.log(colorize('Top fallback chains (24h)', ANSI.bold, io.useColor));
io.log('─'.repeat(60));
for (const f of body.top_fallback_chains.slice(0, 10)) {
for (const f of body.top_fallback_chains_24h.slice(0, 10)) {
io.log(` ${String(f.count ?? '?').padStart(5)} ${(f.chain ?? []).join(' → ')}`);
}
}
@@ -360,13 +381,20 @@ async function cmdCache(flags, io) {
try { body = JSON.parse(res.body); }
catch { io.errln('Error: server returned non-JSON body'); return 2; }
if (io.wantJson) { io.emitJson(body); return 0; }
io.log(colorize('OLP cache', ANSI.bold, io.useColor));
// D74 P2-3 fix: cacheStore.stats() returns { hits, misses, size, inflightCount }
// per lib/cache/store.mjs:320. There is no entries / evictions / bytes / maxBytes
// in the OLP cache model — those were OCP-era field names. Compute a hit rate from
// the numerator/denominator instead of fabricating bytes.
const hits = body.hits ?? 0;
const misses = body.misses ?? 0;
const denom = hits + misses;
const hitRate = denom > 0 ? ((hits / denom) * 100).toFixed(1) : '0.0';
io.log(colorize('OLP cache (live in-memory)', ANSI.bold, io.useColor));
io.log('─'.repeat(60));
io.log(` entries: ${body.entries ?? '?'}`);
io.log(` hits: ${body.hits ?? 0}`);
io.log(` misses: ${body.misses ?? 0}`);
io.log(` evictions:${body.evictions ?? 0}`);
io.log(` bytes: ${formatBytes(body.bytes ?? 0)} (max ${formatBytes(body.maxBytes ?? 0)})`);
io.log(` entries: ${body.size ?? 0}`);
io.log(` hits / misses: ${hits} / ${misses} (hit rate ${hitRate}%)`);
io.log(` inflight: ${body.inflightCount ?? 0}`);
if (body.generated_at) io.log(` generated_at: ${body.generated_at}`);
return 0;
}
@@ -574,10 +602,16 @@ async function cmdDoctor(flags, io) {
const proxyUrl = resolveProxyUrl({ proxyUrl: flags['proxy-url'] });
const checkFilter = typeof flags.check === 'string' ? flags.check : undefined;
// D74 P1-1: pass authHeaders so server.running / server.version checks
// succeed under the default production posture (auth.allow_anonymous:
// false). resolveBearerToken returns null when no env var is set; the
// doctor still runs but distinguishes 401 from "server down" by status
// code per the updated check.
const result = await runDoctor({
olpHome,
proxyUrl,
checkFilter,
authHeaders: authHeaders(),
});
if (io.wantJson) {