mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-21 21:15:10 +00:00
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>
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
# ADR 0011 — Anonymous-Key Deployment-Context Limits (Trusted-LAN Invariant)
|
||||
|
||||
**Status:** Accepted (2026-05-26)
|
||||
**Date:** 2026-05-26
|
||||
**D-day:** D70 (lands alongside D68 `olp-connect` + D69 `/health.anonymousKey`)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
ADR 0010 § Phase 4 D68-D70 charter scoped a three-deliverable bundle:
|
||||
|
||||
- **D68** `olp-connect <ip>` — client-side bash script that auto-configures a
|
||||
family member's machine to point at a remote OLP instance, including IDE
|
||||
detection (Cline / Continue.dev / Cursor / Aider / OpenClaw) and rc-file +
|
||||
system-level env var writes.
|
||||
- **D69** `/health.anonymousKey` field — opt-in (`auth.advertise_anonymous_key:
|
||||
true` in `~/.olp/config.json`; default `false`) surface that emits the
|
||||
plaintext of a designated guest-tier key so `olp-connect` can pick it up
|
||||
with zero-config (no out-of-band token paste).
|
||||
- **D70** this ADR — codifies the deployment-context limits that make D69
|
||||
safe.
|
||||
|
||||
The D69 mechanism is a deliberate port of OCP's clever `PROXY_ANONYMOUS_KEY` +
|
||||
`/health.anonymousKey` pattern (OCP `server.mjs:148, 1454, 1488, 1555`,
|
||||
shipped 2026-04 under OCP issue #12 § 14 Path A) which made family-member
|
||||
onboarding go from "maintainer texts API key + edits IDE config" to
|
||||
`curl -fsSL .../ocp-connect | bash -s -- <ip>`. The OCP pattern works on
|
||||
trusted family LAN deployments; it would catastrophically fail on a public
|
||||
internet deployment. ADR 0011 makes the trust assumption explicit before OLP
|
||||
inherits the pattern.
|
||||
|
||||
The ADR also pins three implementation details that are NOT obvious from
|
||||
reading the D69 patch alone:
|
||||
|
||||
1. The plaintext token must live on disk SOMEWHERE for the server to surface
|
||||
it. ADR 0007 § 5 + § 6.2 explicitly forbid plaintext storage in the
|
||||
default manifest. D69 introduces an explicit **opt-in** `plaintext_advertise`
|
||||
manifest field for ONLY the advertised key — every other key retains the
|
||||
ADR 0007 § 5 hash-only contract.
|
||||
2. The advertised key is **guest-tier**, not owner-tier. Owner-tier
|
||||
advertisement is rejected at keygen time AND at config-load time —
|
||||
exposing the owner identity unauthenticated would grant any LAN caller
|
||||
`/health` full payload, `/v0/management/*` mutating access, and
|
||||
`X-OLP-Fallback-Detail` visibility — the exact inverse of the advertise
|
||||
key's intent (a low-privilege zero-config tier).
|
||||
3. Three prerequisites MUST hold simultaneously for `/health.anonymousKey`
|
||||
to be emitted; missing any one is logged at startup but the server still
|
||||
boots — graceful-degrade rather than refuse-to-start.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
### Three-prerequisite gate (server-side)
|
||||
|
||||
`/health` emits the `anonymousKey` field if and only if ALL THREE hold:
|
||||
|
||||
1. `auth.advertise_anonymous_key === true` in `~/.olp/config.json` (default
|
||||
`false` — opt-in).
|
||||
2. `auth.allow_anonymous === true` (the anonymous tier must be reachable for
|
||||
the advertised key to be meaningful to zero-config callers; advertising a
|
||||
key into a deployment that rejects anonymous requests is incoherent).
|
||||
3. At least one active (`revoked_at === null`) manifest under `~/.olp/keys/`
|
||||
carries a non-empty `plaintext_advertise: "olp_..."` field.
|
||||
|
||||
When prerequisite (1) holds but (2) or (3) fails, the server logs a
|
||||
startup warn (`anonymous_key_advertised_but_denied` or
|
||||
`anonymous_key_advertised_but_no_anonymous_key_exists`) but starts normally
|
||||
and simply omits the field from `/health` responses.
|
||||
|
||||
### Plaintext storage mechanism (`plaintext_advertise` manifest field)
|
||||
|
||||
ADR 0007 § 5 forbids plaintext storage anywhere. D69 introduces a single,
|
||||
**explicitly opt-in** exception: the manifest of the designated advertised
|
||||
key gains a `plaintext_advertise` field whose value is the plaintext token.
|
||||
|
||||
This field is written ONLY when the operator runs:
|
||||
|
||||
```
|
||||
olp-keys keygen --anonymous --advertise
|
||||
```
|
||||
|
||||
(or `--advertise` alone on a guest-tier `keygen` invocation; `--anonymous`
|
||||
is a friendly shorthand for `--tier=guest --name=anonymous`). The keygen
|
||||
command surfaces an explicit `WARNING` to stderr at creation time:
|
||||
|
||||
```
|
||||
WARNING: this key's plaintext is now stored on disk + will be exposed via
|
||||
/health.anonymousKey when auth.advertise_anonymous_key=true AND
|
||||
auth.allow_anonymous=true. Use ONLY on a trusted LAN. See ADR 0011.
|
||||
```
|
||||
|
||||
Every other key (every existing key, and every newly-created key without
|
||||
`--advertise`) retains the ADR 0007 § 5 hash-only contract — `manifest.json`
|
||||
contains `token_hash` and NEVER `plaintext_advertise`.
|
||||
|
||||
**Schema-version note (D69 reviewer P2-2).** This adds a new optional field
|
||||
to the manifest. Per ADR 0007 § 4 ("Increment `schema_version` on any
|
||||
non-additive change"), additive optional fields do NOT require a
|
||||
`schema_version` bump — older parsers ignore unknown fields per the same
|
||||
section's forward-compat rule. The manifest stays at `schema_version: 1`.
|
||||
Documented here so a future archaeologist asking "why didn't D69 bump
|
||||
`schema_version`?" has a one-line answer.
|
||||
|
||||
**`listKeys()` redaction (D69 reviewer P2-1).** `lib/keys.mjs listKeys()`
|
||||
strips BOTH `token_hash` AND `plaintext_advertise` from its return value.
|
||||
Callers wanting the advertised plaintext for the `/health` publication
|
||||
path MUST go through `findAdvertisedKey()` — the only sanctioned read
|
||||
site. This protects against a future caller of `listKeys()` accidentally
|
||||
emitting the plaintext into logs / HTTP responses / dashboards.
|
||||
|
||||
### Tier restriction (guest only)
|
||||
|
||||
`createKey()` rejects `plaintext_advertise: true` for `owner_tier: 'owner'`
|
||||
with the error `createKey: plaintext_advertise requires owner_tier="guest"`.
|
||||
The CLI also rejects `--owner --advertise` with a clear error pointing at
|
||||
this ADR.
|
||||
|
||||
Rationale: owner-tier confers `/health` full payload visibility,
|
||||
`/v0/management/*` mutating access, and `X-OLP-Fallback-Detail` header
|
||||
visibility. Advertising owner-tier plaintext unauthenticated would let any
|
||||
LAN caller assume the owner identity — the exact opposite of the design
|
||||
intent.
|
||||
|
||||
### Trusted-LAN deployment invariant
|
||||
|
||||
`auth.advertise_anonymous_key: true` is permitted ONLY when the OLP server
|
||||
is bound to a trust-equivalent address space:
|
||||
|
||||
| Tier | Address space | Permitted? |
|
||||
|------|---------------|------------|
|
||||
| Loopback | `127.0.0.0/8` | yes |
|
||||
| RFC 1918 LAN | `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` | yes |
|
||||
| Tailnet | `100.64.0.0/10` (CGNAT range used by Tailscale) | yes |
|
||||
| Localhost domains | `localhost`, `*.local`, `*.internal` | yes |
|
||||
| Public internet | any routable IPv4/IPv6 outside the above | NO |
|
||||
|
||||
This is a **soft constraint** at v0.4.0 — OLP does not enforce IP-allowlist
|
||||
or BIND_ADDRESS inspection. The constraint is documented here, surfaced in
|
||||
README § "Anonymous-key advertise mode (trusted-LAN-only)", and warned-but-
|
||||
not-blocked at server startup when `auth.advertise_anonymous_key=true` and
|
||||
the bind address looks public.
|
||||
|
||||
Hard enforcement (refuse to start when bind is public + advertise enabled)
|
||||
is deferred. The maintainer's deployments are LAN-only, the family-scale
|
||||
audience cannot tolerate a startup-refuse mode that bricks the proxy on
|
||||
ambiguous network topology (e.g., TLS-fronted private network where the
|
||||
underlying bind IP IS public but the network itself is trusted), and the
|
||||
trade-off in ADR 0010 explicitly accepted operator-discretion gates for
|
||||
soft constraints of this class.
|
||||
|
||||
---
|
||||
|
||||
## Threat model
|
||||
|
||||
The advertised anonymous key is **public** within the boundary of "anyone
|
||||
who can reach `GET /health`." Anyone within that boundary can read the
|
||||
plaintext from `/health.anonymousKey` and use it for `/v1/chat/completions`,
|
||||
`/v1/models`, etc.
|
||||
|
||||
| Deployment | Boundary | Acceptable? |
|
||||
|------------|----------|-------------|
|
||||
| Mac mini + Tailscale, only family devices on tailnet | family devices | YES |
|
||||
| Home LAN with no guest WiFi, no port-forward | household + neighbors-within-WiFi-range | YES (within risk tolerance) |
|
||||
| Home LAN with guest WiFi joined to same VLAN as proxy | EVERYONE who visits and connects to guest WiFi | borderline; treat with caution |
|
||||
| Coffee shop / open WiFi | EVERYONE physically present | NO |
|
||||
| Public internet via Cloudflare Tunnel / port-forward / VPS | EVERYONE on the internet | NO — instant compromise |
|
||||
|
||||
The capability gain for an attacker who reads `/health.anonymousKey` is
|
||||
**equal to the capability the operator deliberately granted the
|
||||
anonymous-tier key**:
|
||||
|
||||
- `providers_enabled` (when `'*'`, the attacker can dispatch any provider —
|
||||
burning the operator's subscription quotas).
|
||||
- `/v1/chat/completions` access (LLM use under the operator's billing).
|
||||
- Cache pollution under `__anonymous__` namespace (per ADR 0007 § 7.1; the
|
||||
advertised guest key uses its own `<key-id>` namespace — but anonymous
|
||||
callers who DON'T present the key use `__anonymous__`).
|
||||
|
||||
What the attacker does NOT get:
|
||||
|
||||
- `/health` full payload — gated to `owner_tier === 'owner'` (ADR 0007 § 7.1).
|
||||
- `/v0/management/*` mutating endpoints — gated to owner (ADR 0008 § 7).
|
||||
- `X-OLP-Fallback-Detail` header — `'owner_only'` policy default (ADR 0007 § 7.2).
|
||||
- The owner key's plaintext (which is never stored anywhere; only its
|
||||
`token_hash` is on disk per ADR 0007 § 5).
|
||||
|
||||
The "burn the operator's subscription quotas" failure mode is bounded by
|
||||
the per-provider quota limits AND the maintainer's monitoring (`/health`
|
||||
owner-view shows quota status per provider; `/v0/management/audit` shows
|
||||
per-key usage). Detection is fast; the question is how much quota the
|
||||
attacker can burn between compromise and key revocation.
|
||||
|
||||
---
|
||||
|
||||
## `olp-connect` integration (D68 client-side)
|
||||
|
||||
`bin/olp-connect <ip>` queries `GET /health` as its first action. If the
|
||||
response contains `anonymousKey: "olp_..."`, the script uses that value
|
||||
silently for the rest of the run (printing a one-line `Using server-
|
||||
advertised anonymous key: olp_...XXXX` notice + a pointer to this ADR).
|
||||
This is what makes `olp-connect <ip>` a true zero-config command — no
|
||||
out-of-band token paste needed.
|
||||
|
||||
If `anonymousKey` is absent (the default, when `auth.advertise_anonymous_key`
|
||||
is false), the script falls back to interactive prompt or `--key` flag.
|
||||
|
||||
`olp-connect` does NOT perform any of the trusted-LAN soft-checks itself —
|
||||
it trusts that an operator who set `auth.advertise_anonymous_key: true`
|
||||
knows their deployment context. The script does, however, document the
|
||||
trade-off in its `--help` output and prints the ADR 0011 reference
|
||||
alongside the "using server-advertised key" notice.
|
||||
|
||||
---
|
||||
|
||||
## Re-evaluation triggers
|
||||
|
||||
Re-open this ADR when ANY of the following fires:
|
||||
|
||||
1. OLP gains a "expose to public internet" deployment mode in the README
|
||||
(e.g., Cloudflare Tunnel guidance, ngrok recipe). At that point the
|
||||
soft-constraint MUST become a hard constraint (bind-address inspection
|
||||
at startup, refusal to enable `advertise_anonymous_key` when bind is
|
||||
public — likely with a separate `OLP_TRUSTED_PUBLIC_OVERRIDE=1` env
|
||||
escape hatch for operators who run their own TLS termination).
|
||||
2. The OCP `/health.anonymousKey` model is found to have caused a
|
||||
real-world quota-burn incident; that learning amends this ADR.
|
||||
3. Phase 5 introduces multi-tenant SaaS-like deployments (currently
|
||||
non-goal per ADR 0001); the entire family-scale assumption is
|
||||
re-examined.
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive.**
|
||||
|
||||
- Family-member onboarding becomes a single command: `olp-connect <ip>`. No
|
||||
out-of-band token paste. No "wait, what's the API key?" friction loop.
|
||||
- The trust trade-off is now an explicit, single-knob config decision, not
|
||||
an implicit consequence of OCP-pattern inheritance.
|
||||
- The `plaintext_advertise` field is a single auditable on-disk surface —
|
||||
`grep plaintext_advertise ~/.olp/keys/*/manifest.json` answers "which key
|
||||
is advertised?" definitively, and an operator who wants to disable the
|
||||
feature can simply revoke that key.
|
||||
- Owner-tier advertisement is impossible (both at keygen and at config
|
||||
load), eliminating an entire class of foot-gun.
|
||||
|
||||
**Negative.**
|
||||
|
||||
- ADR 0007 § 5's "no plaintext on disk, ever" property is weakened to "no
|
||||
plaintext on disk except for ONE explicitly-opted-in field on ONE key."
|
||||
The exception is narrow and audit-grep-able but the property is no
|
||||
longer absolute.
|
||||
- Operators who enable advertise mode then move the deployment from LAN to
|
||||
public internet (e.g., add a Cloudflare Tunnel without revisiting the
|
||||
config) silently invert the threat model. The startup warn for "public
|
||||
bind detected" does not currently fire (soft constraint per § "Trusted-
|
||||
LAN deployment invariant" above).
|
||||
- The OCP precedent shows operators sometimes share `olp-connect <ip>`
|
||||
invocations in chat / docs that include their IP; an LLM training corpus
|
||||
could harvest these IPs. The advertised key is only useful while the
|
||||
network reaches the IP, but the IP-disclosure surface grows.
|
||||
|
||||
**Neutral.**
|
||||
|
||||
- The plaintext storage is per-key, not global. Revoking the advertised key
|
||||
removes the plaintext exposure within one filesystem write (the manifest
|
||||
stays on disk for audit attribution per ADR 0007 § 6.1, but `revoked_at`
|
||||
becomes non-null and `findAdvertisedKey()` skips revoked manifests).
|
||||
|
||||
---
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
1. **Store plaintext in `config.json` directly.** Rejected. Mixes secrets
|
||||
with operational config; complicates git-crypt boundary; loses the
|
||||
per-key revocation path (you'd have to edit JSON to "revoke" the
|
||||
exposure rather than running `olp-keys revoke --id=<id>`).
|
||||
2. **Add an `anonymous` owner_tier instead of using `guest` + `plaintext_
|
||||
advertise`.** Rejected. Bumps ADR 0007 § 4 schema version (a
|
||||
non-additive change), adds a third identity class that the rest of the
|
||||
codebase (cache namespacing, /health gating, audit attribution) has no
|
||||
reason to know about, and conflicts with ADR 0007 § 7.1's "anonymous =
|
||||
no auth header + allow_anonymous=true" definition. A single optional
|
||||
field on the manifest is strictly less invasive.
|
||||
3. **Hard-enforce trusted-LAN bind address at startup.** Rejected for
|
||||
v0.4.0; deferred until a public-deployment-mode README section ships
|
||||
(see Re-evaluation triggers § 1). Soft constraint + startup warn is
|
||||
appropriate while OLP has zero public-internet deployment recipes.
|
||||
4. **Encrypt `plaintext_advertise` at rest with a key derived from
|
||||
`OLP_HOME` path or a separate `OLP_ADVERTISE_KEY` env var.** Rejected.
|
||||
The threat model is "anyone who can read `/health` reads the plaintext
|
||||
token over the wire," not "anyone who can read `~/.olp/keys/`." Both
|
||||
require LAN-reach; encrypting on-disk doesn't change the over-the-wire
|
||||
exposure. Adds complexity for no security gain in the relevant attack
|
||||
model.
|
||||
5. **Make `--advertise` allowed only when `--name` is exactly `anonymous`.**
|
||||
Rejected as over-restrictive. The CLI's `--anonymous` shorthand
|
||||
defaults `--name=anonymous`, but operators may legitimately want a
|
||||
named advertised key (e.g., `family-guest`, `lan-zero-config`). The
|
||||
discriminator is the field, not the name.
|
||||
|
||||
---
|
||||
|
||||
## Authority citations
|
||||
|
||||
- **ADR 0007 § 7** (Identity-class table — anonymous tier definition;
|
||||
`__anonymous__` keyId).
|
||||
- **ADR 0007 § 5** (Token format — establishes hash-only on-disk; D69 is
|
||||
the explicit opt-in exception).
|
||||
- **ADR 0007 § 4** (Manifest schema — D69 adds optional `plaintext_advertise`
|
||||
field; § 4 already specifies "unrecognized fields cause a warn but not a
|
||||
reject (forward-compat)" so the addition is non-breaking for older
|
||||
parsers).
|
||||
- **ADR 0007 § 7.2** (Configuration — D69 adds `auth.advertise_anonymous_key`
|
||||
alongside existing `allow_anonymous` / `owner_only_endpoints` /
|
||||
`fallback_detail_header_policy`).
|
||||
- **ADR 0010 § Phase 4 charter D68-D70 row** (scope authority for this ADR).
|
||||
- **OCP `server.mjs:148, 1454, 1488, 1555`** (prior-art for the
|
||||
`PROXY_ANONYMOUS_KEY` env + `/health.anonymousKey` pattern; OCP v3.13.0).
|
||||
- **OCP issue #12 § 14 Path A** (the original anonymous-key decision
|
||||
context for OCP; the "Path A" label is OCP-specific and not used in
|
||||
OLP).
|
||||
- **`bin/olp-connect`** (D68 client-side consumer of `/health.anonymousKey`).
|
||||
- **`bin/olp-keys.mjs`** (D69 keygen `--advertise` flag implementation).
|
||||
- **`lib/keys.mjs` `findAdvertisedKey()`** (D69 server-side resolver).
|
||||
- **`server.mjs handleHealth`** (D69 emission point + startup-warn site).
|
||||
|
||||
---
|
||||
|
||||
## Procedural mechanism
|
||||
|
||||
CC 开发铁律 v1.6 § 10 (independent reviewer per implementation D-day) — D68
|
||||
+ D69 + D70 ship as ONE PR per Iron Rule 11 IDR (the three deliverables
|
||||
are mutually constituting: `olp-connect` consumes `/health.anonymousKey`,
|
||||
`/health.anonymousKey` is governed by ADR 0011, ADR 0011 documents
|
||||
`olp-connect`'s trust posture). The reviewer is a fresh-context opus
|
||||
subagent.
|
||||
@@ -24,6 +24,7 @@ New ADRs increment from the highest existing number. Filenames are `NNNN-<short-
|
||||
| [0008](0008-dashboard-and-audit-query.md) | Dashboard + Audit Query Layer | Phase 3 design ADR (D48, 2026-05-25). Static HTML dashboard + vanilla JS + fetch (no build step). In-memory ndjson scan for aggregate queries (O(N) per call; family-scale acceptable; defers SQLite migration to Option 3 hybrid trigger). Daily audit rotation `audit-YYYY-MM-DD.ndjson` on first append after UTC midnight; cross-file query layer for rolling 30-day windows. Owner-only gating on `/dashboard` + 3 `/v0/management/*` JSON endpoints reusing ADR 0007 § 7 auth model. 30s page poll (no SSE infra). Panels: per-provider quota / 24h request+cache+fallback / 30d spend trend / top-N fallback chains per spec § 4.6. Opens ADR 0007 § 12 Phase 3 deferral (Dashboard + audit query + rotation). |
|
||||
| [0009](0009-interactive-mode-path-placeholder.md) | Anthropic Interactive-Mode Path (Placeholder) | Placeholder ADR (2026-05-25, Draft) — blocked on OCP ADR 0007 P0 experiment outcome. Records the maintainer's "wait + port" decision: do NOT independently implement; ride OCP's P0 result. If P0 confirms Transport A (stdio NDJSON) or B (PTY) bills as subscription rather than Agent SDK credit, port to OLP `lib/providers/anthropic.mjs` (Option 1 parallel impl, or Option 2 OCP-as-backend; decision deferred to P0-resolution time). If P0 fails on both, shelve. No Phase 4 D-day scheduled until P0 lands AND maintainer issues explicit "go" naming this ADR. |
|
||||
| [0010](0010-phase-4-charter-operator-and-client-ux.md) | Phase 4 Charter — Operator + Client UX | Phase 4 scope ratification (2026-05-26, Accepted). Phase 4 = operator + client UX (SSE heartbeat / `olp` CLI + doctor / `olp-connect` zero-config + Telegram-Discord plugin + IDE docs bundle). ~13 D-days, D60 → v0.4.0. Records the explicit decision to DEFER `/v1/messages` (Anthropic-shape entry surface) on the rationale that under ADR 0009 P0 failure it provides no billing benefit AND degrades worse on fallback than OpenAI-shape clients. Re-open trigger: ADR 0009 P0 success + maintainer-named family CC user. Also closes the OCP-OLP port co-host ambiguity from ADR 0001 (default `OLP_PORT` 3456 → 4567). |
|
||||
| [0011](0011-anonymous-key-deployment-context.md) | Anonymous-Key Deployment-Context Limits (Trusted-LAN Invariant) | D70 (2026-05-26, Accepted). Codifies the trust posture for `/health.anonymousKey` opt-in field (D69) + `bin/olp-connect` zero-config consumer (D68). Three-prerequisite gate (`auth.advertise_anonymous_key=true` + `auth.allow_anonymous=true` + an active key with `plaintext_advertise` field). Guest-tier-only restriction (`createKey()` + CLI reject owner+advertise). Trusted-LAN deployment invariant (loopback / RFC1918 / tailnet / `.local` / `.internal` — soft constraint at v0.4.0; hard enforcement deferred until OLP gains a public-deployment recipe). Re-evaluation trigger: any "expose to public internet" README mode. |
|
||||
|
||||
## When to write a new ADR
|
||||
|
||||
|
||||
Reference in New Issue
Block a user