instar 1.3.744 → 1.3.745

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.744",
3
+ "version": "1.3.745",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,9 +28,11 @@
28
28
  "test:contract": "node scripts/run-contract-tests.js",
29
29
  "test:contract:raw": "vitest run --config vitest.contract.config.ts",
30
30
  "test:all": "vitest run && vitest run --config vitest.integration.config.ts && vitest run --config vitest.e2e.config.ts",
31
- "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-no-unbounded-llm-spawn.js && node scripts/lint-no-unfunneled-credential-write.js && node scripts/lint-state-registry.js && node scripts/lint-store-retention-declared.js && node scripts/lint-no-wholefile-sync-read.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-sync-subprocess-chokepoint.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-guard-manifest.js && node scripts/lint-llm-attribution.js && node scripts/lint-no-mainthread-cartographer-walk.js && node scripts/lint-scrape-fixture-realness.js && node scripts/check-codex-rule1-drift.js && node scripts/lint-routing-registry-freshness.js && node scripts/lint-no-opus-claude-cli-gating.js",
31
+ "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-no-unbounded-llm-spawn.js && node scripts/lint-no-unfunneled-credential-write.js && node scripts/lint-state-registry.js && node scripts/lint-store-retention-declared.js && node scripts/lint-no-wholefile-sync-read.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-sync-subprocess-chokepoint.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-guard-manifest.js && node scripts/lint-llm-attribution.js && node scripts/lint-no-mainthread-cartographer-walk.js && node scripts/lint-scrape-fixture-realness.js && node scripts/check-codex-rule1-drift.js && node scripts/lint-routing-registry-freshness.js && node scripts/lint-no-opus-claude-cli-gating.js && node scripts/lint-model-registry-freshness.mjs",
32
32
  "lint:routing-registry": "node scripts/lint-routing-registry-freshness.js",
33
33
  "lint:no-opus-claude-cli-gating": "node scripts/lint-no-opus-claude-cli-gating.js",
34
+ "lint:model-freshness": "node scripts/lint-model-registry-freshness.mjs",
35
+ "lint:model-freshness:strict": "INSTAR_MODEL_FRESHNESS_STRICT=1 node scripts/lint-model-registry-freshness.mjs",
34
36
  "lint:no-unbounded-llm-spawn": "node scripts/lint-no-unbounded-llm-spawn.js",
35
37
  "lint:no-unbounded-llm-spawn:staged": "node scripts/lint-no-unbounded-llm-spawn.js --staged",
