@ngockhoale/ukit 3.3.2 → 3.4.0
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/CHANGELOG.md +44 -0
- package/manifests/engineConformance.yaml +17 -1
- package/manifests/hostCapabilities.yaml +68 -1
- package/manifests/platform.full.yaml +138 -0
- package/manifests/platform.user.yaml +255 -3
- package/package.json +1 -1
- package/scripts/bench/subagent-orchestrator-corpus.mjs +275 -0
- package/scripts/bench/subagent-orchestrator-eval.mjs +565 -0
- package/scripts/probe/codex-capability-probe.mjs +169 -0
- package/src/cli/commands/doctor.js +168 -0
- package/src/cli/commands/indexTools.js +7 -0
- package/src/cli/commands/metrics.js +66 -2
- package/src/cli/commands/playbook.js +4 -4
- package/src/cli/commands/vm.js +49 -8
- package/src/core/agentRuntime/adapters.js +328 -27
- package/src/core/agentRuntime/artifacts.js +89 -0
- package/src/core/agentRuntime/context.js +345 -1
- package/src/core/agentRuntime/contract.js +296 -0
- package/src/core/agentRuntime/eventStore.js +176 -0
- package/src/core/agentRuntime/shadowRun.js +481 -5
- package/src/core/agentRuntime/telemetry.js +121 -0
- package/src/core/observability/emit/lifecycle.js +68 -1
- package/src/core/observability/emit/sessionBoot.js +393 -0
- package/src/core/observability/privacy/allowlist.js +10 -1
- package/src/core/observability/schema/registry.js +10 -0
- package/src/core/runtimeConfig.js +133 -0
- package/src/core/userPlaybooks.js +18 -3
- package/src/decision/registry.js +19 -0
- package/src/diagnostics/feedbackEvents.js +7 -4
- package/src/diagnostics/routeOutcomes.js +51 -6
- package/src/diagnostics/skillAccuracy.js +43 -3
- package/src/index/crossCheckMatrix.js +412 -0
- package/src/index/fixLoopEscalation.js +453 -0
- package/src/index/playbookRegistry.js +691 -0
- package/src/index/reviewPolicy.js +368 -0
- package/src/index/routeResolver.js +915 -0
- package/src/index/sessionHistoryExtractor.js +359 -0
- package/src/index/taskRouting.js +764 -581
- package/src/index/tierSelection.js +308 -0
- package/src/index/verificationMap.js +404 -0
- package/template_project/.claude/hooks/observability-emit.mjs +14 -0
- package/template_project/.claude/hooks/record-execution.mjs +19 -1
- package/template_project/.claude/hooks/skill-router.sh +691 -25
- package/template_project/.claude/hooks/verification-guard.sh +230 -1
- package/template_project/.claude/settings.json +2 -2
- package/template_project/.claude/ukit/index/cross-check-matrix.mjs +415 -0
- package/template_project/.claude/ukit/index/fix-loop-escalation.mjs +456 -0
- package/template_project/.claude/ukit/index/playbook-registry.mjs +690 -0
- package/template_project/.claude/ukit/index/review-panel-aggregate.mjs +20 -2
- package/template_project/.claude/ukit/index/review-policy.mjs +376 -0
- package/template_project/.claude/ukit/index/route-resolver.mjs +1059 -0
- package/template_project/.claude/ukit/index/route-task.mjs +1253 -846
- package/template_project/.claude/ukit/index/session-history-extractor.mjs +362 -0
- package/template_project/.claude/ukit/index/tier-selection.mjs +309 -0
- package/template_project/.claude/ukit/index/verification-map.mjs +403 -0
- package/template_project/.claude/ukit/index/worktree-sweep.mjs +195 -0
- package/template_project/.claude/ukit/runtime/execution-ledger.mjs +789 -11
- package/template_project/.claude/ukit/runtime/observability-emit.mjs +1102 -0
- package/template_project/.claude/ukit/runtime/reinject-context.mjs +9 -1
- package/template_project/.claude/ukit/runtime/resumable-run.mjs +149 -5
- package/template_project/.claude/ukit/runtime/stop-coordinator.mjs +323 -6
- package/template_project/.codex/README.md +8 -0
- package/template_project/.omp/hooks/pre/ukit-bridge.js +8 -1
- package/template_project/ukit/README.md +1 -1
- package/template_project/ukit/storage/config.json +20 -0
- package/template_user/playbooks/architecture-decision.md +28 -0
- package/template_user/playbooks/autonomous-run.md +43 -0
- package/template_user/playbooks/autopilot-full.md +59 -0
- package/template_user/playbooks/autopilot-stack.md +54 -0
- package/template_user/playbooks/babysit.md +39 -0
- package/template_user/playbooks/bug-fix.md +3 -1
- package/template_user/playbooks/{issue-implementation.md → feature-implementation.md} +4 -2
- package/template_user/playbooks/hillclimb.md +44 -0
- package/template_user/playbooks/investigation.md +21 -0
- package/template_user/playbooks/migration.md +21 -0
- package/template_user/playbooks/open-pr.md +48 -0
- package/template_user/playbooks/orchestrate.md +45 -0
- package/template_user/playbooks/performance.md +33 -0
- package/template_user/playbooks/prototype.md +28 -0
- package/template_user/playbooks/refactor.md +19 -0
- package/template_user/playbooks/release.md +28 -0
- package/template_user/playbooks/runtime-forensics.md +23 -0
- package/template_user/playbooks/session-pickup.md +31 -0
- package/template_user/playbooks/shipping.md +53 -0
- package/template_user/playbooks/skill-evaluation.md +48 -0
- package/template_user/playbooks/small-feature.md +20 -0
- package/template_user/playbooks/verification-map.json +153 -0
- package/template_user/playbooks/verification.md +22 -0
- package/template_user/playbooks/worktree-cleanup.md +37 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// tierSelection.js — measured reliability → tier selection (C86 BL-019,
|
|
2
|
+
// SPEC FR-001 §5/§7/§8/§14).
|
|
3
|
+
//
|
|
4
|
+
// modelTier stops being purely static: the route-audit ↔ exec-ledger join
|
|
5
|
+
// (BL-003/004/007) produces measured per-(execution-mode, tier) reliability,
|
|
6
|
+
// every route logs the measured pick vs the static pick as a `tier-selection`
|
|
7
|
+
// shadow receipt, and the measured map becomes authoritative ONLY when the
|
|
8
|
+
// promotion gates pass (≥50 joined routes, ≥60% agreement, disagreements skew
|
|
9
|
+
// cheaper, no verdict regression). Thin data → the static map stays.
|
|
10
|
+
//
|
|
11
|
+
// Boundary rules (SPEC §14, ARCH §Safety Boundary):
|
|
12
|
+
// * Tier names only — 'lite'|'code'|'smart'. A model ID in the artifact is
|
|
13
|
+
// dropped at read time so it can never reach a route field or receipt.
|
|
14
|
+
// * `modelRoles` config is never written — the measured map is derived state
|
|
15
|
+
// under .ukit/storage/cache/, not user configuration.
|
|
16
|
+
// * handoff-model-guard.sh enforcement is unchanged (not touched here).
|
|
17
|
+
//
|
|
18
|
+
// Mirror parity: template_project/.claude/ukit/index/tier-selection.mjs
|
|
19
|
+
// carries the identical logic (the mirror cannot import src/). Both twins are
|
|
20
|
+
// locked by tests/consistency/tierSelectionParity.test.js.
|
|
21
|
+
|
|
22
|
+
import fs from 'node:fs/promises';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import crypto from 'node:crypto';
|
|
25
|
+
|
|
26
|
+
// Provider-neutral cost-tier vocabulary — literal mirror of the
|
|
27
|
+
// lite→code→smart ladder in src/core/executionContracts.js (MODEL_TIER_ORDER).
|
|
28
|
+
export const MEASURED_TIER_ORDER = Object.freeze(['lite', 'code', 'smart']);
|
|
29
|
+
|
|
30
|
+
// Derived cache artifact (SPEC §7): metrics/diagnostics writes, router reads.
|
|
31
|
+
export const TIER_MAP_PATH_REL = path.join('.ukit', 'storage', 'cache', 'tier-map.json');
|
|
32
|
+
|
|
33
|
+
// Executor-decided defaults (task Discussion): a conservative reliability
|
|
34
|
+
// floor so a cheap-but-flaky tier never displaces a reliable one, and a 24h
|
|
35
|
+
// artifact TTL so a stale promote cannot silently own routing forever.
|
|
36
|
+
const DEFAULT_RELIABILITY_FLOOR = 0.8;
|
|
37
|
+
const TIER_MAP_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
38
|
+
|
|
39
|
+
const PROMOTION_REASONS = Object.freeze({
|
|
40
|
+
thin: 'joined<50',
|
|
41
|
+
agreement: 'agreement<0.6',
|
|
42
|
+
skew: 'disagreements-not-cheaper',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function tierName(value) {
|
|
46
|
+
return MEASURED_TIER_ORDER.includes(value) ? value : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// One joined row is either the {audit, ledger} pair collectRouteOutcomes
|
|
50
|
+
// emits or a flat {executionMode, modelTier, writeSucceeded} row — both are
|
|
51
|
+
function normalizeJoinedRow(row) {
|
|
52
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) return null;
|
|
53
|
+
const audit = row.audit && typeof row.audit === 'object' ? row.audit : row;
|
|
54
|
+
const ledger = row.ledger && typeof row.ledger === 'object' ? row.ledger : row;
|
|
55
|
+
// An empty object is not a joined row — nothing identifiable to count.
|
|
56
|
+
if (Object.keys(audit).length === 0) return null;
|
|
57
|
+
return { audit, ledger };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// SPEC §8: deriveTierStats(joinedRows) → { byModeTier, joinedRoutes }.
|
|
61
|
+
// byModeTier is "<mode>|<tier>"-keyed; `routes` counts joined rows where the
|
|
62
|
+
// route ran on that tier, `successes` counts writeSucceeded === true (the
|
|
63
|
+
// ledger outcome the existing byMode buckets already count), `reliability`
|
|
64
|
+
// is successes/routes. Rows without a provider-neutral tier name are counted
|
|
65
|
+
// in joinedRoutes (coverage) but never land in a tier bucket — a model ID
|
|
66
|
+
// must not create a pseudo-tier.
|
|
67
|
+
export function deriveTierStats(joinedRows = []) {
|
|
68
|
+
const byModeTier = {};
|
|
69
|
+
const rows = Array.isArray(joinedRows) ? joinedRows : [];
|
|
70
|
+
let joinedRoutes = 0;
|
|
71
|
+
for (const raw of rows) {
|
|
72
|
+
const normalized = normalizeJoinedRow(raw);
|
|
73
|
+
if (!normalized) continue;
|
|
74
|
+
joinedRoutes += 1;
|
|
75
|
+
const mode = typeof normalized.audit.executionMode === 'string'
|
|
76
|
+
? normalized.audit.executionMode
|
|
77
|
+
: null;
|
|
78
|
+
const tier = tierName(normalized.audit.modelTier);
|
|
79
|
+
if (!mode || !tier) continue;
|
|
80
|
+
const key = `${mode}|${tier}`;
|
|
81
|
+
const bucket = byModeTier[key] ??= { routes: 0, successes: 0, reliability: 0 };
|
|
82
|
+
bucket.routes += 1;
|
|
83
|
+
if (normalized.ledger.writeSucceeded === true) bucket.successes += 1;
|
|
84
|
+
bucket.reliability = bucket.successes / bucket.routes;
|
|
85
|
+
}
|
|
86
|
+
return { byModeTier, joinedRoutes };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// SPEC §8: resolveMeasuredTier({executionMode, staticTier, stats,
|
|
90
|
+
// reliabilityFloor?}) → tier name | null. Cheapest tier whose measured
|
|
91
|
+
// reliability for that mode meets the floor; null when the data is thin or
|
|
92
|
+
// absent. `staticTier` rides the signature per SPEC but does not bias the
|
|
93
|
+
// pick — the measured table decides on its own.
|
|
94
|
+
export function resolveMeasuredTier({
|
|
95
|
+
executionMode = null,
|
|
96
|
+
staticTier = null,
|
|
97
|
+
stats = null,
|
|
98
|
+
reliabilityFloor = DEFAULT_RELIABILITY_FLOOR,
|
|
99
|
+
} = {}) {
|
|
100
|
+
if (!executionMode || !stats || typeof stats !== 'object') return null;
|
|
101
|
+
const floor = Number.isFinite(reliabilityFloor)
|
|
102
|
+
? reliabilityFloor
|
|
103
|
+
: DEFAULT_RELIABILITY_FLOOR;
|
|
104
|
+
const byModeTier = stats.byModeTier ?? {};
|
|
105
|
+
for (const tier of MEASURED_TIER_ORDER) {
|
|
106
|
+
const bucket = byModeTier[`${executionMode}|${tier}`];
|
|
107
|
+
if (bucket && bucket.routes >= 1 && bucket.reliability >= floor) {
|
|
108
|
+
return tier;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// SPEC §8/§14: evaluateTierPromotion({stats, staticMap, minJoined?=50,
|
|
115
|
+
// minAgreement?=0.60}) → {decision, agreement, cheaperShare, regressionFree,
|
|
116
|
+
// reasons[], byMode, joinedRoutes}.
|
|
117
|
+
//
|
|
118
|
+
// Promote only when ALL gates pass:
|
|
119
|
+
// * joined ≥ minJoined (default 50 joined route↔outcome rows);
|
|
120
|
+
// * agreement ≥ minAgreement — the measured pick equals the static pick for
|
|
121
|
+
// ≥60% of modes where a pick exists;
|
|
122
|
+
// * disagreements skew cheaper — measured picks that differ must be a
|
|
123
|
+
// lower rung (cheaperShare = cheaper/disagreements > 0.5; zero
|
|
124
|
+
// disagreements passes as perfectly agreeing);
|
|
125
|
+
// * regressionFree — no mode where the measured pick's measured
|
|
126
|
+
// reliability is lower than the static pick's measured reliability
|
|
127
|
+
// (a tier-verdict regression, verdict = writeSucceeded reliability).
|
|
128
|
+
// `byMode` carries the measured pick per mode for the artifact; modes with
|
|
129
|
+
// no pick fall back to static at read time.
|
|
130
|
+
export function evaluateTierPromotion({
|
|
131
|
+
stats = null,
|
|
132
|
+
staticMap = {},
|
|
133
|
+
minJoined = 50,
|
|
134
|
+
minAgreement = 0.60,
|
|
135
|
+
reliabilityFloor = DEFAULT_RELIABILITY_FLOOR,
|
|
136
|
+
} = {}) {
|
|
137
|
+
const byModeTier = stats?.byModeTier ?? {};
|
|
138
|
+
const joinedRoutes = Number.isFinite(stats?.joinedRoutes) ? stats.joinedRoutes : 0;
|
|
139
|
+
const modes = Object.keys(staticMap && typeof staticMap === 'object' ? staticMap : {});
|
|
140
|
+
const byMode = {};
|
|
141
|
+
|
|
142
|
+
let compared = 0;
|
|
143
|
+
let agreed = 0;
|
|
144
|
+
let disagreements = 0;
|
|
145
|
+
let cheaper = 0;
|
|
146
|
+
const regressedModes = [];
|
|
147
|
+
|
|
148
|
+
for (const mode of modes) {
|
|
149
|
+
const staticTier = staticMap[mode];
|
|
150
|
+
const measured = resolveMeasuredTier({
|
|
151
|
+
executionMode: mode,
|
|
152
|
+
staticTier,
|
|
153
|
+
stats,
|
|
154
|
+
reliabilityFloor,
|
|
155
|
+
});
|
|
156
|
+
if (measured !== null) byMode[mode] = measured;
|
|
157
|
+
if (!tierName(staticTier) || measured === null) continue;
|
|
158
|
+
compared += 1;
|
|
159
|
+
if (measured === staticTier) {
|
|
160
|
+
agreed += 1;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
disagreements += 1;
|
|
164
|
+
const measuredIdx = MEASURED_TIER_ORDER.indexOf(measured);
|
|
165
|
+
const staticIdx = MEASURED_TIER_ORDER.indexOf(staticTier);
|
|
166
|
+
if (measuredIdx !== -1 && staticIdx !== -1 && measuredIdx < staticIdx) {
|
|
167
|
+
cheaper += 1;
|
|
168
|
+
}
|
|
169
|
+
const measuredRel = byModeTier[`${mode}|${measured}`]?.reliability;
|
|
170
|
+
const staticRel = byModeTier[`${mode}|${staticTier}`]?.reliability;
|
|
171
|
+
if (Number.isFinite(measuredRel) && Number.isFinite(staticRel) && measuredRel < staticRel) {
|
|
172
|
+
regressedModes.push(mode);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const agreement = compared > 0 ? agreed / compared : 0;
|
|
177
|
+
const cheaperShare = disagreements > 0 ? cheaper / disagreements : 1;
|
|
178
|
+
const regressionFree = regressedModes.length === 0;
|
|
179
|
+
|
|
180
|
+
const reasons = [];
|
|
181
|
+
if (!(joinedRoutes >= minJoined)) reasons.push(PROMOTION_REASONS.thin);
|
|
182
|
+
if (!(agreement >= minAgreement)) reasons.push(PROMOTION_REASONS.agreement);
|
|
183
|
+
if (!(cheaperShare > 0.5)) reasons.push(PROMOTION_REASONS.skew);
|
|
184
|
+
for (const mode of regressedModes) reasons.push(`regression:${mode}`);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
decision: reasons.length === 0 ? 'promote' : 'hold',
|
|
188
|
+
agreement,
|
|
189
|
+
cheaperShare,
|
|
190
|
+
regressionFree,
|
|
191
|
+
reasons,
|
|
192
|
+
byMode,
|
|
193
|
+
joinedRoutes,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// SPEC §7: { version: 1, derivedAt, joinedRoutes, agreement, decision,
|
|
198
|
+
// byMode: {<mode>: <tier>} } — plus cheaperShare/reasons for the metrics
|
|
199
|
+
// rollup (additive, bounded enums/numbers only).
|
|
200
|
+
export function buildTierMapArtifact({
|
|
201
|
+
stats = null,
|
|
202
|
+
staticMap = {},
|
|
203
|
+
minJoined = 50,
|
|
204
|
+
minAgreement = 0.60,
|
|
205
|
+
now = () => new Date(),
|
|
206
|
+
} = {}) {
|
|
207
|
+
const evaluation = evaluateTierPromotion({ stats, staticMap, minJoined, minAgreement });
|
|
208
|
+
return {
|
|
209
|
+
version: 1,
|
|
210
|
+
derivedAt: now().toISOString(),
|
|
211
|
+
joinedRoutes: evaluation.joinedRoutes,
|
|
212
|
+
agreement: evaluation.agreement,
|
|
213
|
+
decision: evaluation.decision,
|
|
214
|
+
byMode: evaluation.byMode,
|
|
215
|
+
cheaperShare: evaluation.cheaperShare,
|
|
216
|
+
regressionFree: evaluation.regressionFree,
|
|
217
|
+
reasons: evaluation.reasons,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Atomic tmp+rename write — same discipline as route-audit/state writes so a
|
|
222
|
+
// reader never sees a torn artifact. Advisory: a write failure returns
|
|
223
|
+
// {written:false}, never throws (metrics must stay read-mostly-safe).
|
|
224
|
+
export async function writeTierMap(projectRoot, artifact) {
|
|
225
|
+
try {
|
|
226
|
+
const filePath = path.join(projectRoot, TIER_MAP_PATH_REL);
|
|
227
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
228
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${crypto.randomBytes(6).toString('hex')}`;
|
|
229
|
+
try {
|
|
230
|
+
await fs.writeFile(tempPath, JSON.stringify(artifact, null, 2), 'utf8');
|
|
231
|
+
await fs.rename(tempPath, filePath);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
try { await fs.rm(tempPath, { force: true }); } catch { /* best effort */ }
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
return { written: true, path: filePath };
|
|
237
|
+
} catch (error) {
|
|
238
|
+
return { written: false, error: error?.message ?? String(error) };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// SPEC §8: loadTierMap(projectRoot) → TierMap|null. Absent, corrupt, wrong
|
|
243
|
+
// version, invalid decision, or stale (derivedAt older than the 24h TTL) →
|
|
244
|
+
// null so the static map stays authoritative. byMode values are filtered to
|
|
245
|
+
// the tier vocabulary — anything else (e.g. a model ID) is dropped so it can
|
|
246
|
+
// never reach a route field or receipt.
|
|
247
|
+
export async function loadTierMap(projectRoot, { now = () => Date.now() } = {}) {
|
|
248
|
+
try {
|
|
249
|
+
const filePath = path.join(projectRoot, TIER_MAP_PATH_REL);
|
|
250
|
+
const doc = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
251
|
+
if (!doc || typeof doc !== 'object' || doc.version !== 1) return null;
|
|
252
|
+
if (doc.decision !== 'promote' && doc.decision !== 'hold') return null;
|
|
253
|
+
const derivedAtMs = Date.parse(doc.derivedAt ?? '');
|
|
254
|
+
if (!Number.isFinite(derivedAtMs)) return null;
|
|
255
|
+
if (now() - derivedAtMs > TIER_MAP_MAX_AGE_MS) return null;
|
|
256
|
+
const byMode = {};
|
|
257
|
+
if (doc.byMode && typeof doc.byMode === 'object') {
|
|
258
|
+
for (const [mode, tier] of Object.entries(doc.byMode)) {
|
|
259
|
+
const name = tierName(tier);
|
|
260
|
+
if (name) byMode[mode] = name;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
version: 1,
|
|
265
|
+
derivedAt: doc.derivedAt,
|
|
266
|
+
joinedRoutes: Number.isFinite(doc.joinedRoutes) ? doc.joinedRoutes : 0,
|
|
267
|
+
agreement: Number.isFinite(doc.agreement) ? doc.agreement : 0,
|
|
268
|
+
decision: doc.decision,
|
|
269
|
+
byMode,
|
|
270
|
+
...(Number.isFinite(doc.cheaperShare) ? { cheaperShare: doc.cheaperShare } : {}),
|
|
271
|
+
...(Array.isArray(doc.reasons) ? { reasons: doc.reasons.filter((r) => typeof r === 'string') } : {}),
|
|
272
|
+
};
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Route-side emission (SPEC §5 FR-001): folds the persisted measured map into
|
|
279
|
+
// a route summary. Sets the additive `measuredTier` field (nullable — null
|
|
280
|
+
// when data is thin or promotion is off) and, ONLY when the persisted map
|
|
281
|
+
// says 'promote' AND a pick exists for this mode, overrides the authoritative
|
|
282
|
+
// executionContract.modelTier with the measured tier. Never throws; absent or
|
|
283
|
+
// invalid input leaves the static map authoritative.
|
|
284
|
+
//
|
|
285
|
+
// Returns { map, staticTier, measuredPick, promoted } — `measuredPick` is the
|
|
286
|
+
// raw map pick (logged on the A/B receipt even on hold), `promoted` whether
|
|
287
|
+
// the measured pick took over modelTier.
|
|
288
|
+
export async function applyMeasuredTier({
|
|
289
|
+
projectRoot = process.cwd(),
|
|
290
|
+
routeSummary = null,
|
|
291
|
+
tierMap = undefined,
|
|
292
|
+
} = {}) {
|
|
293
|
+
const map = tierMap !== undefined ? tierMap : await loadTierMap(projectRoot);
|
|
294
|
+
if (!routeSummary || typeof routeSummary !== 'object') {
|
|
295
|
+
return { map, staticTier: null, measuredPick: null, promoted: false };
|
|
296
|
+
}
|
|
297
|
+
const executionMode = routeSummary.executionMode ?? null;
|
|
298
|
+
const staticTier = routeSummary.executionContract?.modelTier ?? null;
|
|
299
|
+
const pick = map?.byMode?.[executionMode] ?? null;
|
|
300
|
+
const measuredPick = tierName(pick);
|
|
301
|
+
const promoted = map?.decision === 'promote' && measuredPick !== null;
|
|
302
|
+
|
|
303
|
+
routeSummary.measuredTier = promoted ? measuredPick : null;
|
|
304
|
+
if (promoted && routeSummary.executionContract && typeof routeSummary.executionContract === 'object') {
|
|
305
|
+
routeSummary.executionContract.modelTier = measuredPick;
|
|
306
|
+
}
|
|
307
|
+
return { map, staticTier, measuredPick, promoted };
|
|
308
|
+
}
|