@ngockhoale/ukit 3.3.3 → 3.4.1
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,412 @@
|
|
|
1
|
+
// crossCheckMatrix.js — adaptive cross-check depth matrix (BL-016 /
|
|
2
|
+
// SPEC FR-004 / ARCH §Adaptive Cross-Check).
|
|
3
|
+
//
|
|
4
|
+
// Verification depth scales with risk instead of being uniform — a separate
|
|
5
|
+
// dimension from implementation depth (fast path vs full workflow). This
|
|
6
|
+
// module is deterministic and pure: it resolves
|
|
7
|
+
//
|
|
8
|
+
// change-risk × implementer-reliability × evidence-quality
|
|
9
|
+
// → none | runnable | review-round | escalate
|
|
10
|
+
//
|
|
11
|
+
// and the result feeds the review policy's `escalationDepth` input slot
|
|
12
|
+
// (src/index/reviewPolicy.js), which maps depth → action floor.
|
|
13
|
+
//
|
|
14
|
+
// Contract (SPEC §8):
|
|
15
|
+
// resolveCrossCheckDepth({riskFloor?, rigor?, implementerReliability?,
|
|
16
|
+
// evidenceQuality?, reviewer?, implementer?, runnable?, role?, model?,
|
|
17
|
+
// project?, overrides?}) → {depth, reasons[]}
|
|
18
|
+
//
|
|
19
|
+
// Invariants from ARCH §Adaptive Cross-Check:
|
|
20
|
+
// * high change-risk overrides implementer reliability — a strong model on
|
|
21
|
+
// an auth/data-path change still escalates to mandatory review.
|
|
22
|
+
// * a reviewer of the SAME model family is not independent review — a
|
|
23
|
+
// same-family re-read never satisfies a review-round; the depth demotes
|
|
24
|
+
// to `runnable` with the limit stated in reasons[].
|
|
25
|
+
// * no reviewer → `runnable` where possible + honest stated limit; the
|
|
26
|
+
// caller reports INCONCLUSIVE when nothing is runnable (the depth enum
|
|
27
|
+
// cannot return INCONCLUSIVE — it names a depth, not a verdict).
|
|
28
|
+
// * implementer-reliability is a joined outcome signal, never a model-name
|
|
29
|
+
// verdict — absent or unrecognized input resolves to baseline-neutral
|
|
30
|
+
// 'unknown' (GAP M13).
|
|
31
|
+
// * `crossCheck.{role|model}×{project}` maintainer overrides may tighten or
|
|
32
|
+
// loosen within the enum; malformed overrides are ignored, never fatal.
|
|
33
|
+
//
|
|
34
|
+
// Parity: tests/consistency/crossCheckParity.test.js locks this module and
|
|
35
|
+
// the installed mirror to identical exports, identical matrix rows, and
|
|
36
|
+
// identical {depth, reasons[]} on shared fixtures.
|
|
37
|
+
|
|
38
|
+
// Ordered by severity — override clamping and floor comparisons rely on the
|
|
39
|
+
// index order, so consumers must never reorder this list.
|
|
40
|
+
export const CROSS_CHECK_DEPTHS = Object.freeze([
|
|
41
|
+
'none',
|
|
42
|
+
'runnable',
|
|
43
|
+
'review-round',
|
|
44
|
+
'escalate',
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
export const CROSS_CHECK_RISK_LEVELS = Object.freeze(['low', 'medium', 'high']);
|
|
48
|
+
|
|
49
|
+
export const CROSS_CHECK_RELIABILITY_LEVELS = Object.freeze([
|
|
50
|
+
'trusted',
|
|
51
|
+
'needs-oversight',
|
|
52
|
+
'unknown',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
export const CROSS_CHECK_EVIDENCE_QUALITIES = Object.freeze([
|
|
56
|
+
'strong',
|
|
57
|
+
'thin',
|
|
58
|
+
'missing-hooks',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
// The ARCH §Adaptive Cross-Check matrix verbatim (7 rows). 'any' is a
|
|
62
|
+
// wildcard. First matching row wins, which gives the two 'any'-wildcard rows
|
|
63
|
+
// their precedence: high-risk escalate beats everything; missing-hooks
|
|
64
|
+
// degrade applies to whatever remains.
|
|
65
|
+
export const CROSS_CHECK_MATRIX = Object.freeze([
|
|
66
|
+
{ risk: 'low', reliability: 'trusted', evidence: 'strong', depth: 'none' },
|
|
67
|
+
{ risk: 'low', reliability: 'needs-oversight', evidence: 'thin', depth: 'review-round' },
|
|
68
|
+
{ risk: 'low', reliability: 'unknown', evidence: 'strong', depth: 'runnable' },
|
|
69
|
+
{ risk: 'medium', reliability: 'trusted', evidence: 'strong', depth: 'runnable' },
|
|
70
|
+
{ risk: 'medium', reliability: 'needs-oversight', evidence: 'any', depth: 'review-round' },
|
|
71
|
+
{ risk: 'high', reliability: 'any', evidence: 'any', depth: 'escalate' },
|
|
72
|
+
{ risk: 'any', reliability: 'any', evidence: 'missing-hooks', depth: 'runnable' },
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// Cells the matrix does not list resolve conservatively: needs-oversight on
|
|
76
|
+
// a low/medium-risk change is the row-2/row-5 signal family, so it still
|
|
77
|
+
// earns a review round; trusted and baseline-neutral reliability never drop
|
|
78
|
+
// below `runnable` on unlisted cells — thin evidence can never justify `none`.
|
|
79
|
+
const RELIABILITY_RESIDUAL = Object.freeze({
|
|
80
|
+
'needs-oversight': 'review-round',
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Bounded maintainer override shapes: a bound leaf carries only these keys
|
|
84
|
+
// (validated in src/core/runtimeConfig.js); all other top-level keys of the
|
|
85
|
+
// section are reserved.
|
|
86
|
+
const BOUND_KEYS = new Set(['depth', 'minDepth', 'maxDepth']);
|
|
87
|
+
const CROSS_CHECK_SECTION_KEYS = new Set(['enabled', 'byRole', 'byModel']);
|
|
88
|
+
|
|
89
|
+
const MAX_ID_LENGTH = 96;
|
|
90
|
+
|
|
91
|
+
const RISK_ALIASES = Object.freeze({
|
|
92
|
+
high: 'high',
|
|
93
|
+
'high-risk': 'high',
|
|
94
|
+
critical: 'high',
|
|
95
|
+
medium: 'medium',
|
|
96
|
+
moderate: 'medium',
|
|
97
|
+
low: 'low',
|
|
98
|
+
none: 'low',
|
|
99
|
+
trivial: 'low',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const RELIABILITY_ALIASES = Object.freeze({
|
|
103
|
+
trusted: 'trusted',
|
|
104
|
+
'needs-oversight': 'needs-oversight',
|
|
105
|
+
unknown: 'unknown',
|
|
106
|
+
baseline: 'unknown',
|
|
107
|
+
'baseline-neutral': 'unknown',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Receipt classes attesting real-run/surface evidence vs build-only or
|
|
111
|
+
// claimed-only work (ARCH: evidence-quality is about receipt classes present,
|
|
112
|
+
// never about what the agent asserts).
|
|
113
|
+
const STRONG_EVIDENCE_TOKENS = new Set([
|
|
114
|
+
'strong',
|
|
115
|
+
'surface-check',
|
|
116
|
+
'render-observation',
|
|
117
|
+
'io-run',
|
|
118
|
+
'run-receipt',
|
|
119
|
+
'real-run',
|
|
120
|
+
'verification-evidence',
|
|
121
|
+
'write-evidence',
|
|
122
|
+
]);
|
|
123
|
+
const THIN_EVIDENCE_TOKENS = new Set([
|
|
124
|
+
'thin',
|
|
125
|
+
'build-only',
|
|
126
|
+
'claimed',
|
|
127
|
+
'claimed-only',
|
|
128
|
+
'log',
|
|
129
|
+
]);
|
|
130
|
+
const MISSING_HOOKS_TOKENS = new Set([
|
|
131
|
+
'missing-hooks',
|
|
132
|
+
'unavailable',
|
|
133
|
+
'cannot-produce',
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
function isPlainObject(value) {
|
|
137
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function depthIndex(depth) {
|
|
141
|
+
return CROSS_CHECK_DEPTHS.indexOf(depth);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function boundedId(value) {
|
|
145
|
+
return typeof value === 'string' && value.trim()
|
|
146
|
+
? value.trim().slice(0, MAX_ID_LENGTH)
|
|
147
|
+
: null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ─── input normalization ────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
// riskFloor arrives as the route record's {floor:'none'|'high-risk', codes[]},
|
|
153
|
+
// a bare enum/code string, or a level name. `rigor` (R0-R4) only ever RAISES
|
|
154
|
+
// the floor: R3+ ceremony on a non-floor route still counts as medium risk —
|
|
155
|
+
// the router asked for deep work, so verification follows.
|
|
156
|
+
function normalizeRisk(riskFloor, rigor) {
|
|
157
|
+
let risk = 'low';
|
|
158
|
+
if (typeof riskFloor === 'string') {
|
|
159
|
+
risk = RISK_ALIASES[riskFloor.trim().toLowerCase()] ?? 'low';
|
|
160
|
+
} else if (isPlainObject(riskFloor)) {
|
|
161
|
+
const floor = typeof riskFloor.floor === 'string'
|
|
162
|
+
? riskFloor.floor.trim().toLowerCase()
|
|
163
|
+
: null;
|
|
164
|
+
if (RISK_ALIASES[floor]) {
|
|
165
|
+
risk = RISK_ALIASES[floor];
|
|
166
|
+
} else if (typeof riskFloor.risk === 'string') {
|
|
167
|
+
risk = RISK_ALIASES[riskFloor.risk.trim().toLowerCase()] ?? 'low';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (risk === 'low' && typeof rigor === 'string') {
|
|
171
|
+
const level = /^r(\d)$/i.exec(rigor.trim());
|
|
172
|
+
if (level && Number(level[1]) >= 3) risk = 'medium';
|
|
173
|
+
}
|
|
174
|
+
return risk;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Reliability is joined outcome data, never a model-name verdict: anything
|
|
178
|
+
// absent or unrecognized collapses to baseline-neutral 'unknown'.
|
|
179
|
+
function normalizeReliability(value) {
|
|
180
|
+
if (typeof value !== 'string') return 'unknown';
|
|
181
|
+
return RELIABILITY_ALIASES[value.trim().toLowerCase()] ?? 'unknown';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function normalizeEvidenceQuality(value) {
|
|
185
|
+
if (value == null) return 'thin';
|
|
186
|
+
if (Array.isArray(value)) {
|
|
187
|
+
if (value.length === 0) return 'thin';
|
|
188
|
+
const tokens = value
|
|
189
|
+
.filter((token) => typeof token === 'string' && token.trim())
|
|
190
|
+
.map((token) => token.trim().toLowerCase());
|
|
191
|
+
if (tokens.some((token) => MISSING_HOOKS_TOKENS.has(token))) return 'missing-hooks';
|
|
192
|
+
if (tokens.some((token) => STRONG_EVIDENCE_TOKENS.has(token))) return 'strong';
|
|
193
|
+
return 'thin';
|
|
194
|
+
}
|
|
195
|
+
if (typeof value === 'string') {
|
|
196
|
+
const token = value.trim().toLowerCase();
|
|
197
|
+
if (MISSING_HOOKS_TOKENS.has(token)) return 'missing-hooks';
|
|
198
|
+
if (STRONG_EVIDENCE_TOKENS.has(token)) return 'strong';
|
|
199
|
+
return 'thin';
|
|
200
|
+
}
|
|
201
|
+
if (isPlainObject(value)) {
|
|
202
|
+
if (typeof value.quality === 'string') return normalizeEvidenceQuality(value.quality);
|
|
203
|
+
if (Array.isArray(value.classes)) return normalizeEvidenceQuality(value.classes);
|
|
204
|
+
}
|
|
205
|
+
return 'thin';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ─── reviewer independence ──────────────────────────────────────────────────
|
|
209
|
+
// A review-round only counts when a reviewer of a DIFFERENT model family
|
|
210
|
+
// exists — same-family re-reads and self re-reads are not independent review
|
|
211
|
+
// (SPEC FR-004). A reviewer descriptor that supplies no family cannot be
|
|
212
|
+
// proven same or different; it is treated as independent but the assumption
|
|
213
|
+
// is named in the reasons so the receipt stays honest.
|
|
214
|
+
function reviewerIndependence(reviewer, implementer) {
|
|
215
|
+
if (!isPlainObject(reviewer)) {
|
|
216
|
+
return { independent: false, reason: 'no reviewer available' };
|
|
217
|
+
}
|
|
218
|
+
const reviewerFamily = boundedId(reviewer.modelFamily) ?? boundedId(reviewer.family);
|
|
219
|
+
const reviewerModel = boundedId(reviewer.model) ?? boundedId(reviewer.id) ?? boundedId(reviewer.name);
|
|
220
|
+
const implementerFamily = isPlainObject(implementer)
|
|
221
|
+
? (boundedId(implementer.modelFamily) ?? boundedId(implementer.family))
|
|
222
|
+
: null;
|
|
223
|
+
const implementerModel = isPlainObject(implementer)
|
|
224
|
+
? (boundedId(implementer.model) ?? boundedId(implementer.id) ?? boundedId(implementer.name))
|
|
225
|
+
: null;
|
|
226
|
+
if (reviewerModel && implementerModel && reviewerModel === implementerModel) {
|
|
227
|
+
return { independent: false, reason: 'reviewer is the implementer re-reading its own diff — not independent' };
|
|
228
|
+
}
|
|
229
|
+
if (reviewerFamily && implementerFamily && reviewerFamily === implementerFamily) {
|
|
230
|
+
return { independent: false, reason: 'same-model-family reviewer is not independent review' };
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
independent: true,
|
|
234
|
+
reason: reviewerFamily
|
|
235
|
+
? 'different-model-family reviewer available'
|
|
236
|
+
: 'reviewer family unproven — assuming independent',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── maintainer overrides ───────────────────────────────────────────────────
|
|
241
|
+
// `crossCheck.{role|model}×{project}` bounded overrides, per ARCH §Config:
|
|
242
|
+
// enum-only, shipped ON. A bound leaf is {depth|minDepth|maxDepth}; an entry
|
|
243
|
+
// may also be a project map {projectId|'*': bound}. byRole wins over byModel;
|
|
244
|
+
// a project-named leaf wins over the '*' leaf of the same entry. Unknown
|
|
245
|
+
// shapes are ignored with a reason — a maintainer typo must never crash the
|
|
246
|
+
// route nor invent a depth outside the enum.
|
|
247
|
+
function boundLeaf(entry) {
|
|
248
|
+
if (!isPlainObject(entry)) return null;
|
|
249
|
+
if (Object.keys(entry).some((key) => BOUND_KEYS.has(key))) return entry;
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resolveOverrideBound(overrides, { role = null, model = null, project = null }) {
|
|
254
|
+
if (!isPlainObject(overrides) || overrides.enabled === false) return null;
|
|
255
|
+
const maps = [
|
|
256
|
+
['role', role, overrides.byRole],
|
|
257
|
+
['model', model, overrides.byModel],
|
|
258
|
+
];
|
|
259
|
+
for (const [kind, name, map] of maps) {
|
|
260
|
+
if (!name || !isPlainObject(map)) continue;
|
|
261
|
+
const entry = map[name];
|
|
262
|
+
if (entry === undefined) continue;
|
|
263
|
+
const direct = boundLeaf(entry);
|
|
264
|
+
if (direct) {
|
|
265
|
+
return { bound: direct, label: `${kind} "${name}"` };
|
|
266
|
+
}
|
|
267
|
+
if (isPlainObject(entry)) {
|
|
268
|
+
const projectLeaf = project ? boundLeaf(entry[project]) : null;
|
|
269
|
+
const starLeaf = boundLeaf(entry['*']);
|
|
270
|
+
const picked = projectLeaf ?? starLeaf;
|
|
271
|
+
if (picked) {
|
|
272
|
+
return {
|
|
273
|
+
bound: picked,
|
|
274
|
+
label: `${kind} "${name}"${projectLeaf ? ` project "${project}"` : ''}`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { bound: null, label: `${kind} "${name}"` };
|
|
279
|
+
}
|
|
280
|
+
// A bare bound ({depth:…} / {minDepth:…} / {maxDepth:…}) applies directly.
|
|
281
|
+
const direct = boundLeaf(overrides);
|
|
282
|
+
if (direct) return { bound: direct, label: 'crossCheck' };
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ─── resolver ───────────────────────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
export function resolveCrossCheckDepth({
|
|
289
|
+
riskFloor = null,
|
|
290
|
+
rigor = null,
|
|
291
|
+
implementerReliability = null,
|
|
292
|
+
evidenceQuality = null,
|
|
293
|
+
reviewer = undefined,
|
|
294
|
+
implementer = null,
|
|
295
|
+
runnable = true,
|
|
296
|
+
role = null,
|
|
297
|
+
model = null,
|
|
298
|
+
project = null,
|
|
299
|
+
overrides = null,
|
|
300
|
+
} = {}) {
|
|
301
|
+
const reasons = [];
|
|
302
|
+
const risk = normalizeRisk(riskFloor, rigor);
|
|
303
|
+
const reliability = normalizeReliability(implementerReliability);
|
|
304
|
+
const evidence = normalizeEvidenceQuality(evidenceQuality);
|
|
305
|
+
|
|
306
|
+
if (implementerReliability == null
|
|
307
|
+
|| (typeof implementerReliability === 'string'
|
|
308
|
+
&& RELIABILITY_ALIASES[implementerReliability.trim().toLowerCase()] === undefined)) {
|
|
309
|
+
reasons.push(
|
|
310
|
+
'implementerReliability unresolved — defaulting to project-baseline neutral '
|
|
311
|
+
+ '("unknown"); reliability is a joined outcome signal, never a model-name verdict',
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let depth = null;
|
|
316
|
+
let decidingRow = null;
|
|
317
|
+
for (const row of CROSS_CHECK_MATRIX) {
|
|
318
|
+
if ((row.risk === 'any' || row.risk === risk)
|
|
319
|
+
&& (row.reliability === 'any' || row.reliability === reliability)
|
|
320
|
+
&& (row.evidence === 'any' || row.evidence === evidence)) {
|
|
321
|
+
depth = row.depth;
|
|
322
|
+
decidingRow = row;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (depth) {
|
|
328
|
+
const index = CROSS_CHECK_MATRIX.indexOf(decidingRow) + 1;
|
|
329
|
+
reasons.push(
|
|
330
|
+
`matrix row ${index}: risk=${decidingRow.risk === 'any' ? risk : decidingRow.risk} × `
|
|
331
|
+
+ `reliability=${decidingRow.reliability === 'any' ? reliability : decidingRow.reliability} × `
|
|
332
|
+
+ `evidence=${decidingRow.evidence === 'any' ? evidence : decidingRow.evidence} → ${decidingRow.depth}`,
|
|
333
|
+
);
|
|
334
|
+
if (decidingRow.risk === 'high') {
|
|
335
|
+
reasons.push('high change-risk overrides implementer reliability — mandatory review regardless of track record');
|
|
336
|
+
}
|
|
337
|
+
if (decidingRow.evidence === 'missing-hooks') {
|
|
338
|
+
reasons.push(
|
|
339
|
+
'engine cannot produce a needed receipt class (missing hooks) — runnable '
|
|
340
|
+
+ 'checks where possible + honest stated limit; INCONCLUSIVE if nothing runnable',
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
depth = RELIABILITY_RESIDUAL[reliability] ?? 'runnable';
|
|
345
|
+
reasons.push(
|
|
346
|
+
`no verbatim matrix row for risk=${risk} × reliability=${reliability} × evidence=${evidence} — `
|
|
347
|
+
+ `residual rule resolves ${depth} (needs-oversight still earns a review round; `
|
|
348
|
+
+ 'trusted/baseline never drops below runnable)',
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Reviewer independence only demotes the review-round depth: the matrix
|
|
353
|
+
// chose it for the signal, but without a different-family reviewer a
|
|
354
|
+
// same-family or self re-read is not independent review. 'escalate' is a
|
|
355
|
+
// mandatory hand-off — it never silently downgrades; the reason names the
|
|
356
|
+
// missing reviewer so the consumer escalates to a human or another lane.
|
|
357
|
+
const independence = reviewerIndependence(reviewer, implementer);
|
|
358
|
+
if (depth === 'review-round' && !independence.independent) {
|
|
359
|
+
reasons.push(`${independence.reason} — review-round demoted to runnable checks + stated limit`);
|
|
360
|
+
depth = 'runnable';
|
|
361
|
+
if (runnable === false) {
|
|
362
|
+
reasons.push('nothing runnable — report INCONCLUSIVE');
|
|
363
|
+
}
|
|
364
|
+
} else if (depth === 'review-round') {
|
|
365
|
+
reasons.push(independence.reason);
|
|
366
|
+
} else if (depth === 'escalate' && !independence.independent) {
|
|
367
|
+
reasons.push(
|
|
368
|
+
`${independence.reason} — escalate stands: mandatory review requires a `
|
|
369
|
+
+ 'different-family reviewer or human sign-off, never a silent downgrade',
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Maintainer overrides bound the resolved depth last. A 'depth' pin wins;
|
|
374
|
+
// minDepth/maxDepth clamp within the enum. Invalid bound values are ignored
|
|
375
|
+
// with a named reason — schema validation in runtimeConfig.js rejects them
|
|
376
|
+
// at parse time; this guard covers programmatic callers.
|
|
377
|
+
const resolved = resolveOverrideBound(overrides, { role, model, project });
|
|
378
|
+
if (resolved && resolved.bound) {
|
|
379
|
+
const bound = resolved.bound;
|
|
380
|
+
let applied = null;
|
|
381
|
+
if (typeof bound.depth === 'string' && CROSS_CHECK_DEPTHS.includes(bound.depth)) {
|
|
382
|
+
depth = bound.depth;
|
|
383
|
+
applied = `depth=${bound.depth}`;
|
|
384
|
+
} else {
|
|
385
|
+
if (typeof bound.maxDepth === 'string'
|
|
386
|
+
&& CROSS_CHECK_DEPTHS.includes(bound.maxDepth)
|
|
387
|
+
&& depthIndex(depth) > depthIndex(bound.maxDepth)) {
|
|
388
|
+
depth = bound.maxDepth;
|
|
389
|
+
applied = `maxDepth=${bound.maxDepth}`;
|
|
390
|
+
}
|
|
391
|
+
if (typeof bound.minDepth === 'string'
|
|
392
|
+
&& CROSS_CHECK_DEPTHS.includes(bound.minDepth)
|
|
393
|
+
&& depthIndex(depth) < depthIndex(bound.minDepth)) {
|
|
394
|
+
depth = bound.minDepth;
|
|
395
|
+
applied = applied ? `${applied} + minDepth=${bound.minDepth}` : `minDepth=${bound.minDepth}`;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (applied) {
|
|
399
|
+
reasons.push(`crossCheck override (${resolved.label}) applied: ${applied} → ${depth}`);
|
|
400
|
+
} else {
|
|
401
|
+
reasons.push(`crossCheck override (${resolved.label}) present but no enum bound applied`);
|
|
402
|
+
}
|
|
403
|
+
} else if (resolved) {
|
|
404
|
+
reasons.push(`crossCheck override (${resolved.label}) has no valid enum bound — ignored`);
|
|
405
|
+
} else if (isPlainObject(overrides) && Object.keys(overrides).some(
|
|
406
|
+
(key) => !CROSS_CHECK_SECTION_KEYS.has(key) && !BOUND_KEYS.has(key),
|
|
407
|
+
)) {
|
|
408
|
+
reasons.push('crossCheck override keys unrecognized — ignored');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return { depth, reasons };
|
|
412
|
+
}
|