wicked-crew 0.7.12 → 0.7.14
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/dist/api/delivery-cache.d.ts +135 -0
- package/dist/api/delivery-cache.d.ts.map +1 -0
- package/dist/api/delivery-cache.js +245 -0
- package/dist/api/delivery-cache.js.map +1 -0
- package/dist/api/delivery-index.d.ts +42 -11
- package/dist/api/delivery-index.d.ts.map +1 -1
- package/dist/api/delivery-index.js +104 -52
- package/dist/api/delivery-index.js.map +1 -1
- package/dist/api/endpoint-manifest-live.d.ts.map +1 -1
- package/dist/api/endpoint-manifest-live.js +0 -1
- package/dist/api/endpoint-manifest-live.js.map +1 -1
- package/dist/api/endpoint-manifest.d.ts +26 -6
- package/dist/api/endpoint-manifest.d.ts.map +1 -1
- package/dist/api/endpoint-manifest.js +46 -10
- package/dist/api/endpoint-manifest.js.map +1 -1
- package/dist/api/gate-cache.d.ts +24 -0
- package/dist/api/gate-cache.d.ts.map +1 -1
- package/dist/api/gate-cache.js +35 -0
- package/dist/api/gate-cache.js.map +1 -1
- package/dist/api/routes.d.ts +14 -7
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +60 -13
- package/dist/api/routes.js.map +1 -1
- package/dist/api/seat-health.d.ts +0 -25
- package/dist/api/seat-health.d.ts.map +1 -1
- package/dist/api/seat-health.js +6 -81
- package/dist/api/seat-health.js.map +1 -1
- package/dist/api/server.d.ts +18 -15
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +87 -36
- package/dist/api/server.js.map +1 -1
- package/dist/api/stall-watchdog.d.ts +40 -3
- package/dist/api/stall-watchdog.d.ts.map +1 -1
- package/dist/api/stall-watchdog.js +94 -15
- package/dist/api/stall-watchdog.js.map +1 -1
- package/dist/campaigns/rollup.d.ts +3 -0
- package/dist/campaigns/rollup.d.ts.map +1 -1
- package/dist/campaigns/rollup.js +17 -2
- package/dist/campaigns/rollup.js.map +1 -1
- package/dist/campaigns/routes.d.ts +4 -4
- package/dist/core/deliver.d.ts.map +1 -1
- package/dist/core/deliver.js +31 -0
- package/dist/core/deliver.js.map +1 -1
- package/dist/core/types.d.ts +20 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +20 -1
- package/dist/core/types.js.map +1 -1
- package/endpoint-manifest.json +3 -2
- package/package.json +2 -2
package/dist/api/routes.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FastifyInstance } from 'fastify';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { CoreAdapter } from '../core/adapter.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type GateCache } from './gate-cache.js';
|
|
5
5
|
import type { ElicitationCache } from './elicitation-cache.js';
|
|
6
6
|
import { QeGateCache } from '../qe/gate-events.js';
|
|
7
7
|
import { SeatHealthTracker } from './seat-health.js';
|
|
@@ -14,6 +14,7 @@ import { RetryIndex } from './retry-index.js';
|
|
|
14
14
|
import { GroupIndex } from './group-index.js';
|
|
15
15
|
import { GuidanceIndex } from './guidance-index.js';
|
|
16
16
|
import { DeliveryIndex } from './delivery-index.js';
|
|
17
|
+
import { DeliveryDerivationCache } from './delivery-cache.js';
|
|
17
18
|
import { type DeliverExec, type WorktreeReprovisioner } from './post-hoc-deliver.js';
|
|
18
19
|
import { type AuthMode } from './auth.js';
|
|
19
20
|
export { API_PREFIX } from './api-prefix.js';
|
|
@@ -66,11 +67,11 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
66
67
|
}, "strict", z.ZodTypeAny, {
|
|
67
68
|
problem: string;
|
|
68
69
|
deliver?: "pr" | "none" | undefined;
|
|
70
|
+
repoRef?: string | undefined;
|
|
69
71
|
projectId?: string | undefined;
|
|
70
72
|
campaignId?: string | undefined;
|
|
71
73
|
groupLabel?: string | undefined;
|
|
72
74
|
workflow?: string | undefined;
|
|
73
|
-
repoRef?: string | undefined;
|
|
74
75
|
clisJson?: string | undefined;
|
|
75
76
|
sessionId?: string | undefined;
|
|
76
77
|
retryOf?: string | undefined;
|
|
@@ -79,11 +80,11 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
79
80
|
}, {
|
|
80
81
|
problem: string;
|
|
81
82
|
deliver?: "pr" | "none" | undefined;
|
|
83
|
+
repoRef?: string | undefined;
|
|
82
84
|
projectId?: string | undefined;
|
|
83
85
|
campaignId?: string | undefined;
|
|
84
86
|
groupLabel?: string | undefined;
|
|
85
87
|
workflow?: string | undefined;
|
|
86
|
-
repoRef?: string | undefined;
|
|
87
88
|
clisJson?: string | undefined;
|
|
88
89
|
sessionId?: string | undefined;
|
|
89
90
|
retryOf?: string | undefined;
|
|
@@ -92,11 +93,11 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
92
93
|
}>, {
|
|
93
94
|
problem: string;
|
|
94
95
|
deliver?: "pr" | "none" | undefined;
|
|
96
|
+
repoRef?: string | undefined;
|
|
95
97
|
projectId?: string | undefined;
|
|
96
98
|
campaignId?: string | undefined;
|
|
97
99
|
groupLabel?: string | undefined;
|
|
98
100
|
workflow?: string | undefined;
|
|
99
|
-
repoRef?: string | undefined;
|
|
100
101
|
clisJson?: string | undefined;
|
|
101
102
|
sessionId?: string | undefined;
|
|
102
103
|
retryOf?: string | undefined;
|
|
@@ -105,11 +106,11 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
105
106
|
}, {
|
|
106
107
|
problem: string;
|
|
107
108
|
deliver?: "pr" | "none" | undefined;
|
|
109
|
+
repoRef?: string | undefined;
|
|
108
110
|
projectId?: string | undefined;
|
|
109
111
|
campaignId?: string | undefined;
|
|
110
112
|
groupLabel?: string | undefined;
|
|
111
113
|
workflow?: string | undefined;
|
|
112
|
-
repoRef?: string | undefined;
|
|
113
114
|
clisJson?: string | undefined;
|
|
114
115
|
sessionId?: string | undefined;
|
|
115
116
|
retryOf?: string | undefined;
|
|
@@ -118,11 +119,11 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
118
119
|
}>, {
|
|
119
120
|
problem: string;
|
|
120
121
|
deliver?: "pr" | "none" | undefined;
|
|
122
|
+
repoRef?: string | undefined;
|
|
121
123
|
projectId?: string | undefined;
|
|
122
124
|
campaignId?: string | undefined;
|
|
123
125
|
groupLabel?: string | undefined;
|
|
124
126
|
workflow?: string | undefined;
|
|
125
|
-
repoRef?: string | undefined;
|
|
126
127
|
clisJson?: string | undefined;
|
|
127
128
|
sessionId?: string | undefined;
|
|
128
129
|
retryOf?: string | undefined;
|
|
@@ -131,11 +132,11 @@ export declare const LaunchSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
|
|
|
131
132
|
}, {
|
|
132
133
|
problem: string;
|
|
133
134
|
deliver?: "pr" | "none" | undefined;
|
|
135
|
+
repoRef?: string | undefined;
|
|
134
136
|
projectId?: string | undefined;
|
|
135
137
|
campaignId?: string | undefined;
|
|
136
138
|
groupLabel?: string | undefined;
|
|
137
139
|
workflow?: string | undefined;
|
|
138
|
-
repoRef?: string | undefined;
|
|
139
140
|
clisJson?: string | undefined;
|
|
140
141
|
sessionId?: string | undefined;
|
|
141
142
|
retryOf?: string | undefined;
|
|
@@ -231,6 +232,12 @@ export interface RuntimeDeps {
|
|
|
231
232
|
* the same reason; defaults to the TTL-memoized `gitRunBranchIsEmpty` over the adapter's
|
|
232
233
|
* repo registry. */
|
|
233
234
|
runBranchIsEmpty?: (repoRef: string, runId: string) => Promise<boolean>;
|
|
235
|
+
/** The background delivery-derivation cache (GET /runs p99): the run DTOs READ this instead
|
|
236
|
+
* of running the two git probes above at assembly — `createServer` builds one over the
|
|
237
|
+
* production probes, arms its sweep, and warms it at each run's terminal frame. A
|
|
238
|
+
* directly-driven route set gets a COLD, unstarted one over the same injectable probes:
|
|
239
|
+
* reads then answer the stat-only tri-state until a test sweeps or warms it explicitly. */
|
|
240
|
+
deliveryCache?: DeliveryDerivationCache;
|
|
234
241
|
/** The post-hoc deliver exec (crew#393, `POST /runs/:id/deliver`) — spawns the hardened
|
|
235
242
|
* deliver script in a run's worktree. Injectable so route tests aim the spawn's HOME/PATH at
|
|
236
243
|
* a fixture (stub `gh`, local bare origin); defaults to the real `bash -lc` spawn. */
|
package/dist/api/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAOxB,OAAO,EAAmD,WAAW,EAA4E,MAAM,oBAAoB,CAAC;AAE5K,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/api/routes.js
CHANGED
|
@@ -7,6 +7,7 @@ import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { CampaignsUnsupportedError, ChatUnsupportedError, CoreAdapter, ElicitationUnsupportedError, SteeringUnsupportedError, humanGatePhaseIds } from '../core/adapter.js';
|
|
9
9
|
import { codeGraphDb, requirementsGraph } from '../core/repoPaths.js';
|
|
10
|
+
import { detectRefusal } from './gate-cache.js';
|
|
10
11
|
import { QeGateCache } from '../qe/gate-events.js';
|
|
11
12
|
import { buildAcceptanceView, resolveRunWorkflow } from '../qe/acceptance.js';
|
|
12
13
|
import { buildEvidenceBundle, coreUnitId, evidenceFilename } from './evidence.js';
|
|
@@ -34,7 +35,8 @@ import { AcpFoldCache, EngineVersionCache, eventsDirOf, installedPackageVersion,
|
|
|
34
35
|
import { RetryIndex } from './retry-index.js';
|
|
35
36
|
import { GroupIndex } from './group-index.js';
|
|
36
37
|
import { GuidanceIndex } from './guidance-index.js';
|
|
37
|
-
import { DeliveryIndex,
|
|
38
|
+
import { DeliveryIndex, gitRunBranchIsEmpty, gitWorktreeIsClean, isDeliverConflictStranded, prUrlFrom, } from './delivery-index.js';
|
|
39
|
+
import { DeliveryDerivationCache } from './delivery-cache.js';
|
|
38
40
|
import { gitReprovisionWorktree, runDeliverScript, } from './post-hoc-deliver.js';
|
|
39
41
|
import { LOCAL_ACTOR } from './auth.js';
|
|
40
42
|
// Re-exported so existing `import { API_PREFIX } from './routes.js'` callers keep working; the
|
|
@@ -262,6 +264,16 @@ runtime = {}) {
|
|
|
262
264
|
return repos.find((r) => r.id === repoRef)?.root_path;
|
|
263
265
|
}),
|
|
264
266
|
};
|
|
267
|
+
// The background layer that owns the git probes above (GET /runs p99): the run DTOs read it,
|
|
268
|
+
// the daemon's sweep/warm feed it. The default (a directly-driven route set) is COLD and
|
|
269
|
+
// unstarted — no background timer under a unit test, reads degrade to the stat-only
|
|
270
|
+
// tri-state — while `createServer` injects a started one over the production probes.
|
|
271
|
+
const deliveryCache = runtime.deliveryCache ??
|
|
272
|
+
new DeliveryDerivationCache({
|
|
273
|
+
listViews: () => adapter.sessionsDetail(),
|
|
274
|
+
probes: vacuityProbes,
|
|
275
|
+
isDelivered: (runId) => deliveryIndex.urlFor(runId) !== undefined,
|
|
276
|
+
});
|
|
265
277
|
const deliverExec = runtime.deliverExec ?? runDeliverScript;
|
|
266
278
|
const reprovisionWorktree = runtime.reprovisionWorktree ?? gitReprovisionWorktree;
|
|
267
279
|
/** Repo root for a repo ref, from the registry — shared by the reprovision path below. */
|
|
@@ -275,7 +287,10 @@ runtime = {}) {
|
|
|
275
287
|
// completed code run whose work is sitting unlifted in its worktree is VISIBLE on the wire,
|
|
276
288
|
// legacy records included. The would-be-stranded runs are further split (crew#311): a
|
|
277
289
|
// completed run whose worktree carries NO contribution at all reads `'vacuous'` — units all
|
|
278
|
-
// "done" with nothing produced must be LOUD on the wire, never silently green.
|
|
290
|
+
// "done" with nothing produced must be LOUD on the wire, never silently green. The vacuity
|
|
291
|
+
// split's git probes run in the BACKGROUND (delivery-cache.ts — GET /runs p99): DTO assembly
|
|
292
|
+
// reads the cache and degrades to the stat-only stranded/none label for at most one sweep
|
|
293
|
+
// tick on a miss, so the list fan-out never spawns git.
|
|
279
294
|
// Applied at DTO assembly on exactly the two endpoints that serve the run DTO
|
|
280
295
|
// (GET /runs + GET /runs/:id); the internal sessionsDetail() consumers are untouched.
|
|
281
296
|
// crew#418 A: a `failed` run whose ONLY failure was the deliver phase's LIFT collision (a
|
|
@@ -303,16 +318,18 @@ runtime = {}) {
|
|
|
303
318
|
};
|
|
304
319
|
/** The run's delivery state, honest by construction (crew#393/#311/#418): a recorded PR wins
|
|
305
320
|
* (`'delivered'`); else a lift-conflict strand reads `'stranded'` from the branch; else the
|
|
306
|
-
*
|
|
307
|
-
|
|
321
|
+
* delivery-derivation CACHE (a background-derived stranded / vacuous / none — GET /runs p99).
|
|
322
|
+
* A cache miss answers the stat-only tri-state (one existsSync, the pre-crew#311 label) for
|
|
323
|
+
* at most one sweep tick; the request path never spawns git and never awaits a derivation. */
|
|
324
|
+
const resolveDelivery = (view, conflictStrand) => {
|
|
308
325
|
const url = deliveryIndex.urlFor(view.session.id);
|
|
309
326
|
if (url !== undefined)
|
|
310
327
|
return { delivery: 'delivered', deliverUrl: url };
|
|
311
328
|
if (conflictStrand)
|
|
312
329
|
return { delivery: 'stranded' };
|
|
313
|
-
return
|
|
330
|
+
return deliveryCache.read(view.session);
|
|
314
331
|
};
|
|
315
|
-
const decorateRun =
|
|
332
|
+
const decorateRun = (view) => {
|
|
316
333
|
const conflictStrand = normalizeStranded(view);
|
|
317
334
|
view.session.project_id = projects.index.projectOf(view.session.id) ?? null;
|
|
318
335
|
const retryOf = retryIndex.retryOfFor(view.session.id);
|
|
@@ -330,7 +347,7 @@ runtime = {}) {
|
|
|
330
347
|
const guidance = guidanceIndex.guidanceFor(view.session.id);
|
|
331
348
|
if (guidance !== undefined)
|
|
332
349
|
view.session.guidance = guidance;
|
|
333
|
-
const state =
|
|
350
|
+
const state = resolveDelivery(view, conflictStrand);
|
|
334
351
|
view.session.delivery = state.delivery;
|
|
335
352
|
if (state.deliverUrl !== undefined)
|
|
336
353
|
view.session.deliverUrl = state.deliverUrl;
|
|
@@ -858,7 +875,7 @@ runtime = {}) {
|
|
|
858
875
|
});
|
|
859
876
|
// Run list (replaces GET /sessions). Actionable-first; reconciles the gate and elicitation caches
|
|
860
877
|
// so that terminal-run entries are pruned even when their terminal CoreEvent was missed.
|
|
861
|
-
app.get(`${V}/runs`, { config: { manifest: { responseType: '{ runs: SessionView[] }', statusCodes: [200] } } }, async (req) => {
|
|
878
|
+
app.get(`${V}/runs`, { config: { manifest: { responseType: '{ runs: SessionView[] }', statusCodes: [200, 400] } } }, async (req, reply) => {
|
|
862
879
|
const views = await adapter.sessionsDetail();
|
|
863
880
|
gateCache.reconcile(views);
|
|
864
881
|
elicitationCache.reconcile(views);
|
|
@@ -868,12 +885,35 @@ runtime = {}) {
|
|
|
868
885
|
// resolve, not leak.
|
|
869
886
|
// Fastify parses a REPEATED query param as string[] — normalize so `?include=archived`
|
|
870
887
|
// and `?include=archived&include=archived` behave identically (Copilot).
|
|
871
|
-
const { include } = req.query;
|
|
888
|
+
const { include, limit } = req.query;
|
|
872
889
|
const includeArchived = (Array.isArray(include) ? include : [include]).includes('archived');
|
|
890
|
+
// `?limit=N` — the top N AFTER the actionable-first sort below, so a capped poll still sees
|
|
891
|
+
// the runs that need a human before the terminal sediment. The default stays UNBOUNDED: the
|
|
892
|
+
// param used to be read by nobody (silently ignored — the full payload regardless), so an
|
|
893
|
+
// implicit cap would silently change results for un-migrated callers. A malformed value is
|
|
894
|
+
// refused loudly instead of ignored (the FINDING-031 posture: ignoring a field runs a
|
|
895
|
+
// different request than the caller sent) — and a repeated `?limit` is malformed too, since
|
|
896
|
+
// two caps are an ambiguity, not an idempotent repetition like `include`.
|
|
897
|
+
let cap;
|
|
898
|
+
if (limit !== undefined) {
|
|
899
|
+
// STRICT canonical decimal only (Copilot on #435): `Number(...)` also admits hex
|
|
900
|
+
// (`0x10`), exponent (`2e3`), signed (`+1`), and whitespace-padded spellings — surprising
|
|
901
|
+
// aliases a 400-on-malformed contract must refuse, not quietly honor. `0` is deliberately
|
|
902
|
+
// VALID and answers the empty list — a real non-negative integer, not a malformation —
|
|
903
|
+
// while non-canonical zeros (`00`, `01`) are refused with the rest.
|
|
904
|
+
const canonical = !Array.isArray(limit) && /^(0|[1-9][0-9]*)$/.test(limit);
|
|
905
|
+
if (!canonical) {
|
|
906
|
+
return reply.code(400).send({
|
|
907
|
+
error: `Invalid ?limit — expected one non-negative decimal integer, got ${JSON.stringify(limit)}`,
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
cap = Number(limit);
|
|
911
|
+
}
|
|
873
912
|
const visible = includeArchived
|
|
874
913
|
? views
|
|
875
914
|
: views.filter((v) => v.session.archived_at == null);
|
|
876
|
-
|
|
915
|
+
const ordered = sortActionableFirst(visible);
|
|
916
|
+
return { runs: (cap !== undefined ? ordered.slice(0, cap) : ordered).map(decorateRun) };
|
|
877
917
|
});
|
|
878
918
|
// ── Run archival (crew#265) — write-off, not delete ────────────────────────
|
|
879
919
|
const ArchiveSchema = z.object({
|
|
@@ -933,7 +973,7 @@ runtime = {}) {
|
|
|
933
973
|
const run = views.find((v) => v.session.id === id);
|
|
934
974
|
if (!run)
|
|
935
975
|
return reply.code(404).send({ error: 'Run not found' });
|
|
936
|
-
return { run:
|
|
976
|
+
return { run: decorateRun(run) };
|
|
937
977
|
});
|
|
938
978
|
// ── Post-hoc delivery (crew#393) — lift a stranded run's worktree into a PR ──
|
|
939
979
|
// The recovery path for the run 83052f0b class: a COMPLETED repo-scoped run whose reviewable
|
|
@@ -1463,7 +1503,7 @@ runtime = {}) {
|
|
|
1463
1503
|
// The 409 body carries the machine-readable pointer (`ResumeRefusal` in api-types).
|
|
1464
1504
|
const terminal = run.session.status;
|
|
1465
1505
|
if (terminal === 'completed' || terminal === 'cancelled') {
|
|
1466
|
-
const state =
|
|
1506
|
+
const state = resolveDelivery(run, conflictStrand);
|
|
1467
1507
|
const recovery = terminal === 'completed' && state.delivery === 'stranded'
|
|
1468
1508
|
? 'deliver'
|
|
1469
1509
|
: 'retry';
|
|
@@ -1562,11 +1602,16 @@ runtime = {}) {
|
|
|
1562
1602
|
: null;
|
|
1563
1603
|
const durableGate = durableRows?.find((r) => r.kind === 'gate');
|
|
1564
1604
|
if (durableGate !== undefined) {
|
|
1605
|
+
// This path builds the entry inline (not through `fold`), so it must run the SAME refusal
|
|
1606
|
+
// detection (issue #419) — otherwise a gate served from the durable row after a restart would
|
|
1607
|
+
// silently drop the warning that the live/replay paths carry.
|
|
1608
|
+
const refusal = detectRefusal(durableGate.prompt);
|
|
1565
1609
|
const entry = {
|
|
1566
1610
|
ord: durableGate.ord ?? 0,
|
|
1567
1611
|
prompt: durableGate.prompt,
|
|
1568
1612
|
lifecycle: 'open',
|
|
1569
1613
|
receivedAt: new Date(durableGate.created_at).toISOString(),
|
|
1614
|
+
...(refusal !== undefined ? { refusal } : {}),
|
|
1570
1615
|
};
|
|
1571
1616
|
gateCache.adopt(id, entry);
|
|
1572
1617
|
return { runId: id, ...entry };
|
|
@@ -2434,7 +2479,7 @@ runtime = {}) {
|
|
|
2434
2479
|
// The escalation ladder's knobs (crew#341). This trio lets the PLATFORM touch runs, so a
|
|
2435
2480
|
// typo must be a 400, never a silently-dropped key that leaves the operator believing they
|
|
2436
2481
|
// armed (or disarmed) automatic recovery. `workerStallEscalateMinutes: 0` is the explicit
|
|
2437
|
-
// OFF spelling —
|
|
2482
|
+
// OFF spelling — the shipped default is ON at 30 (perf#4), so 0 is how an operator disarms.
|
|
2438
2483
|
if (Object.hasOwn(patch, 'workerStallEscalateMinutes')) {
|
|
2439
2484
|
const mins = patch.workerStallEscalateMinutes;
|
|
2440
2485
|
if (typeof mins !== 'number' || !Number.isInteger(mins) || mins < 0 || mins > 1440) {
|
|
@@ -2556,6 +2601,8 @@ runtime = {}) {
|
|
|
2556
2601
|
groupIndex,
|
|
2557
2602
|
deliveryUrlFor: (runId) => deliveryIndex.urlFor(runId),
|
|
2558
2603
|
vacuity: vacuityProbes,
|
|
2604
|
+
// A non-probe derivation throw in the rollup is a defect — error level, loud in diagnostics.
|
|
2605
|
+
logDefect: (m) => app.log.error(m),
|
|
2559
2606
|
});
|
|
2560
2607
|
// ── Governance wiki management (wiki-mgmt) — scoreboard + honest empty-state meta ──────────
|
|
2561
2608
|
// Browse (`GET /governance/rules` + facets) and retire (`DELETE /governance/rules/:id`) stay
|