36
38
  "lint:destructive": "node scripts/lint-no-direct-destructive.js",
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lint-model-registry-freshness.mjs — the model-registry FRESHNESS ratchet.
4
+ *
5
+ * THE PROBLEM (2026-07-03, operator directive): Instar's per-provider "capable/
6
+ * latest/frontier" model pins rot SILENTLY. Nothing forces anyone to re-check
7
+ * that (e.g.) the gemini `capable` tier still points at a current model — so
8
+ * `gemini-2.5-pro` kept routing spec-review/converge work long after
9
+ * Gemini 3-class shipped. A stale pin is invisible until it degrades output.
10
+ *
11
+ * THE GUARD (model-id-AGNOSTIC by construction): a deterministic check with
12
+ * TWO teeth, both driven by scripts/model-registry-freshness.manifest.json —
13
+ * the single human-edit surface:
14
+ *
15
+ * TOOTH 1 — STALENESS. `lastReviewedAt` must be within `stalenessWindowDays`
16
+ * of today. An un-reviewed list ages out and fails LOUDLY, forcing a
17
+ * periodic "is each pin still frontier?" review + a date bump. This is the
18
+ * anti-rot mechanism: it fails even if no id ever changes.
19
+ *
20
+ * TOOTH 2 — DRIFT. Every pinned capable/latest model id (extracted live from
21
+ * the real source files via each pin's regex) must be a member of that
22
+ * door's `frontierAllowlist` (the CURRENT_FRONTIER_MODELS set). A pin that
23
+ * names a model not in the maintained allowlist fails — either the pin is
24
+ * stale, or the allowlist wasn't updated. To go green a human must reconcile
25
+ * the two, which IS the review.
26
+ *
27
+ * Plus: `flaggedStale[]` entries (known-stale-pending-operator-confirmation) are
28
+ * always printed as WARN lines and, under strict enforcement, count as findings.
29
+ *
30
+ * The guard NEVER hard-codes what the "right" model id is — it only asserts the
31
+ * pins and the allowlist agree and that the review is fresh. Swapping to a new
32
+ * frontier id is a manifest edit (allowlist + date), never a code change here.
33
+ *
34
+ * ENFORCEMENT (dark/reversible): manifest `enforcement` field —
35
+ * "report" (default) — prints findings, ALWAYS exits 0 (non-gating). Safe to
36
+ * wire into CI today while the current list is known-stale: it stays VISIBLE
37
+ * in the lint log without breaking the build.
38
+ * "strict" — exits 1 on any finding (staleness, drift, or a flaggedStale row).
39
+ * Flip here once the flagged door swaps are operator-confirmed + applied.
40
+ * Env INSTAR_MODEL_FRESHNESS_STRICT=1 forces strict for a one-off run.
41
+ *
42
+ * Exit codes:
43
+ * 0 — no findings, OR findings under "report" enforcement (non-gating).
44
+ * 1 — findings under "strict" enforcement, or a manifest/parse error.
45
+ *
46
+ * Usage:
47
+ * node scripts/lint-model-registry-freshness.mjs # honor manifest enforcement
48
+ * INSTAR_MODEL_FRESHNESS_STRICT=1 node scripts/lint-model-registry-freshness.mjs
49
+ *
50
+ * Test overrides:
51
+ * INSTAR_MODEL_FRESHNESS_MANIFEST=<path> # point at a fixture manifest
52
+ * INSTAR_MODEL_FRESHNESS_ROOT=<path> # resolve pin files under this root
53
+ * INSTAR_MODEL_FRESHNESS_NOW=<ISO date> # inject the clock (staleness tests)
54
+ */
55
+
56
+ import fs from 'node:fs';
57
+ import path from 'node:path';
58
+ import { fileURLToPath } from 'node:url';
59
+
60
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
61
+ const REPO_ROOT = process.env.INSTAR_MODEL_FRESHNESS_ROOT
62
+ ? path.resolve(process.env.INSTAR_MODEL_FRESHNESS_ROOT)
63
+ : path.resolve(__dirname, '..');
64
+ const MANIFEST_PATH = process.env.INSTAR_MODEL_FRESHNESS_MANIFEST
65
+ ? path.resolve(process.env.INSTAR_MODEL_FRESHNESS_MANIFEST)
66
+ : path.join(__dirname, 'model-registry-freshness.manifest.json');
67
+
68
+ /**
69
+ * Run the freshness check. Pure over its inputs (manifest + files under root +
70
+ * the injected clock), so the unit test drives it directly with fixtures.
71
+ * @returns {{ findings: string[], warnings: string[], info: string[], strict: boolean, error: string|null }}
72
+ */
73
+ export function checkModelRegistryFreshness({
74
+ manifestPath = MANIFEST_PATH,
75
+ repoRoot = REPO_ROOT,
76
+ now = process.env.INSTAR_MODEL_FRESHNESS_NOW
77
+ ? new Date(process.env.INSTAR_MODEL_FRESHNESS_NOW)
78
+ : new Date(),
79
+ forceStrict = process.env.INSTAR_MODEL_FRESHNESS_STRICT === '1',
80
+ } = {}) {
81
+ const findings = [];
82
+ const warnings = [];
83
+ const info = [];
84
+
85
+ let manifest;
86
+ try {
87
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
88
+ } catch (e) {
89
+ return { findings: [], warnings: [], info: [], strict: true, error: `cannot read/parse manifest ${manifestPath}: ${e.message}` };
90
+ }
91
+
92
+ const strict = forceStrict || manifest.enforcement === 'strict';
93
+
94
+ // --- TOOTH 1: staleness ---
95
+ const reviewedRaw = manifest.lastReviewedAt;
96
+ const reviewed = reviewedRaw ? new Date(reviewedRaw) : null;
97
+ const windowDays = Number(manifest.stalenessWindowDays);
98
+ if (!reviewed || Number.isNaN(reviewed.getTime())) {
99
+ findings.push(`STALENESS: manifest.lastReviewedAt is missing or unparseable (got ${JSON.stringify(reviewedRaw)}).`);
100
+ } else if (!Number.isFinite(windowDays) || windowDays <= 0) {
101
+ findings.push(`STALENESS: manifest.stalenessWindowDays is missing or invalid (got ${JSON.stringify(manifest.stalenessWindowDays)}).`);
102
+ } else {
103
+ const ageDays = Math.floor((now.getTime() - reviewed.getTime()) / 86_400_000);
104
+ if (ageDays > windowDays) {
105
+ findings.push(
106
+ `STALENESS: model registry last reviewed ${reviewedRaw} (${ageDays}d ago) exceeds the ${windowDays}d window. ` +
107
+ `Re-review each capable/latest pin against current frontier, update frontierAllowlist if a model has moved, then bump lastReviewedAt.`
108
+ );
109
+ } else {
110
+ info.push(`Staleness OK: reviewed ${reviewedRaw} (${ageDays}d ago, window ${windowDays}d).`);
111
+ }
112
+ }
113
+
114
+ // --- TOOTH 2: drift (pin id must be in its door's frontier allowlist) ---
115
+ const allowlist = manifest.frontierAllowlist || {};
116
+ for (const pin of manifest.pins || []) {
117
+ const abs = path.join(repoRoot, pin.file);
118
+ let src;
119
+ try {
120
+ src = fs.readFileSync(abs, 'utf8');
121
+ } catch {
122
+ findings.push(`DRIFT: pin '${pin.id}' references ${pin.file} which is missing under ${repoRoot} (a pin site moved or was deleted — re-anchor it).`);
123
+ continue;
124
+ }
125
+ let re;
126
+ try {
127
+ re = new RegExp(pin.regex);
128
+ } catch (e) {
129
+ findings.push(`DRIFT: pin '${pin.id}' has an invalid regex (${e.message}).`);
130
+ continue;
131
+ }
132
+ const m = src.match(re);
133
+ if (!m) {
134
+ findings.push(`DRIFT: pin '${pin.id}' pattern did not match in ${pin.file} (the pinned site changed shape — re-anchor the regex).`);
135
+ continue;
136
+ }
137
+ // All capture groups are candidate model ids (e.g. default + escalated).
138
+ const ids = m.slice(1).filter(Boolean);
139
+ const doorAllow = allowlist[pin.door] || [];
140
+ for (const id of ids) {
141
+ if (!doorAllow.includes(id)) {
142
+ findings.push(
143
+ `DRIFT: pin '${pin.id}' (${pin.door}) pins '${id}' in ${pin.file}, which is NOT in frontierAllowlist['${pin.door}'] = [${doorAllow.join(', ')}]. ` +
144
+ `Either the pin is stale or the allowlist wasn't updated — reconcile the two (operator-confirm the frontier id).`
145
+ );
146
+ } else {
147
+ info.push(`Drift OK: ${pin.door} '${pin.id}' -> '${id}' (in allowlist).`);
148
+ }
149
+ }
150
+ }
151
+
152
+ // --- flaggedStale (known-stale-pending-confirmation): always WARN; strict counts them ---
153
+ for (const f of manifest.flaggedStale || []) {
154
+ const line =
155
+ `FLAGGED-STALE: ${f.door} pin '${f.pin}' currently '${f.currentId}' -> suspected frontier '${f.suspectedFrontier}'. ` +
156
+ `${f.evidence || ''} ${f.note || ''}`.trim();
157
+ warnings.push(line);
158
+ if (strict) findings.push(line);
159
+ }
160
+
161
+ return { findings, warnings, info, strict, error: null };
162
+ }
163
+
164
+ // --- CLI entry ---
165
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
166
+ if (isMain) {
167
+ const res = checkModelRegistryFreshness();
168
+ const B = '\x1b[1m', R = '\x1b[0m', Y = '\x1b[33m', RED = '\x1b[31m', G = '\x1b[32m';
169
+ console.log(`${B}[lint-model-registry-freshness]${R} enforcement=${res.strict ? 'strict' : 'report'}`);
170
+
171
+ if (res.error) {
172
+ console.error(`${RED}ERROR:${R} ${res.error}`);
173
+ process.exit(1);
174
+ }
175
+ for (const i of res.info) console.log(` ${G}ok${R} ${i}`);
176
+ for (const w of res.warnings) console.log(` ${Y}warn${R} ${w}`);
177
+ for (const f of res.findings) console.log(` ${RED}FIND${R} ${f}`);
178
+
179
+ if (res.findings.length === 0) {
180
+ console.log(`${G}PASS${R} — model registry pins fresh and in-allowlist.`);
181
+ process.exit(0);
182
+ }
183
+ if (res.strict) {
184
+ console.error(`${RED}FAIL${R} — ${res.findings.length} finding(s) under strict enforcement.`);
185
+ process.exit(1);
186
+ }
187
+ console.log(
188
+ `${Y}REPORT-ONLY${R} — ${res.findings.length} finding(s) surfaced but NOT gating (manifest enforcement="report"). ` +
189
+ `Resolve them, then flip enforcement to "strict".`
190
+ );
191
+ process.exit(0);
192
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "$comment": "Frontier-model allowlist + staleness gate for Instar's per-provider 'capable/latest' model pins. This file is the SINGLE human-edit surface that keeps the model registry from silently rotting. See docs/LLM-ROUTING-REGISTRY.md and scripts/lint-model-registry-freshness.mjs. Two teeth: (1) STALENESS — lastReviewedAt must be within stalenessWindowDays; (2) DRIFT — every pinned capable model id must be a member of frontierAllowlist[door]. To pass the check a human must confirm each capable pin is genuinely current frontier AND bump lastReviewedAt. An un-reviewed list fails loudly.",
3
+ "lastReviewedAt": "2026-07-03",
4
+ "stalenessWindowDays": 45,
5
+ "enforcement": "report",
6
+ "$enforcementNote": "'report' = non-gating (prints findings, always exits 0) — the dark/reversible default while the current list is known-stale. Flip to 'strict' (exits 1 on any finding) ONLY after the flaggedStale door swaps below are operator-confirmed and applied. Env INSTAR_MODEL_FRESHNESS_STRICT=1 forces strict for a one-off run.",
7
+ "doors": {
8
+ "claude-code": {
9
+ "accessMethod": "Claude Code CLI (subscription/OAuth)",
10
+ "status": "alive",
11
+ "note": "Primary door. Tier aliases opus/sonnet/haiku auto-resolve to latest and do NOT rot; only concrete claude-*-N ids below are pinned."
12
+ },
13
+ "anthropic-headless": {
14
+ "accessMethod": "claude -p one-shot (Agent SDK credit pot)",
15
+ "status": "alive",
16
+ "note": "Background/internal calls. Billing shifts to interactive pool after 2026-06-15 per subscriptionPath."
17
+ },
18
+ "codex-cli": {
19
+ "accessMethod": "codex exec (ChatGPT account)",
20
+ "status": "referenced-not-installed",
21
+ "note": "codex CLI is NOT installed on this machine (which codex -> not found), yet componentFrameworks routes job/Usher/TopicIntentExtractor to it -> those fall back at runtime. Door-liveness gap flagged to operator."
22
+ },
23
+ "gemini-cli": {
24
+ "accessMethod": "gemini -m <model> (Homebrew formula, OAuth)",
25
+ "status": "alive-but-deprecated-formula",
26
+ "note": "Homebrew gemini-cli formula DEPRECATED (upstream-unsupported, disabled 2026-12-18). CLI v0.25.2 still runs. Default model still gemini-2.5-pro; CLI already knows gemini-3-pro-preview / gemini-3-flash-preview. Migrate off the deprecated formula (npm @google/gemini-cli, or an OpenRouter/pi door)."
27
+ },
28
+ "pi-cli": {
29
+ "accessMethod": "pi --model <provider>/<id> (multi-provider: openai-codex, openrouter, ...)",
30
+ "status": "alive",
31
+ "note": "Multi-provider consolidation door. Can reach OpenRouter (one door to many frontier models; vault key metered_openrouter_bench present). pi policy DENIES anthropic/claude via openrouter passthrough by default to protect subscription billing."
32
+ }
33
+ },
34
+ "frontierAllowlist": {
35
+ "$comment": "CURRENT_FRONTIER_MODELS — the set of model ids considered acceptable-current for each door. A pinned capable model NOT in its door's list fails the DRIFT tooth. Add a new frontier id here (operator-confirmed) at the same time you update the pin.",
36
+ "claude-code": [
37
+ "claude-opus-4-8",
38
+ "claude-fable-5"
39
+ ],
40
+ "anthropic-headless": [
41
+ "claude-opus-4-6",
42
+ "claude-sonnet-4-6",
43
+ "claude-haiku-4-5"
44
+ ],
45
+ "codex-cli": [
46
+ "gpt-5.5"
47
+ ],
48
+ "gemini-cli": [
49
+ "gemini-2.5-pro"
50
+ ]
51
+ },
52
+ "pins": [
53
+ {
54
+ "id": "gemini-capable-tier",
55
+ "door": "gemini-cli",
56
+ "tier": "capable",
57
+ "file": "src/providers/adapters/gemini-cli/models.ts",
58
+ "regex": "capable:\\s*'((?:claude|gpt|gemini)-[^']+)'",
59
+ "note": "TIER_TO_MODEL.capable in the gemini adapter."
60
+ },
61
+ {
62
+ "id": "codex-capable-tier",
63
+ "door": "codex-cli",
64
+ "tier": "capable",
65
+ "file": "src/providers/adapters/openai-codex/models.ts",
66
+ "regex": "capable:\\s*'((?:claude|gpt|gemini)-[^']+)'",
67
+ "note": "TIER_TO_MODEL.capable in the codex adapter."
68
+ },
69
+ {
70
+ "id": "anthropic-headless-capable-tier",
71
+ "door": "anthropic-headless",
72
+ "tier": "capable",
73
+ "file": "src/providers/adapters/anthropic-headless/models.ts",
74
+ "regex": "capable:\\s*'((?:claude|gpt|gemini)-[^']+)'",
75
+ "note": "TIER_TO_MODEL.capable in the anthropic-headless adapter."
76
+ },
77
+ {
78
+ "id": "claude-tier-escalation-default-escalated",
79
+ "door": "claude-code",
80
+ "tier": "capable",
81
+ "file": "src/core/ModelTierEscalation.ts",
82
+ "regex": "'claude-code':\\s*\\{\\s*default:\\s*'([^']+)',\\s*escalated:\\s*'([^']+)'",
83
+ "note": "DEFAULT_TIER_ESCALATION.frameworks['claude-code'] default + escalated ids."
84
+ }
85
+ ],
86
+ "flaggedStale": [
87
+ {
88
+ "door": "gemini-cli",
89
+ "pin": "gemini-capable-tier",
90
+ "currentId": "gemini-2.5-pro",
91
+ "suspectedFrontier": "gemini-3-pro-preview",
92
+ "evidence": "gemini CLI v0.25.2 accepts gemini-3-pro-preview / gemini-3-flash-preview (in gemini-cli-core known-models); operator referenced 'Gemini 3-class'. Homebrew formula also deprecated (disabled 2026-12-18).",
93
+ "note": "OPERATOR TO CONFIRM exact frontier id before the pin+allowlist are updated. Also present at src/core/frameworkSessionLaunch.ts, src/commands/route.ts (KNOWN_MODELS), src/server/routes.ts."
94
+ },
95
+ {
96
+ "door": "codex-cli",
97
+ "pin": "codex-capable-tier",
98
+ "currentId": "gpt-5.5",
99
+ "suspectedFrontier": "gpt-5.6 (Sol) — UNVERIFIED",
100
+ "evidence": "codex CLI NOT installed on this machine, so newer accepted ids could NOT be probed. Operator referenced 'GPT-5.6 Sol'.",
101
+ "note": "OPERATOR TO CONFIRM. Cannot verify from a live probe (codex door not installed)."
102
+ }
103
+ ]
104
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-04T00:06:57.116Z",
5
- "instarVersion": "1.3.744",
4
+ "generatedAt": "2026-07-04T00:24:28.139Z",
5
+ "instarVersion": "1.3.745",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,19 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Repo-internal CI tooling only — a deterministic lint + manifest + unit test that
9
+ flags when per-provider `capable/latest/frontier` model pins go stale. Ships in
10
+ report mode (non-gating); changes no model id and no runtime path. No user-facing
11
+ or agent-facing behavior change.
12
+
13
+ ## What to Tell Your User
14
+
15
+ None — internal change (no user-facing surface).
16
+
17
+ ## Summary of New Capabilities
18
+
19
+ None — internal change (no user-facing surface).
@@ -0,0 +1,53 @@
1
+ # Side-effects — Model-registry freshness guard
2
+
3
+ **Change:** add a deterministic, model-id-AGNOSTIC lint that flags when Instar's
4
+ per-provider "capable/latest/frontier" model pins have gone stale, plus its
5
+ manifest (the human-edit frontier allowlist + `lastReviewedAt`) and a unit test.
6
+ Ships **non-gating** (`enforcement: "report"`), dark/reversible.
7
+
8
+ ## Files touched
9
+ - `scripts/lint-model-registry-freshness.mjs` (new) — the checker. Two teeth:
10
+ staleness window + per-door allowlist-membership drift. Exports a pure
11
+ `checkModelRegistryFreshness()` for the test; CLI honors `enforcement`.
12
+ - `scripts/model-registry-freshness.manifest.json` (new) — the single edit
13
+ surface: `frontierAllowlist`, `pins` (file+regex), `lastReviewedAt`,
14
+ `stalenessWindowDays`, `enforcement`, `doors` (door-status map), `flaggedStale`.
15
+ - `tests/unit/model-registry-freshness.test.ts` (new) — 11 tests, both sides of
16
+ both teeth + gating + the shipped manifest's self-consistency.
17
+ - `package.json` — appended the lint to the `lint` chain (runs in report mode,
18
+ exits 0, VISIBLE in CI logs) + `lint:model-freshness[:strict]` aliases.
19
+ - `docs/LLM-ROUTING-REGISTRY.md` — a freshness-guard pointer note.
20
+
21
+ ## Blast radius
22
+ - **CI:** the lint is added to `npm run lint`. In `report` mode it ALWAYS exits
23
+ 0, so it cannot break the build while the current list is known-stale. It only
24
+ prints findings/warnings. Flipping `enforcement: "strict"` (a future,
25
+ operator-gated one-line manifest edit) is what makes it gate.
26
+ - **Runtime:** NONE. This is build/CI tooling only — it never runs in the server,
27
+ never touches a session, never changes model routing. It reads source files
28
+ and a JSON manifest; it writes nothing.
29
+ - **Model IDs:** UNCHANGED. This change deliberately does NOT swap any pinned
30
+ model id — those swaps wait on operator confirmation of exact frontier ids.
31
+ The manifest's allowlist is seeded with the CURRENTLY-pinned ids so drift is
32
+ green today; the known-stale pins are carried in `flaggedStale` as warnings.
33
+
34
+ ## Reversibility
35
+ - Delete the two new scripts + test + revert the `package.json`/doc edits.
36
+ - Or set `enforcement` stays `report` forever (inert-but-visible).
37
+ - No migration, no config default, no state file, no template change.
38
+
39
+ ## Rollback lever
40
+ - Remove `&& node scripts/lint-model-registry-freshness.mjs` from the `lint`
41
+ script, or set the manifest `enforcement` to any value other than `strict`.
42
+
43
+ ## Migration parity
44
+ - N/A — this is repo-internal CI tooling, not an agent-installed file. No hook,
45
+ config default, CLAUDE.md template section, or built-in skill is changed, so
46
+ `PostUpdateMigrator` needs no entry.
47
+
48
+ ## Follow-ups (operator-gated, NOT in this change)
49
+ 1. Confirm exact frontier ids (`gemini-3-pro-preview`?, `gpt-5.6 Sol`?) and swap
50
+ the pins + update `frontierAllowlist` + bump `lastReviewedAt` in one change.
51
+ 2. Then flip `enforcement: "strict"` so the guard gates.
52
+ 3. Door-liveness gaps surfaced by the audit: codex CLI not installed but routed
53
+ to; Homebrew gemini-cli formula deprecated (disabled 2026-12-18).