Files
ocp/docs/adr/0003-models-json-spot.md
1c29f4867f chore(privacy): scrub personal identifiers from public repo (#43)
Removes all occurrences of maintainer's real name and handle from
tracked files in this PUBLIC repository. Replaces with generic terms
like "project maintainer" / "the maintainer".

Files scrubbed:
- CLAUDE.md (2 instances)
- ALIGNMENT.md (Auditor field)
- docs/adr/0002, 0003, 0004 (Authors/Deciders fields + body text)
- memory/constitution.md (1 instance)
- docs/superpowers/plans/2026-04-10-lan-mode.md (Users/taodeng paths → $HOME)

Also:
- Replaces specific machine hostnames (Taos-Mac-mini, Taos-MacBook-Pro)
  with role-based names (home-mac, travel-macbook) in ADR 0001 (if
  committed; currently untracked).
- Replaces /Users/taodeng/ paths with $HOME/ in old plan doc.

NOTE: git history still contains the personal info. See postmortem
(issue TBD) for whether to rewrite history via git-filter-repo.

Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 11:01:44 +10:00

66 lines
5.3 KiB
Markdown

# 0003 — `models.json` as Single Source of Truth
- **Date**: 2026-04-20
- **Status**: Accepted
- **Authors**: project maintainer (with AI drafting assistance)
- **Related**: PR #30, commit c6f7850; precursor to ADR 0004
## Context
OCP's model catalog (the mapping from short aliases like `sonnet` and `opus` to full model IDs with context-window metadata) had organically drifted into three independent locations:
1. `server.mjs``MODEL_MAP` and `MODELS` arrays, hardcoded at the top of the file. This was the runtime authority for `/v1/models` responses and alias resolution.
2. `setup.mjs` — a separate `MODELS` constant, unchanged since the v3.0 era. Used only at first-install time to seed user config; by v3.10 it was stale and listed no Claude 4.x models at all.
3. `~/.openclaw/openclaw.json` (on user machines) — written exactly once by `setup.mjs` during initial OCP install and never refreshed. A user who installed OCP in v3.0 and ran `ocp update` faithfully through v3.10 still had their OpenClaw config listing only three pre-Claude-4 models.
By the v3.10.0 release, Opus 4.7 was correctly present in location (1) and absent from (2) and (3). The symptom reaching users: native Claude Code saw the new model immediately (because it queries `/v1/models` live from server.mjs), but OpenClaw users saw nothing new, and new-installers via `setup.mjs` got an incomplete initial config. Three distinct bug reports in the two weeks following v3.10.0.
The drift was structural, not a bug in any one file. The files disagreed because there was no mechanism requiring them to agree.
## Decision
Extract all model metadata into `models.json` at the repo root. `server.mjs` and `setup.mjs` both read this file and derive their in-memory `MODEL_MAP`/`MODELS` structures from it. The file is committed to the repo; it is neither generated nor cached.
Shape (summarized):
- `models` — array of entries, each with `id` (full model ID), `alias` (short name), `context_window`, and flags where relevant.
- `default_alias` — which alias resolves when the client sends an unknown or empty model.
Migration approach:
1. Hand-populate `models.json` from the v3.10.0 `server.mjs` `MODEL_MAP` values.
2. Rewrite `server.mjs` to load and index `models.json` at startup.
3. Rewrite `setup.mjs` to derive its `MODELS` constant from the same file.
4. Verify byte-equivalence: the derived `MODEL_MAP` in v3.11.0 must be a byte-identical superset of the v3.10.0 hardcoded `MODEL_MAP`. This is checked by a one-shot comparison script during the refactor PR; no regression is permitted.
Post-refactor, the contract for adding a model is: edit `models.json`, open a PR, reviewer sanity-checks the `id` string against Anthropic's model announcement, merge. No other file changes.
## Consequences
**Positive**
- Single edit point eliminates the "updated one place, forgot the other" failure mode structurally.
- `setup.mjs`'s latent staleness is repaired as a side effect — new-installers now get a fresh model list.
- Opens the door to ADR 0004 (OpenClaw auto-sync), which requires a file-based SPOT to sync from.
- The `models.json` format is stable, Markdown-friendly JSON, easy to diff in code review.
**Negative**
- One additional file to load at server startup (negligible cost, but now a startup dependency).
- Schema drift risk: if anyone adds a new field to `models.json` that `server.mjs` or `setup.mjs` doesn't know about, the field is silently ignored. A future schema version tag may be warranted if the format grows.
- `models.json` parse failure is now a fatal startup error; previously, bad model config required editing source. Consider this a feature, not a regression.
**Follow-ons**
- ADR 0004 (OpenClaw auto-sync) consumes `models.json` directly in `scripts/sync-openclaw.mjs`.
- Future additions (per-model pricing, per-model capability flags, etc.) belong in `models.json`, not scattered back across `server.mjs`.
- The README "Available Models" table is now derived documentation and its source of truth should be pinned to `models.json` in the release_kit overlay.
## Alternatives considered
**(a) Keep the three locations, enforce sync by manual review discipline.** A reviewer checklist item: "did you update all three places?" Rejected: the drift had already demonstrated that manual discipline is insufficient when the three files are in unrelated sections of the diff. Human reviewers routinely miss the third file. The 2026-04-11 alignment drift had already taught the project that discipline-only approaches fail.
**(b) YAML with SOPS field-level encryption.** Some projects prefer YAML for multi-line string readability and use SOPS to encrypt sensitive fields. Rejected: OCP's model catalog contains no secrets — model IDs, aliases, and context windows are all public information published by Anthropic. YAML adds a parser dependency and SOPS adds a decryption step at startup, both for zero benefit. JSON is already native to Node, and `models.json` is easy to diff line-by-line in GitHub review UI.
**(c) Fetch the model list live from Anthropic at server start.** Rejected: `cli.js` does not perform this operation, so per `ALIGNMENT.md` Rule 2 it is out of scope for OCP. Additionally, a live fetch introduces a startup-time network dependency and an availability coupling to Anthropic that OCP is explicitly designed to avoid (OCP is the gateway, not another consumer).