instar 1.3.780 → 1.3.781

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.
Files changed (42) hide show
  1. package/dashboard/index.html +161 -0
  2. package/dist/core/PostUpdateMigrator.d.ts +21 -0
  3. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  4. package/dist/core/PostUpdateMigrator.js +59 -0
  5. package/dist/core/PostUpdateMigrator.js.map +1 -1
  6. package/dist/core/routingPriceAuthority.d.ts +0 -0
  7. package/dist/core/routingPriceAuthority.d.ts.map +1 -0
  8. package/dist/core/routingPriceAuthority.js +0 -0
  9. package/dist/core/routingPriceAuthority.js.map +1 -0
  10. package/dist/core/routingSpendView.d.ts +172 -0
  11. package/dist/core/routingSpendView.d.ts.map +1 -0
  12. package/dist/core/routingSpendView.js +215 -0
  13. package/dist/core/routingSpendView.js.map +1 -0
  14. package/dist/core/types.d.ts +25 -0
  15. package/dist/core/types.d.ts.map +1 -1
  16. package/dist/core/types.js.map +1 -1
  17. package/dist/monitoring/FeatureMetricsLedger.d.ts +74 -2
  18. package/dist/monitoring/FeatureMetricsLedger.d.ts.map +1 -1
  19. package/dist/monitoring/FeatureMetricsLedger.js +233 -8
  20. package/dist/monitoring/FeatureMetricsLedger.js.map +1 -1
  21. package/dist/scaffold/templates.d.ts.map +1 -1
  22. package/dist/scaffold/templates.js +2 -1
  23. package/dist/scaffold/templates.js.map +1 -1
  24. package/dist/server/AgentServer.d.ts +1 -0
  25. package/dist/server/AgentServer.d.ts.map +1 -1
  26. package/dist/server/AgentServer.js +35 -2
  27. package/dist/server/AgentServer.js.map +1 -1
  28. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  29. package/dist/server/CapabilityIndex.js +12 -0
  30. package/dist/server/CapabilityIndex.js.map +1 -1
  31. package/dist/server/routes.d.ts +3 -0
  32. package/dist/server/routes.d.ts.map +1 -1
  33. package/dist/server/routes.js +46 -0
  34. package/dist/server/routes.js.map +1 -1
  35. package/package.json +1 -1
  36. package/scripts/routing-price-refresh.mjs +216 -0
  37. package/scripts/routing-prices.manifest.json +64 -0
  38. package/src/data/builtin-manifest.json +64 -64
  39. package/src/scaffold/templates/jobs/instar/routing-price-refresh.md +30 -0
  40. package/src/scaffold/templates.ts +2 -1
  41. package/upgrades/1.3.781.md +25 -0
  42. package/upgrades/side-effects/routing-spend-increment-a.md +84 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.780",
