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

5.3 KiB

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.mjsMODEL_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).