Files
olp/docs/adr/README.md
T
d253c2b98d docs: D43-B — ADR 0007 multi-key auth design draft (design-only) (#19)
* docs: D43-B — ADR 0007 multi-key auth design draft (design-only, no code change)

Phase 2 mainline design ADR. Ratifies the storage / token / manifest /
atomic-write / owner-gating / bootstrap / Node-baseline decisions ahead
of D44+ implementation D-days.

Pure design doc — no .mjs / no tests / 4 files touched. Test count
468 → 468.

- docs/adr/0007-multi-key-auth.md (new, ~400 lines): 13 sections
  covering Context / Decision (Option 2 filesystem manifest + opaque
  token) / Storage layout / Manifest schema / Token format (olp_+32B
  base64url, SHA-256 hash) / Atomic write & audit append (manifest
  lifecycle-only atomic via tmpfile+fsync+rename; audit per-request
  append with warn+1-retry, no memory buffer at Phase 2) / Owner-vs-
  guest-vs-anonymous gating (config.json auth.allow_anonymous default
  false, no env auto-detection) / Audit ndjson schema (no PII) /
  Bootstrap & recovery (minimal keygen command surface +
  OLP_OWNER_TOKEN env override with stable __env_owner__ keyId) /
  Acceptance criteria (11 test surfaces) / Node baseline (Option 1
  SQLite port rejection rationale citing engines >=18 + CI 20/24 vs
  node:sqlite v22.5.0/RC) / Out of scope (Dashboard, quota
  enforcement, audit query, file locking deferred to Phase 3+) /
  Future forward (Option 3 hybrid migration trigger + preconditions).

- docs/adr/README.md index: added ADR 0007 row with one-paragraph
  summary covering storage choice + rejection rationale.

- docs/v1x-roadmap.md #2: marked PHASE 2 ACTIVE (no longer deferred);
  "Design ADR (NOT YET RATIFIED)" → "Design ADR (ratified) → ADR
  0007"; trigger updated to "already fired 2026-05-25"; code anchors
  pinned to exact line numbers (cache/store.mjs:77-79/:287, server
  .mjs:502/:531/:392/:1072/:1101).

- CHANGELOG.md Unreleased: D43-B entry per release_kit overlay
  phase_rolling_mode discipline.

Authority:
- Phase 2 kickoff handoff (~/.cc-rules/memory/handoffs/2026-05-25-phase-2-kickoff.md, cc-rules d9da966)
- OLP v0.1 spec § 4.5 (planning authority for ~/.olp/ layout)
- OCP keys.mjs (prior-art for opaque-key + per-key isolation model)
- Node node:sqlite docs (https://nodejs.org/api/sqlite.html — Option 1 rejection per ADR 0007 § 11)
- CC 开发铁律 v1.6 § 10 — fresh-context opus reviewer required for design ADR per Iron Rule 10

ALIGNMENT.md scope check: this PR introduces a new ADR; per ALIGNMENT.md
Rule 1 (Cite First), the ADR itself contains the authority citations
its decisions rest on (v0.1 spec § 4.5, OCP keys.mjs, Node docs URL).
No provider plugin / entry surface / IR change in this commit.

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

* docs: D43-B fold-in — ADR 0007 reviewer findings (2 P2 + 3 P3, all polish)

Fresh-context opus reviewer (PR #19) returned APPROVE_WITH_MINOR with 2 P2
load-bearing-but-non-blocking findings + 3 P3 polish findings. All five
are accepted as suggested; design contract clarified without semantic
change.

- § 6.2 step 1 (P2 #1) — pin audit serialization to fire AFTER status_code
  is determined and latency_ms is measured. Makes acceptance criterion #2
  (anonymous-401 audit event records the 401 + latency) testable in the
  way the criterion was written.

- § 6.3.5 (P2 #2, new subsection) — explicit "Token validation MUST hit
  the manifest on every authenticated request (no in-process validation
  cache at Phase 2)" rule. The acceptance criterion #6 (post-revoke 401
  within the next request) was previously enforced only by the test; the
  rule now belongs to the design contract. Forward-path note documents
  when a Phase 3+ amendment may add a cache.

- § 6.1 atomic-write step 5 follow-up (P3 #3) — document the deliberate
  omission of directory fsync after rename. Single-process family-scale
  deployment accepts the tiny rename-loss window under abrupt host crash;
  future POSIX-strict deployments know where to add the step.

- § 9.4 (P3 #4) — declare token-collision between OLP_OWNER_TOKEN and
  a filesystem-stored key's plaintext as undefined behaviour. Operators
  MUST NOT reuse plaintext across both surfaces. Phase MAY add startup
  collision-detection later.

- § 10 criterion #4 (P3 #5) — rephrased to assert against the config-
  driven owner_only_endpoints predicate rather than a hardcoded trimmed
  payload shape. The test stays stable if an operator removes /health
  from owner_only_endpoints.

CHANGELOG D43-B entry: fold-in bullet added to summarize the 5 fixes.

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

Authority: PR #19 fresh-context opus reviewer findings; CLAUDE.md
release_kit overlay phase_rolling_mode — under Unreleased.

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

* docs: D43-B fold-in #2 — maintainer text-review findings (1 P1 + 1 P2 + 1 P3)

Maintainer (codex) did final text review of PR #19 against the Phase 2
ADR-ratification checklist. Returned 3 findings; all accepted as
suggested. Two are contract-level (P1 safety + P2 factual); one is
trivial (P3 line-count drift). No semantic change beyond what the
findings called out.

P1 — § 6.3 / § 6.4 / § 10 #7 — revoke-dominates-touch safety contract

  Original § 6.3 said touchLastUsed used the same atomic-write pattern
  as 6.1; § 6.4 said concurrent CLI revoke + touchLastUsed left "both
  states valid" with "observability-grade" failure mode. Codex correctly
  identified the bug: a stale manifest snapshot held by the touch path
  could overwrite a fresh revoke and silently clear revoked_at back to
  null, breaking acceptance criterion #6 (post-revoke 401 within next
  request) under concurrent CLI revoke + in-flight server request. The
  ADR was promising security-grade behavior on a path that was actually
  last-write-wins.

  Fix:
  - § 6.3 rewritten with explicit read-modify-write discipline: touch
    MUST re-read latest manifest from disk inside the per-key write-lock,
    NO-OP if revoked_at is non-null, otherwise merge last_used_at
    preserving all other fields including revoked_at.
  - § 6.4 reframed from "both states valid" to "revoke dominates touch"
    safety frame, citing § 6.3 as the load-bearing discipline. The
    CLI revoke writer always wins the dimension that matters; touch
    may lose its last_used_at update if it raced.
  - § 10 criterion #7 expanded to test all three orderings (revoke
    -> touch, touch -> revoke, interleaved) with the explicit MUST:
    revoked_at is non-null and equals the revoke writer's timestamp
    after any interleaving; FAIL if any path produces revoked_at: null.
  - Forward-path § 6.4 file-locking note updated to clarify §6.3
    already holds the contract single-process; flock adds defense-in-
    depth for rare multi-writer TOCTOU.

P2 — § 11 forward path step (1) — Node baseline version history corrected

  Original wording "Node v22.5.0+ for unflagged but RC; Node TBD for
  stable" was wrong. v22.5.0 added with --experimental-sqlite flag;
  v22.12 still required the flag; the module moved past flag-gating
  in v22.13.0 (LTS) / v23.4.0 (current); entered Release Candidate at
  v25.7.0 per current docs.

  Fix: § 11 forward path step (1) rewritten with accurate versions +
  two Node release-history URLs cited (https://nodejs.org/download/
  release/v22.12.0/docs/api/sqlite.html and https://nodejs.org/api/
  sqlite.html). Minimum non-flag-gated baseline is now stated as
  >=22.13.0 (LTS) / >=23.4.0 (current); stable baseline TBD pending
  Node v25.x+. The rejection-evidence paragraph earlier in § 11
  ("v22.12 still required --experimental-sqlite ... current docs mark
  RC") was already correct and is untouched.

P3 — CHANGELOG D43-B line-count corrected

  Entry said ADR was "~270 lines"; actual file is 420 lines after both
  fold-ins. Changed to "~420 lines after fold-ins". Phase 2 fold-in #2
  bullet enumerates the 3 fixes in this commit; fold-in #1 bullet
  retained for the opus reviewer round.

Test count: 468 / 468 (npm test verified locally after fold-in;
design-only doc changes, no test file touched).

Authority: PR #19 maintainer text review findings 2026-05-25; CLAUDE.md
release_kit overlay phase_rolling_mode — under Unreleased; Node SQLite
docs URLs cited in ADR § 11 forward path.

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

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 12:56:35 +10:00

3.9 KiB
Raw Blame History

Architecture Decision Records — OLP

This directory holds the OLP Architecture Decision Records (ADRs) — short documents that capture the why behind structural choices.

Read these before proposing governance, plug-in contract, IR, fallback, cache, or provider-inclusion changes.

Numbering

ADRs in OLP start at 0001 (the project's founding decision). OLP did not inherit the 0001 placeholder convention OCP used; the project starts on a fresh numbering line.

New ADRs increment from the highest existing number. Filenames are NNNN-<short-slug>.md.

Index

ADR Title What it covers
0001 Project Founding Why OLP exists (the 2026-06-15 Anthropic billing-split trigger), mission + non-mission boundaries, explicit supersession of OCP ADR 0005 "No Multi-Provider".
0002 Plugin Architecture for Providers The lib/providers/<name>.mjs plug-in model, the Provider contract (name / models / auth / spawn / estimateCost / quotaStatus / healthCheck / hints), static in-tree registry, why-not full external plugin discovery.
0003 Intermediate Representation (IR) Design The OLP-internal canonical request/response shape between the OpenAI-compat entry surface and each provider plugin. v1.0 IR fields, IR-vs-OpenAI-vs-native-provider three-shape model, IR is not exposed externally.
0004 Fallback Engine Semantics & Safety Trigger taxonomy (Hard / Soft / Deterministic-deferred / Cost-aware-deferred), idempotent-failure safety (first-chunk rule), chain advancement one-at-a-time, observability headers.
0005 Cache Layer Cross-Provider Design Cache key composition over (provider, model, messages, …), per-model isolation, D1+D2+D3+D4 port from OCP v3.13.0, cross-provider fallback cache behaviour (correct miss).
0006 Provider Inclusion / Exclusion + Risk-Tier Framework The 4-tier classification (A excluded by default / B explicit consent / C opt-in / D eligible-for-default-enabled), Candidate-vs-Enabled distinction, current v0.1 candidate inventory (0 Enabled), Antigravity exclusion rationale (named prohibition + no cost advantage + reinstatement friction; pending primary-source pin), consent UX, future provider addition procedure.
0007 Multi-Key Auth (lib/keys.mjs) Phase 2 design ADR (D43-B, 2026-05-25). Option 2 (filesystem manifest at ~/.olp/keys/<key-id>/manifest.json) + opaque olp_<32-byte> token + SHA-256 hash. Owner / guest / anonymous tier gating with explicit config.json auth.allow_anonymous (default false). Bootstrap keygen command surface + OLP_OWNER_TOKEN env override with stable synthetic key_id. Audit ndjson append-only at ~/.olp/logs/audit.ndjson, warn+1-retry on append failure. Rejects direct SQLite port at v0.2.0 due to Node baseline (engines >=18 + CI 20/24 vs node:sqlite added 22.5.0 / RC); Option 3 hybrid documented as forward path when Phase 3+ Dashboard / SQL-aggregate quota arrives.

When to write a new ADR

Open one whenever:

  • A structural rule is being added or changed (e.g., new IR field, new trigger type, new cache-key composition).
  • A new provider plugin is being proposed (per ADR 0002 + ADR 0006 — ADR is a hard prerequisite for plugin merge).
  • A decision encodes a lesson from an external event (provider policy change, drift incident).
  • A future contributor reading the code alone could plausibly undo or re-litigate the choice.

Skip ADRs for routine implementation choices (algorithm pick, naming) — those belong in commit messages.

Format

Keep ADRs short — Context / Decision / Consequences / Alternatives is the standard skeleton. Cite incidents, PRs, or commits where useful. Length per ADR ~150250 lines; longer than that, split.