3
+ "version": "1.3.781",
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",
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * routing-price-refresh.mjs — the deterministic prober behind the OFF-by-default
4
+ * `routing-price-refresh` job (docs/specs/routing-control-room-spend-alerts.md, FD-8).
5
+ *
6
+ * It re-confirms published per-token prices for the metered doors and writes them into
7
+ * the MACHINE-LOCAL OBSERVED CACHE ONLY (`.instar/routing-prices.observed.json`) —
8
+ * STRUCTURALLY never the canonical manifest (a lint + unit test assert this). Observed
9
+ * points feed the REPORTING view + the promote-me drift hint; they are gate-INELIGIBLE
10
+ * by construction (in Increment B the money gate reads only the canonical manifest).
11
+ *
12
+ * FD-8 discipline (all enforced here):
13
+ * - FORWARD-ONLY: every written point has `effectiveAt = today (UTC, day-aligned)`,
14
+ * `corrects: null`. It can never write a backdated point or a correction.
15
+ * - FREE-PROBE FIRST: `--scope free-probes` (default) queries only public, no-auth
16
+ * model-list endpoints (OpenRouter). Metered / web-verify probes are MANUAL-ONLY and
17
+ * refused unless a positive `--budget-usd` is passed (default 0 → refuse) — an
18
+ * unknown price refuses rather than guesses.
19
+ * - SANE-PRICE VALIDATION: a candidate failing the range / cached≤input checks is
20
+ * dropped (never written), matching the reporting authority's fail-closed load.
21
+ *
22
+ * Pure core (parse / forward-only-merge / validate) is unit-tested with fixtures — no
23
+ * network in tests. Usage:
24
+ * node scripts/routing-price-refresh.mjs [--scope free-probes|+liveness|+web-verify]
25
+ * [--budget-usd N] [--dry-run] [--out <path>] [--project-dir <dir>] [--state-dir <dir>]
26
+ */
27
+
28
+ import * as fs from 'node:fs';
29
+ import * as path from 'node:path';
30
+
31
+ /** The metered (door → canonical model ids) we track prices for — kept in sync with the manifest. */
32
+ export const TRACKED = {
33
+ 'openrouter-api': ['openai/gpt-5.5', 'anthropic/claude-opus-4-8'],
34
+ 'gemini-api': ['gemini-3.1-flash-lite'],
35
+ 'groq-api': ['openai/gpt-oss-120b'],
36
+ };
37
+
38
+ /** UTC-day-aligned ISO (T00:00:00Z) for a timestamp — every observed point is day-aligned (FD-18). */
39
+ export function dayAlignedIso(ms) {
40
+ const d = new Date(ms);
41
+ return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())).toISOString();
42
+ }
43
+
44
+ function canonical(id) {
45
+ return String(id ?? '').trim().toLowerCase();
46
+ }
47
+
48
+ /** Sane-price validation (mirrors routingPriceAuthority.isValidPricePoint's money-safety checks). */
49
+ export function isSanePoint(p) {
50
+ if (!p || typeof p !== 'object') return false;
51
+ if (typeof p.door !== 'string' || !p.door.trim()) return false;
52
+ if (typeof p.modelId !== 'string' || !p.modelId.trim()) return false;
53
+ if (typeof p.inPerMtok !== 'number' || !Number.isFinite(p.inPerMtok) || p.inPerMtok < 0) return false;
54
+ if (typeof p.outPerMtok !== 'number' || !Number.isFinite(p.outPerMtok) || p.outPerMtok < 0) return false;
55
+ if (p.cachedInPerMtok !== undefined) {
56
+ if (typeof p.cachedInPerMtok !== 'number' || !Number.isFinite(p.cachedInPerMtok) || p.cachedInPerMtok < 0 || p.cachedInPerMtok > p.inPerMtok) return false;
57
+ }
58
+ if (typeof p.effectiveAt !== 'string' || dayAlignedIso(Date.parse(p.effectiveAt)) !== p.effectiveAt) return false;
59
+ return true;
60
+ }
61
+
62
+ /**
63
+ * Parse OpenRouter's public /models payload into candidate observed points for the
64
+ * TRACKED openrouter models. OpenRouter reports `pricing.prompt`/`.completion` as USD
65
+ * PER TOKEN (string); we convert to USD per MILLION tokens. Pure.
66
+ */
67
+ export function parseOpenRouterModels(payload, nowMs) {
68
+ const effectiveAt = dayAlignedIso(nowMs);
69
+ const out = [];
70
+ const data = payload && Array.isArray(payload.data) ? payload.data : [];
71
+ const tracked = new Set(TRACKED['openrouter-api'].map(canonical));
72
+ for (const m of data) {
73
+ if (!m || typeof m !== 'object') continue;
74
+ const modelId = canonical(m.id);
75
+ if (!tracked.has(modelId)) continue;
76
+ const pr = m.pricing;
77
+ if (!pr) continue;
78
+ const inPerMtok = Number(pr.prompt) * 1e6;
79
+ const outPerMtok = Number(pr.completion) * 1e6;
80
+ const point = {
81
+ door: 'openrouter-api',
82
+ modelId,
83
+ inPerMtok: Number.isFinite(inPerMtok) ? round6(inPerMtok) : NaN,
84
+ outPerMtok: Number.isFinite(outPerMtok) ? round6(outPerMtok) : NaN,
85
+ effectiveAt,
86
+ recordedAt: new Date(nowMs).toISOString(),
87
+ source: 'openrouter-models-api',
88
+ corrects: null,
89
+ };
90
+ if (isSanePoint(point)) out.push(point);
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /**
96
+ * FORWARD-ONLY merge into the observed cache: keep existing observed points, and add a
97
+ * new candidate ONLY when its effectiveAt is ≥ every existing point for the same
98
+ * (door, model) — never rewrite history, never a same-day duplicate. Pure.
99
+ */
100
+ export function mergeForwardOnly(existingPoints, candidates) {
101
+ const points = Array.isArray(existingPoints) ? existingPoints.slice() : [];
102
+ const latestByKey = new Map();
103
+ for (const p of points) {
104
+ const key = `${p.door} ${canonical(p.modelId)}`;
105
+ const eff = Date.parse(p.effectiveAt);
106
+ if (!latestByKey.has(key) || eff > latestByKey.get(key)) latestByKey.set(key, eff);
107
+ }
108
+ const added = [];
109
+ for (const c of candidates) {
110
+ if (!isSanePoint(c)) continue;
111
+ const key = `${c.door} ${canonical(c.modelId)}`;
112
+ const eff = Date.parse(c.effectiveAt);
113
+ const latest = latestByKey.get(key);
114
+ if (latest !== undefined && eff <= latest) continue; // forward-only: no backdate, no same-day dup
115
+ points.push(c);
116
+ latestByKey.set(key, eff);
117
+ added.push(c);
118
+ }
119
+ return { points, added };
120
+ }
121
+
122
+ function round6(n) {
123
+ return Math.round(n * 1e6) / 1e6;
124
+ }
125
+
126
+ function readObserved(outPath) {
127
+ try {
128
+ const raw = JSON.parse(fs.readFileSync(outPath, 'utf-8'));
129
+ return Array.isArray(raw?.points) ? raw.points : [];
130
+ } catch {
131
+ return [];
132
+ }
133
+ }
134
+
135
+ function writeObserved(outPath, points) {
136
+ const body = {
137
+ schemaVersion: 1,
138
+ _doc: 'MACHINE-LOCAL observed price cache written ONLY by scripts/routing-price-refresh.mjs (FD-8). REPORTING-ONLY — never gate-eligible; promote to the canonical manifest via the reviewed git/PIN path. Append/forward-only.',
139
+ points,
140
+ };
141
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
142
+ fs.writeFileSync(outPath, `${JSON.stringify(body, null, 2)}\n`);
143
+ }
144
+
145
+ async function fetchOpenRouterModels(timeoutMs = 8000) {
146
+ const ctrl = new AbortController();
147
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
148
+ try {
149
+ const resp = await fetch('https://openrouter.ai/api/v1/models', { signal: ctrl.signal });
150
+ if (!resp.ok) throw new Error(`openrouter models HTTP ${resp.status}`);
151
+ return await resp.json();
152
+ } finally {
153
+ clearTimeout(t);
154
+ }
155
+ }
156
+
157
+ function parseArgs(argv) {
158
+ const args = { scope: 'free-probes', budgetUsd: 0, dryRun: false, out: null, projectDir: process.cwd(), stateDir: null };
159
+ for (let i = 0; i < argv.length; i++) {
160
+ const a = argv[i];
161
+ if (a === '--scope') args.scope = argv[++i];
162
+ else if (a === '--budget-usd') args.budgetUsd = Number(argv[++i]);
163
+ else if (a === '--dry-run') args.dryRun = true;
164
+ else if (a === '--out') args.out = argv[++i];
165
+ else if (a === '--project-dir') args.projectDir = argv[++i];
166
+ else if (a === '--state-dir') args.stateDir = argv[++i];
167
+ }
168
+ return args;
169
+ }
170
+
171
+ async function main() {
172
+ const args = parseArgs(process.argv.slice(2));
173
+ const stateDir = args.stateDir ?? path.join(args.projectDir, '.instar');
174
+ const outPath = args.out ?? path.join(stateDir, 'routing-prices.observed.json');
175
+ const now = Date.now();
176
+
177
+ if (args.scope !== 'free-probes' && !(args.budgetUsd > 0)) {
178
+ // Metered / web-verify probes are MANUAL-ONLY + budget-capped (FD-8). No budget → refuse.
179
+ console.error(`[routing-price-refresh] scope '${args.scope}' requires a positive --budget-usd (metered probes are manual-only, budget-fail-closed). Refusing.`);
180
+ process.exit(2);
181
+ return;
182
+ }
183
+
184
+ const candidates = [];
185
+ const notes = [];
186
+ // Free scope: only publicly queryable doors. OpenRouter's /models is public + no-auth.
187
+ try {
188
+ const payload = await fetchOpenRouterModels();
189
+ const pts = parseOpenRouterModels(payload, now);
190
+ candidates.push(...pts);
191
+ notes.push(`openrouter-api: ${pts.length} tracked model price(s) probed`);
192
+ } catch (err) {
193
+ notes.push(`openrouter-api: probe failed (${err?.message ?? err}) — skipped, no data written for this door`);
194
+ }
195
+ // gemini-api / groq-api need a key → out of the free scope. Honestly reported, never guessed.
196
+ notes.push('gemini-api, groq-api: need an API key → not in free-probe scope (manual metered probe only)');
197
+
198
+ const existing = readObserved(outPath);
199
+ const { points, added } = mergeForwardOnly(existing, candidates);
200
+
201
+ if (args.dryRun) {
202
+ console.log(JSON.stringify({ dryRun: true, wouldAdd: added, notes }, null, 2));
203
+ return;
204
+ }
205
+ if (added.length > 0) writeObserved(outPath, points);
206
+ console.log(JSON.stringify({ added: added.length, totalObserved: points.length, out: outPath, notes }, null, 2));
207
+ }
208
+
209
+ // Only run when invoked directly (import for unit tests stays side-effect-free).
210
+ const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname);
211
+ if (invokedDirectly) {
212
+ main().catch((err) => {
213
+ console.error('[routing-price-refresh] fatal:', err?.message ?? err);
214
+ process.exit(1);
215
+ });
216
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": 1,
4
+ "_doc": "Canonical, human-reviewed price authority for the Routing Control Room spend view (docs/specs/routing-control-room-spend-alerts.md, Layer 1). USD per MILLION tokens, per door+model, effective-dated. APPEND-ONLY: a change or correction ADDS a point; points are never edited in place. GENERIC PUBLISHED prices only — operator-specific deals live in the machine-local overlay (.instar/routing-prices.overlay.json), never here. Written ONLY by a human path (a reviewed git commit, or — in Increment B — the PIN-gated promotion action). Every point's effectiveAt MUST be UTC-day-aligned (T00:00:00Z). In Increment A this manifest feeds the REPORTING view only; no money gate consumes it yet (the gate is Increment B).",
5
+ "doors": {
6
+ "gemini-api": {
7
+ "freshnessSlaDays": 45,
8
+ "staleMode": "book-conservative-max",
9
+ "conservativeMax": { "inPerMtok": 2.0, "outPerMtok": 12.0 }
10
+ },
11
+ "openrouter-api": {
12
+ "freshnessSlaDays": 45,
13
+ "staleMode": "book-conservative-max",
14
+ "conservativeMax": { "inPerMtok": 15.0, "outPerMtok": 90.0 }
15
+ },
16
+ "groq-api": {
17
+ "freshnessSlaDays": 45,
18
+ "staleMode": "book-conservative-max",
19
+ "conservativeMax": { "inPerMtok": 2.0, "outPerMtok": 8.0 }
20
+ }
21
+ },
22
+ "points": [
23
+ {
24
+ "door": "gemini-api",
25
+ "modelId": "gemini-3.1-flash-lite",
26
+ "inPerMtok": 0.25,
27
+ "outPerMtok": 1.5,
28
+ "effectiveAt": "2026-07-01T00:00:00Z",
29
+ "recordedAt": "2026-07-05T00:00:00Z",
30
+ "source": "published-generic-list",
31
+ "corrects": null
32
+ },
33
+ {
34
+ "door": "openrouter-api",
35
+ "modelId": "openai/gpt-5.5",
36
+ "inPerMtok": 5.0,
37
+ "outPerMtok": 30.0,
38
+ "effectiveAt": "2026-07-01T00:00:00Z",
39
+ "recordedAt": "2026-07-05T00:00:00Z",
40
+ "source": "published-generic-list",
41
+ "corrects": null
42
+ },
43
+ {
44
+ "door": "openrouter-api",
45
+ "modelId": "anthropic/claude-opus-4-8",
46
+ "inPerMtok": 5.0,
47
+ "outPerMtok": 25.0,
48
+ "effectiveAt": "2026-07-01T00:00:00Z",
49
+ "recordedAt": "2026-07-05T00:00:00Z",
50
+ "source": "published-generic-list",
51
+ "corrects": null
52
+ },
53
+ {
54
+ "door": "groq-api",
55
+ "modelId": "openai/gpt-oss-120b",
56
+ "inPerMtok": 0.2,
57
+ "outPerMtok": 0.8,
58
+ "effectiveAt": "2026-07-01T00:00:00Z",
59
+ "recordedAt": "2026-07-05T00:00:00Z",
60
+ "source": "published-generic-list",
61
+ "corrects": null
62
+ }
63
+ ]
64
+ }