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.
|
|
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:
|
|
5
|
-
"instarVersion": "1.3.
|
|
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).
|