tickmarkr 2.6.2 → 2.6.3
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/README.md +2 -0
- package/dist/cli/commands/approve.d.ts +6 -3
- package/dist/cli/commands/approve.js +16 -4
- package/dist/cli/commands/doctor.d.ts +6 -2
- package/dist/cli/commands/fleet.js +46 -8
- package/dist/cli/commands/plan.js +13 -8
- package/dist/cli/commands/report.d.ts +2 -1
- package/dist/cli/commands/report.js +56 -6
- package/dist/cli/commands/status.js +19 -1
- package/dist/compile/native.js +7 -0
- package/dist/config/config.d.ts +17 -4
- package/dist/config/config.js +43 -6
- package/dist/config/fleet-overlay.d.ts +13 -3
- package/dist/config/fleet-overlay.js +12 -8
- package/dist/drivers/herdr.d.ts +12 -0
- package/dist/drivers/herdr.js +51 -0
- package/dist/drivers/orca.d.ts +9 -1
- package/dist/drivers/orca.js +29 -7
- package/dist/drivers/types.d.ts +2 -0
- package/dist/drivers/types.js +2 -2
- package/dist/gates/acceptance.d.ts +7 -0
- package/dist/gates/acceptance.js +27 -5
- package/dist/gates/baseline.d.ts +20 -1
- package/dist/gates/baseline.js +100 -20
- package/dist/gates/cache.d.ts +8 -0
- package/dist/gates/cache.js +12 -2
- package/dist/gates/llm.d.ts +6 -0
- package/dist/gates/llm.js +27 -8
- package/dist/gates/review.d.ts +6 -1
- package/dist/gates/review.js +122 -32
- package/dist/gates/run-gates.d.ts +54 -3
- package/dist/gates/run-gates.js +331 -45
- package/dist/gates/test-manifest.d.ts +42 -0
- package/dist/gates/test-manifest.js +69 -10
- package/dist/route/router.d.ts +12 -1
- package/dist/route/router.js +26 -9
- package/dist/run/consult.d.ts +3 -1
- package/dist/run/consult.js +4 -2
- package/dist/run/daemon.d.ts +2 -1
- package/dist/run/daemon.js +342 -77
- package/dist/run/interactive-seed.d.ts +4 -0
- package/dist/run/interactive-seed.js +35 -9
- package/dist/run/journal.d.ts +26 -0
- package/dist/run/journal.js +143 -15
- package/dist/run/lease.d.ts +13 -0
- package/dist/run/lease.js +45 -0
- package/dist/run/protocol.d.ts +15 -0
- package/dist/run/protocol.js +11 -1
- package/dist/run/receipt-resolver.d.ts +22 -0
- package/dist/run/receipt-resolver.js +40 -1
- package/dist/run/repair-selection.d.ts +11 -1
- package/dist/run/repair-selection.js +17 -9
- package/dist/run/wall-budget.d.ts +48 -0
- package/dist/run/wall-budget.js +280 -0
- package/dist/tui/cockpit/run-cockpit.js +2 -2
- package/dist/tui/cockpit/run-view.d.ts +2 -1
- package/dist/tui/cockpit/run-view.js +13 -9
- package/dist/tui/cockpit/setup-cockpit.d.ts +2 -0
- package/dist/tui/cockpit/setup-cockpit.js +4 -0
- package/package.json +2 -1
- package/schema/config.schema.json +8 -1
- package/skills/tickmarkr-loop/SKILL.md +7 -1
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +4 -1
package/README.md
CHANGED
|
@@ -208,6 +208,8 @@ worker. Other parks have different permitted decisions:
|
|
|
208
208
|
| Human gate / other non-gate park | Plain approve records permission to dispatch. |
|
|
209
209
|
| Attempt cap | Plain approve grants a fresh attempt budget; prior routing exclusions remain. |
|
|
210
210
|
| Infrastructure | Plain approve or `--recheck`; recheck reruns the declared battery and satisfies no gate. |
|
|
211
|
+
| Stall with a recorded `reapFailure` (the worker's process census was unreadable or had survivors) | `--recheck` bound to that park's token re-verifies the parked attempt's owned census: only an explicitly recorded empty survivors array (`[]`) releases its harvested commits to the declared battery, with no worker; a missing, unreadable or surviving census re-parks the stall (new token, still approve/recheck). Plain approve dispatches a worker. |
|
|
212
|
+
| Ordinary stall (no `reapFailure`) | Plain approve only — it dispatches a worker; `--recheck` refuses. |
|
|
211
213
|
| Failed review gate | `--waive` satisfies only that identified gate; `--uphold` funds one fixed attempt carrying findings; `--recheck` reruns the battery. |
|
|
212
214
|
| Other failed gate | `--waive` or `--recheck`; plain approve refuses. |
|
|
213
215
|
| Tombstone / gate failure without identifying evidence | Diagnostic only; no invented decision. |
|
|
@@ -26,6 +26,8 @@ export interface NewestPark {
|
|
|
26
26
|
failedGate: string | undefined;
|
|
27
27
|
/** A pre-dispatch human gate whose reason marks it permanent by design (see isTombstonePark). */
|
|
28
28
|
tombstone: boolean;
|
|
29
|
+
/** OBS-1202: a stall park's recorded reap failure (task-human data.reapFailure) — its census is unproven. */
|
|
30
|
+
reapFailure?: string;
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* There is no closed park kind for a declaration-shaped retirement, so the evidence is the one the
|
|
@@ -46,13 +48,14 @@ export declare const parkToken: (park: Pick<NewestPark, "line" | "ts">) => strin
|
|
|
46
48
|
/** OBS-1178: the newest task-failed row for a task — the failure a failed-task recheck binds to. */
|
|
47
49
|
export declare function newestFailure(events: readonly JournalEvent[], taskId: string, sourceIndexes?: readonly number[]): DecisionBinding | undefined;
|
|
48
50
|
/**
|
|
49
|
-
* FINAL §3.3's decision menu as data: human-gate/attempt-cap/other non-gate parks → approve; infra
|
|
50
|
-
*
|
|
51
|
+
* FINAL §3.3's decision menu as data: human-gate/attempt-cap/other non-gate parks → approve; infra, and
|
|
52
|
+
* a stall park that recorded a reapFailure (OBS-1202), → approve or recheck; review gate-fail →
|
|
53
|
+
* waive/uphold/recheck; other gate-fail → waive/recheck; a
|
|
51
54
|
* gate-fail park with no failed-gate evidence, or a tombstone → nothing (a diagnostic, never a
|
|
52
55
|
* fabricated verb). The refusals in `approve` below enforce the same table; this is the one place a
|
|
53
56
|
* surface may read it from, so what a menu offers and what the command accepts cannot drift.
|
|
54
57
|
*/
|
|
55
|
-
export declare function permittedDecisionVerbs(park: Pick<NewestPark, "kind" | "failedGate" | "tombstone"> | undefined): readonly DecisionVerb[];
|
|
58
|
+
export declare function permittedDecisionVerbs(park: Pick<NewestPark, "kind" | "failedGate" | "tombstone" | "reapFailure"> | undefined): readonly DecisionVerb[];
|
|
56
59
|
/** The release marker this command appends for a verb on a park — the fact a read-back must match. */
|
|
57
60
|
export declare function releaseForDecision(verb: DecisionVerb, park: Pick<NewestPark, "kind" | "failedGate">): string | undefined;
|
|
58
61
|
export type ApprovalStatus = "deferred-live" | "recorded-no-owner";
|
|
@@ -59,6 +59,7 @@ sourceIndexes) {
|
|
|
59
59
|
index: i, line: sourceIndexes?.[i] === undefined ? physicalLine(events, i) : sourceIndexes[i] + 1, ts: typeof e.ts === "string" ? e.ts : undefined, kind, reason,
|
|
60
60
|
approveCommand: typeof e.data.approveCommand === "string" ? e.data.approveCommand : undefined,
|
|
61
61
|
failedGate: failedGateBeforePark(events, taskId, i), tombstone: isTombstonePark(kind, reason),
|
|
62
|
+
...(kind === "stall" && typeof e.data.reapFailure === "string" ? { reapFailure: e.data.reapFailure } : {}),
|
|
62
63
|
};
|
|
63
64
|
}
|
|
64
65
|
return undefined;
|
|
@@ -83,8 +84,9 @@ export function newestFailure(events, taskId, sourceIndexes) {
|
|
|
83
84
|
return undefined;
|
|
84
85
|
}
|
|
85
86
|
/**
|
|
86
|
-
* FINAL §3.3's decision menu as data: human-gate/attempt-cap/other non-gate parks → approve; infra
|
|
87
|
-
*
|
|
87
|
+
* FINAL §3.3's decision menu as data: human-gate/attempt-cap/other non-gate parks → approve; infra, and
|
|
88
|
+
* a stall park that recorded a reapFailure (OBS-1202), → approve or recheck; review gate-fail →
|
|
89
|
+
* waive/uphold/recheck; other gate-fail → waive/recheck; a
|
|
88
90
|
* gate-fail park with no failed-gate evidence, or a tombstone → nothing (a diagnostic, never a
|
|
89
91
|
* fabricated verb). The refusals in `approve` below enforce the same table; this is the one place a
|
|
90
92
|
* surface may read it from, so what a menu offers and what the command accepts cannot drift.
|
|
@@ -99,6 +101,10 @@ export function permittedDecisionVerbs(park) {
|
|
|
99
101
|
}
|
|
100
102
|
if (park.kind === "infra")
|
|
101
103
|
return ["approve", "recheck"];
|
|
104
|
+
// OBS-1202: an unreadable reap census parks finished work; recheck re-verifies that census and gates the
|
|
105
|
+
// harvested commits with no worker. An ordinary stall recorded no census failure: approve alone.
|
|
106
|
+
if (park.kind === "stall" && park.reapFailure !== undefined)
|
|
107
|
+
return ["approve", "recheck"];
|
|
102
108
|
// OBS-1084: an authoring park whose newest gate row is a red tool gate is a runner report (a load
|
|
103
109
|
// flake in an unowned suite), so the battery re-runs on the preserved ref with no worker.
|
|
104
110
|
if (park.kind === "authoring" && isToolGate(park.failedGate))
|
|
@@ -196,6 +202,7 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
196
202
|
const gateFailPark = park?.kind === "gate-fail";
|
|
197
203
|
const infraPark = park?.kind === "infra" || park?.kind === "diff-cap";
|
|
198
204
|
const authoringRunnerPark = park?.kind === "authoring" && isToolGate(park.failedGate);
|
|
205
|
+
const reapFailurePark = park?.kind === "stall" && park.reapFailure !== undefined;
|
|
199
206
|
const failedGate = gateFailPark || authoringRunnerPark ? park?.failedGate : undefined;
|
|
200
207
|
const parkBinding = requireBound(runId, taskId, bound, park?.ts === undefined ? undefined : { line: park.line, ts: park.ts }, "park");
|
|
201
208
|
if (namedGate !== undefined && namedGate !== park?.failedGate) {
|
|
@@ -263,7 +270,10 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
263
270
|
return disposition(cwd, runId, "fund-fixed-attempt", `upheld the reviewer for ${taskId} in ${runId} — by ${by}`, serialization.contended);
|
|
264
271
|
}
|
|
265
272
|
if (recheck) {
|
|
266
|
-
if (
|
|
273
|
+
if (park?.kind === "stall" && !reapFailurePark) {
|
|
274
|
+
throw new Error(`--recheck applies to a stall park only when it recorded a reapFailure (census recovery); ${taskId}'s stall park recorded none — plain approve dispatches a worker; refusing`);
|
|
275
|
+
}
|
|
276
|
+
if ((!gateFailPark || !failedGate) && !infraPark && !authoringRunnerPark && !reapFailurePark) {
|
|
267
277
|
throw new Error(`--recheck applies to a gate-fail, infra, diff-cap or red-tool-gate authoring park; ${taskId}'s newest park is ${String(lastHuman?.data.kind ?? "none")} with failed gate ${failedGate ?? "none"} — refusing`);
|
|
268
278
|
}
|
|
269
279
|
journal.append("task-approved", taskId, {
|
|
@@ -276,9 +286,11 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
276
286
|
// carry gate so their durable release row identifies the red tool gate that made recheck admissible.
|
|
277
287
|
...(failedGate ? { failedGate } : {}),
|
|
278
288
|
...(authoringRunnerPark && failedGate ? { gate: failedGate } : {}),
|
|
289
|
+
// OBS-1202: the daemon re-verifies the parked attempt's owned census before any gate runs.
|
|
290
|
+
...(reapFailurePark ? { reapFailure: park.reapFailure } : {}),
|
|
279
291
|
...(reviewRoundCeiling === undefined ? {} : { reviewRoundCeiling }),
|
|
280
292
|
});
|
|
281
|
-
return disposition(cwd, runId, "re-dispatch", `re-checking ${taskId} in ${runId} — by ${by}; ${failedGate ? `failed gate ${failedGate}` : `${park.kind} park`}; no gate marked satisfied`, serialization.contended);
|
|
293
|
+
return disposition(cwd, runId, "re-dispatch", `re-checking ${taskId} in ${runId} — by ${by}; ${failedGate ? `failed gate ${failedGate}` : reapFailurePark ? "reapFailure stall park; the owned census is re-verified before the gates" : `${park.kind} park`}; no gate marked satisfied`, serialization.contended);
|
|
282
294
|
}
|
|
283
295
|
if (waive) {
|
|
284
296
|
if (!gateFailPark || !failedGate) {
|
|
@@ -173,6 +173,8 @@ export declare function doctorProbePreflight(cwd?: string, cfg?: {
|
|
|
173
173
|
tipTest?: string | undefined;
|
|
174
174
|
lint?: string | undefined;
|
|
175
175
|
diffCap?: number | undefined;
|
|
176
|
+
evidenceQuotaBytes?: number | undefined;
|
|
177
|
+
repairSelection?: boolean | undefined;
|
|
176
178
|
byShape?: Partial<Record<"plan" | "spec" | "implement" | "tests" | "docs" | "migration" | "ui" | "refactor" | "chore", {
|
|
177
179
|
[x: string]: unknown;
|
|
178
180
|
acceptance?: boolean | undefined;
|
|
@@ -206,8 +208,8 @@ export declare function doctorProbePreflight(cwd?: string, cfg?: {
|
|
|
206
208
|
};
|
|
207
209
|
executionPolicy?: {
|
|
208
210
|
boundedInfrastructure: boolean;
|
|
209
|
-
repairSelection: boolean;
|
|
210
211
|
taskExecutionLimitMs: number;
|
|
212
|
+
repairSelection?: boolean | undefined;
|
|
211
213
|
} | undefined;
|
|
212
214
|
setup?: string | undefined;
|
|
213
215
|
cost?: {
|
|
@@ -298,6 +300,8 @@ export declare function cachedDoctorDiagnostics(cwd?: string, adapters?: WorkerA
|
|
|
298
300
|
tipTest?: string | undefined;
|
|
299
301
|
lint?: string | undefined;
|
|
300
302
|
diffCap?: number | undefined;
|
|
303
|
+
evidenceQuotaBytes?: number | undefined;
|
|
304
|
+
repairSelection?: boolean | undefined;
|
|
301
305
|
byShape?: Partial<Record<"plan" | "spec" | "implement" | "tests" | "docs" | "migration" | "ui" | "refactor" | "chore", {
|
|
302
306
|
[x: string]: unknown;
|
|
303
307
|
acceptance?: boolean | undefined;
|
|
@@ -331,8 +335,8 @@ export declare function cachedDoctorDiagnostics(cwd?: string, adapters?: WorkerA
|
|
|
331
335
|
};
|
|
332
336
|
executionPolicy?: {
|
|
333
337
|
boundedInfrastructure: boolean;
|
|
334
|
-
repairSelection: boolean;
|
|
335
338
|
taskExecutionLimitMs: number;
|
|
339
|
+
repairSelection?: boolean | undefined;
|
|
336
340
|
} | undefined;
|
|
337
341
|
setup?: string | undefined;
|
|
338
342
|
cost?: {
|
|
@@ -5,7 +5,7 @@ import { allAdapters, discoverChannels, doctorAgeMs, initDoctorReuse, modelAuthE
|
|
|
5
5
|
import { catalogModelAdvisory, catalogTierRanking, declaredModelWindow, fleetUnclassifiedModels } from "../../adapters/model-lints.js";
|
|
6
6
|
import { CATALOG_REFRESH_TIMEOUT_MS, formatCatalogRefreshLegs, readCachedCatalog, refreshCatalogCommand } from "../../adapters/catalog-remote.js";
|
|
7
7
|
import { CLAUDE_ALIAS_IDENTITY_STAMPS, readClaudeAliasIdentity } from "../../adapters/claude-code.js";
|
|
8
|
-
import { fleetEditableFromConfig, fleetEditableEquals, formatFleetPrint, globalConfigDir, DENY_SCOPES, initialDenyPropOf, loadConfigWithMode, lowerLayerModelOverrides, overlayBytesLoadError, readOverlayFile, stagedDenyKeyOf, renderFleetOverlayWrite, repoOverlayPath, ROUTING_MODES, TIER_RANK, unifiedYamlDiff, } from "../../config/config.js";
|
|
8
|
+
import { ConfigError, fleetEditableFromConfig, fleetEditableEquals, formatFleetPrint, globalConfigDir, DENY_SCOPES, initialDenyPropOf, loadConfigWithMode, lowerLayerModelOverrides, overlayBytesLoadError, readOverlayFile, stagedDenyKeyOf, renderFleetOverlayWrite, repoOverlayPath, ROUTING_MODES, TIER_RANK, unifiedYamlDiff, } from "../../config/config.js";
|
|
9
9
|
import { exclusionReason, projectFleetWhy, renderFleetWhy } from "../../config/fleet-why.js";
|
|
10
10
|
import { SHAPES, TIERS } from "../../graph/schema.js";
|
|
11
11
|
import { doctor } from "./doctor.js";
|
|
@@ -346,8 +346,10 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
346
346
|
const channels = discoverChannels(cfg, adapters, health);
|
|
347
347
|
const profile = loadRoutingProfile(cwd, cfg, { preview: true });
|
|
348
348
|
const stagedDenyOf = (editable) => Object.fromEntries(DENY_SCOPES.map((scope) => [stagedDenyKeyOf(scope), editable[scope.key] ?? []]));
|
|
349
|
+
// OBS-1188: previews stage no effort, so both sides drop efforts — the writer then needs no lower layers
|
|
350
|
+
const previewInitial = { ...structuredClone(initial), efforts: undefined };
|
|
349
351
|
const stagedEditable = (map, deny) => ({
|
|
350
|
-
...structuredClone(
|
|
352
|
+
...structuredClone(previewInitial),
|
|
351
353
|
...Object.fromEntries(DENY_SCOPES.map((scope) => [scope.key, deny[stagedDenyKeyOf(scope)]])),
|
|
352
354
|
// OBS-1046: the allow complement is staged beside the deny lists, never folded into them
|
|
353
355
|
allowOut: deny.allowOut ?? initial.allowOut,
|
|
@@ -362,7 +364,7 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
362
364
|
let preview;
|
|
363
365
|
try {
|
|
364
366
|
const bytes = renderFleetOverlayWrite(currentRepoOverlayText(cwd), {
|
|
365
|
-
initial,
|
|
367
|
+
initial: previewInitial,
|
|
366
368
|
edited: stagedEditable(map, deny),
|
|
367
369
|
universe,
|
|
368
370
|
...(mode !== rm.mode.mode ? { mode } : {}),
|
|
@@ -701,8 +703,25 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
701
703
|
const initialJudge = `${cfg.judge.adapter}:${cfg.judge.model}`;
|
|
702
704
|
let pendingWrite = null;
|
|
703
705
|
// OBS-1182: the layers under the repo overlay, read raw, so clearing an effort masks an inherited
|
|
704
|
-
// one even when those layers only validate beside repo fields.
|
|
705
|
-
|
|
706
|
+
// one even when those layers only validate beside repo fields. OBS-1188: re-read at every review
|
|
707
|
+
// and save, never an assembly-time snapshot; an unreadable read keeps the last good one for the
|
|
708
|
+
// preview only — the save guard below refuses it.
|
|
709
|
+
let lowerOverrides = {};
|
|
710
|
+
// OBS-1188: the reviewed bytes are the only bytes that may land. Re-rendered from the repo overlay
|
|
711
|
+
// and lower layers as they are NOW; any difference is a stale preview. Null = still current.
|
|
712
|
+
const stalePreview = (write, reviewed, prior = currentRepoOverlayText(cwd)) => {
|
|
713
|
+
const lower = lowerLayerModelOverrides({ globalDir });
|
|
714
|
+
if (!lower.ok)
|
|
715
|
+
return lower.error;
|
|
716
|
+
try {
|
|
717
|
+
if (renderFleetOverlayWrite(prior, { ...write, lowerOverrides: lower.overrides }) === reviewed)
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
return error.message;
|
|
722
|
+
}
|
|
723
|
+
return "stale preview — the repo overlay or a lower config layer changed after the review diff was rendered; press w to review what would be written now";
|
|
724
|
+
};
|
|
706
725
|
const reviewOverlay = (state) => {
|
|
707
726
|
const staged = structuredClone(initial);
|
|
708
727
|
// OBS-994/FL-1, OBS-1099 add.1: every schema-enumerated deny scope rides the same review/write funnel
|
|
@@ -737,6 +756,9 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
737
756
|
pendingWrite = null;
|
|
738
757
|
return { kind: "empty" };
|
|
739
758
|
}
|
|
759
|
+
const lower = lowerLayerModelOverrides({ globalDir });
|
|
760
|
+
if (lower.ok)
|
|
761
|
+
lowerOverrides = lower.overrides;
|
|
740
762
|
const write = {
|
|
741
763
|
initial,
|
|
742
764
|
edited: staged,
|
|
@@ -762,17 +784,20 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
762
784
|
catch {
|
|
763
785
|
poolNotes = [];
|
|
764
786
|
}
|
|
787
|
+
const notes = [...(lower.ok ? [] : [`${lower.error} — y will be refused`]), ...poolNotes];
|
|
765
788
|
return {
|
|
766
789
|
kind: "diff",
|
|
767
790
|
before,
|
|
768
791
|
after,
|
|
769
792
|
diff: unifiedYamlDiff(before, after, path),
|
|
770
793
|
path,
|
|
771
|
-
...(
|
|
794
|
+
...(notes.length ? { notes } : {}),
|
|
772
795
|
};
|
|
773
796
|
};
|
|
774
|
-
const
|
|
797
|
+
const loaderGuard = io.reloadGuard
|
|
775
798
|
?? ((bytes) => overlayBytesLoadError(cwd, bytes, { globalDir }));
|
|
799
|
+
// OBS-1188: a stale preview or an unreadable lower layer answers on the same error channel as the loader
|
|
800
|
+
const reloadGuard = (bytes) => (pendingWrite && stalePreview(pendingWrite, bytes)) || loaderGuard(bytes);
|
|
776
801
|
const props = {
|
|
777
802
|
ageMs: doctorAgeMs(cwd),
|
|
778
803
|
adapters,
|
|
@@ -820,7 +845,20 @@ export async function assembleFleetEditor(cwd, adapters, io, opts) {
|
|
|
820
845
|
const write = pendingWrite;
|
|
821
846
|
if (!write)
|
|
822
847
|
throw new Error("fleet write reached confirmation without a staged overlay mutation");
|
|
823
|
-
|
|
848
|
+
// OBS-1188: re-checked against the bytes on disk at write time; a refusal throws before the temp file exists
|
|
849
|
+
try {
|
|
850
|
+
writeFleetOverlay(result.review.path, (prior) => {
|
|
851
|
+
const stale = stalePreview(write, result.review.after, prior);
|
|
852
|
+
if (stale !== null)
|
|
853
|
+
throw new ConfigError(stale);
|
|
854
|
+
return result.review.after;
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
if (error instanceof ConfigError)
|
|
859
|
+
return `fleet: nothing written — ${error.message}`;
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
824
862
|
// OBS-529: a freshly classified model has no probe verdict (doctor probes CONFIGURED models,
|
|
825
863
|
// and it was not configured at probe time), so it stays unroutable — invisible in every
|
|
826
864
|
// picker — until the next probe. Name the step, or the classify flow reads as broken.
|
|
@@ -11,9 +11,9 @@ import { batteryPriority, chainDepth, dispatchWaves, graphDefinitionHash, loadGr
|
|
|
11
11
|
import { filesGlob } from "../../graph/files-glob.js";
|
|
12
12
|
import { renderAcceptanceItem } from "../../graph/schema.js";
|
|
13
13
|
import { pendingDaemonApprovalActions, resolveRunMode } from "../../run/daemon.js";
|
|
14
|
-
import { disallowedBy, excludedChannels, exclusionLine, routingEntrySeatLines } from "../../route/preference.js";
|
|
14
|
+
import { disallowedBy, excludedChannels, exclusionLine, observedSeat, routingEntrySeatLines } from "../../route/preference.js";
|
|
15
15
|
import { decayWeight, HALF_LIFE_RUNS, staffLedEvidence } from "../../route/profile.js";
|
|
16
|
-
import { route, RoutingError } from "../../route/router.js";
|
|
16
|
+
import { resolvedFloor, route, RoutingError } from "../../route/router.js";
|
|
17
17
|
import { auditNamedTestOracles, listVitestTests } from "../../gates/acceptance.js";
|
|
18
18
|
import { modelId, modelProvider, pickReviewer } from "../../gates/review.js";
|
|
19
19
|
import { Journal, loadRoutingProfile, readProfileCursor, recordedGraphDefinitionHash, RUNS_WINDOW } from "../../run/journal.js";
|
|
@@ -237,16 +237,21 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
237
237
|
...(runLine ? [runLine] : []),
|
|
238
238
|
"",
|
|
239
239
|
];
|
|
240
|
-
|
|
240
|
+
// OBS-1186: the judge seat is judged under doctor's cached identity for that exact channel — the
|
|
241
|
+
// runtime judge's rule; unknown stays conservative (alias-family deny).
|
|
242
|
+
const judgeDeny = disallowedBy(observedSeat(health, cfg.judge.adapter, cfg.judge.model), cfg.routing, "judge");
|
|
241
243
|
if (judgeDeny?.by === "deny") {
|
|
242
244
|
lines.push(`REFUSAL OBS-576: judge seat ${cfg.judge.adapter}:${cfg.judge.model} is removed by flat routing.deny (${judgeDeny.entry}) — move worker-only policy to routing.deny.workers`, "");
|
|
243
245
|
}
|
|
244
246
|
const entrySeats = routingEntrySeatLines(cfg);
|
|
245
247
|
if (entrySeats.length)
|
|
246
248
|
lines.push(...entrySeats, "");
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
// OBS-1185: the floor route() resolved for THIS task — a task hint over the configured/mode floor.
|
|
250
|
+
const derivation = (t) => {
|
|
251
|
+
const floor = resolvedFloor(t, cfg);
|
|
252
|
+
if (!floor)
|
|
253
|
+
return null;
|
|
254
|
+
return ` floor ${floor.tier} ← ${floor.source === "task" ? "task hint" : mode.provenance[t.shape] ?? "config floors"}`;
|
|
250
255
|
};
|
|
251
256
|
const excluded = excludedChannels(cfg, adapters, health);
|
|
252
257
|
if (excluded.length)
|
|
@@ -408,7 +413,7 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
408
413
|
const review = reviewSeat(t, r.assignment);
|
|
409
414
|
cost += est + judge.cost + review.cost;
|
|
410
415
|
lines.push(` ${t.id.padEnd(6)} ${t.shape.padEnd(10)} c${String(t.complexity).padEnd(3)}→ ${r.assignment.adapter}:${r.assignment.model} [${r.assignment.channel}/${r.assignment.tier}]${t.timeoutMinutes !== undefined ? ` (timeout ${t.timeoutMinutes}m)` : ""}${t.humanGate ? " (human gate)" : ""}${est ? ` ~$${est.toFixed(2)}` : ""} — ${r.provenance}`, ` judge: ${judge.line}${judge.cost ? ` ~$${judge.cost.toFixed(2)}` : ""}`, ` review: ${review.line}${review.cost ? ` ~$${review.cost.toFixed(2)}` : ""}`, chainLine(t));
|
|
411
|
-
const d = derivation(t
|
|
416
|
+
const d = derivation(t);
|
|
412
417
|
if (d)
|
|
413
418
|
lines.push(d);
|
|
414
419
|
if (r.deviation) {
|
|
@@ -432,7 +437,7 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
432
437
|
throw e;
|
|
433
438
|
const msg = `${e.message}${exclusionReason(e.message)}`;
|
|
434
439
|
lines.push(` ${t.id.padEnd(6)} ${t.shape.padEnd(10)} !! ${msg}`, chainLine(t));
|
|
435
|
-
const d = derivation(t
|
|
440
|
+
const d = derivation(t);
|
|
436
441
|
if (d)
|
|
437
442
|
lines.push(d);
|
|
438
443
|
lints.push(`${t.id}: unroutable — ${msg}`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import type { BaselineCommand } from "../../gates/baseline.js";
|
|
1
2
|
import { type ChannelCost } from "../../report/cost.js";
|
|
2
3
|
import { type JournalEvent, type TelemetryRow } from "../../run/journal.js";
|
|
3
|
-
export declare function renderMarkdownRecord(runId: string, events: JournalEvent[], prices?: ChannelCost[], rows?: TelemetryRow[]): string;
|
|
4
|
+
export declare function renderMarkdownRecord(runId: string, events: JournalEvent[], prices?: ChannelCost[], rows?: TelemetryRow[], suite?: BaselineCommand): string;
|
|
4
5
|
export declare function report(argv: string[], cwd?: string): Promise<string>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { parseArgs } from "node:util";
|
|
3
4
|
import { ttyVisual } from "../../adapters/model-lints.js";
|
|
4
5
|
import { addUsage } from "../../adapters/types.js";
|
|
@@ -12,6 +13,7 @@ import { assignmentChannel, buildOperatorRecord, formatChannelMoney, formatChann
|
|
|
12
13
|
import { cellsOf, cellSummary } from "../../route/profile.js";
|
|
13
14
|
import { Journal, loadRoutingProfile } from "../../run/journal.js";
|
|
14
15
|
import { baselineProvenanceOf, fingerprintsOf, forgivenFingerprints, forgivenGateRow, formatBaselineProvenance, formatFingerprints, formatForgiven, formatTipProof, runEndTipProof, } from "../../run/daemon.js";
|
|
16
|
+
import { formatSpan, WALL_PRIORITY_TEXT, wallBudget, wallBudgetFacts } from "../../run/wall-budget.js";
|
|
15
17
|
import { deriveRunCockpitData } from "../../tui/cockpit/derive.js";
|
|
16
18
|
const n = (x) => x.toLocaleString("en-US"); // explicit locale — CI/darwin flake guard
|
|
17
19
|
const EM = "—";
|
|
@@ -75,6 +77,44 @@ const wallClock = (start, end) => {
|
|
|
75
77
|
const minutes = Math.floor(seconds / 60);
|
|
76
78
|
return minutes ? `${minutes}m ${seconds % 60}s` : `${seconds}s`;
|
|
77
79
|
};
|
|
80
|
+
// OBS-1201: the disjoint wall budget, or why the journal cannot give one — never a zeroed table.
|
|
81
|
+
const wallFacts = (events) => {
|
|
82
|
+
const budget = wallBudget(events);
|
|
83
|
+
if (!budget)
|
|
84
|
+
return [["window", "not measurable — the journal names no run-start with a readable timestamp"]];
|
|
85
|
+
return [["window", `${formatSpan(budget.wallMs)} — each instant counted once, by priority ${WALL_PRIORITY_TEXT}; task-time sums concurrent spans`], ...wallBudgetFacts(budget)];
|
|
86
|
+
};
|
|
87
|
+
// OBS-634: the four numbers the baseline capture already measured for the test command. A capture that
|
|
88
|
+
// measured nothing says so; a missing number is not measurable, never a zero.
|
|
89
|
+
const suiteTelemetry = (entry) => {
|
|
90
|
+
if (!entry)
|
|
91
|
+
return "not recorded — this run's baseline holds no test capture";
|
|
92
|
+
if (entry.infra)
|
|
93
|
+
return `not measurable — the baseline capture returned no verdict (${entry.invalidCause ?? "infra"})`;
|
|
94
|
+
if (typeof entry.durationMs !== "number" || !Number.isFinite(entry.durationMs))
|
|
95
|
+
return "not recorded — the baseline predates capture timing";
|
|
96
|
+
const unmeasured = "not measurable (the runner named no per-file durations)";
|
|
97
|
+
const sum = entry.fileDurationSumMs;
|
|
98
|
+
const parallelism = entry.impliedParallelism;
|
|
99
|
+
const longest = entry.longestFile;
|
|
100
|
+
return [
|
|
101
|
+
`wall ${formatSpan(entry.durationMs)}`,
|
|
102
|
+
`file-sum ${typeof sum === "number" ? formatSpan(sum) : unmeasured}`,
|
|
103
|
+
`implied parallelism ${typeof parallelism === "number" ? parallelism.toFixed(2) : unmeasured}`,
|
|
104
|
+
`longest file ${longest ? `${longest.file} ${formatSpan(longest.durationMs)}` : unmeasured}`,
|
|
105
|
+
...(typeof entry.fileCount === "number" ? [`${entry.fileCount} files`] : []),
|
|
106
|
+
].join(" · ");
|
|
107
|
+
};
|
|
108
|
+
/** The run's own baseline.json test entry; absent or unreadable reads as not recorded. */
|
|
109
|
+
const baselineTestEntry = (runDir) => {
|
|
110
|
+
try {
|
|
111
|
+
const baseline = JSON.parse(readFileSync(join(runDir, "baseline.json"), "utf8"));
|
|
112
|
+
return baseline.commands?.test;
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
78
118
|
const detail = (value) => typeof value === "string" || typeof value === "number" ? String(value) : EM;
|
|
79
119
|
// T11: a gate that declined never ran. Drop those before the comparison metrics so the pass rate
|
|
80
120
|
// counts only gates that actually ran — a declined gate is never a pass and never pads the base.
|
|
@@ -194,7 +234,7 @@ const fingerprintClauses = (data) => {
|
|
|
194
234
|
+ `; forgiven vs baseline: ${formatFingerprints(forgiven)} — ${formatBaselineProvenance(baselineProvenanceOf(data.baselineProvenance))}`;
|
|
195
235
|
};
|
|
196
236
|
// VIS-07 / REC-01: derived only from the run journal, telemetry, and local configuration.
|
|
197
|
-
export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
237
|
+
export function renderMarkdownRecord(runId, events, prices = [], rows = [], suite) {
|
|
198
238
|
const runStart = events.find((e) => e.event === "run-start");
|
|
199
239
|
const runEnd = [...events].reverse().find((e) => e.event === "run-end");
|
|
200
240
|
const baseRef = typeof runStart?.data.baseRef === "string" ? runStart.data.baseRef : EM;
|
|
@@ -242,6 +282,11 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
242
282
|
`- **consults:** ${events.filter((e) => e.event === "consult-verdict").length}`,
|
|
243
283
|
`- **escalations:** ${events.filter((e) => e.event === "escalation").length}`,
|
|
244
284
|
"",
|
|
285
|
+
"## Wall budget",
|
|
286
|
+
"",
|
|
287
|
+
...wallFacts(events).map(([label, fact]) => `- **${label}:** ${fact}`),
|
|
288
|
+
`- **suite telemetry (baseline test capture):** ${suiteTelemetry(suite)}`,
|
|
289
|
+
"",
|
|
245
290
|
];
|
|
246
291
|
lines.push("## Channels", "");
|
|
247
292
|
const operatorRecords = buildOperatorRecord(events, prices);
|
|
@@ -326,7 +371,7 @@ export function renderMarkdownRecord(runId, events, prices = [], rows = []) {
|
|
|
326
371
|
}
|
|
327
372
|
return lines.join("\n").trimEnd() + "\n";
|
|
328
373
|
}
|
|
329
|
-
function textReport(runId, events, rows, cwd) {
|
|
374
|
+
function textReport(runId, events, rows, cwd, suite) {
|
|
330
375
|
// one group per adapter:model, carrying channel + the folded usage across its rows
|
|
331
376
|
const groups = new Map();
|
|
332
377
|
for (const r of rows) {
|
|
@@ -388,6 +433,10 @@ function textReport(runId, events, rows, cwd) {
|
|
|
388
433
|
`tickmark rate: ${gateRan ? Math.round((100 * gatePass) / gateRan) : 0}% (${gatePass}/${gateRan})${declinedCount ? ` · declined: ${declinedCount}` : ""}`,
|
|
389
434
|
`escalations: ${escalations} · National Office consults: ${consults} · quota failovers: ${failovers}`,
|
|
390
435
|
"",
|
|
436
|
+
"wall budget — exposed wall, disjoint:",
|
|
437
|
+
...wallFacts(events).map(([label, fact]) => ` ${label.padEnd(14)} ${fact}`),
|
|
438
|
+
` ${"suite".padEnd(14)} ${suiteTelemetry(suite)}`,
|
|
439
|
+
"",
|
|
391
440
|
"spend — tokens (measured where observed):",
|
|
392
441
|
...tokenLines,
|
|
393
442
|
"spend — money:",
|
|
@@ -407,7 +456,7 @@ const stylizeReport = (out) => {
|
|
|
407
456
|
return out;
|
|
408
457
|
return out
|
|
409
458
|
.replace(/^.*$/m, (first) => `${title(first)}\n${rule()}`) // non-global /m ⇒ first line only
|
|
410
|
-
.replace(/^(engagement summary — audit trail:|spend — tokens[^\n]*|spend — money:|learning \([^\n]*)$/gm, (l) => dim(l));
|
|
459
|
+
.replace(/^(engagement summary — audit trail:|wall budget — [^\n]*|spend — tokens[^\n]*|spend — money:|learning \([^\n]*)$/gm, (l) => dim(l));
|
|
411
460
|
};
|
|
412
461
|
export async function report(argv, cwd = process.cwd()) {
|
|
413
462
|
const { values, positionals } = parseArgs({
|
|
@@ -428,6 +477,7 @@ export async function report(argv, cwd = process.cwd()) {
|
|
|
428
477
|
const events = j.read();
|
|
429
478
|
const rows = j.readTelemetry();
|
|
430
479
|
const cfg = loadConfig(cwd);
|
|
480
|
+
const suite = baselineTestEntry(j.dir);
|
|
431
481
|
let bundleNote = "";
|
|
432
482
|
if (values.bundle) {
|
|
433
483
|
// Local-only write of the pure proof packet — no network path exists in buildProofBundle.
|
|
@@ -454,7 +504,7 @@ export async function report(argv, cwd = process.cwd()) {
|
|
|
454
504
|
comparison = "\n" + outcome.text;
|
|
455
505
|
}
|
|
456
506
|
if (values.md) {
|
|
457
|
-
return bundleNote + renderMarkdownRecord(runId, events, estimateCosts(rows, cfg.cost), rows) + comparison;
|
|
507
|
+
return bundleNote + renderMarkdownRecord(runId, events, estimateCosts(rows, cfg.cost), rows, suite) + comparison;
|
|
458
508
|
}
|
|
459
|
-
return bundleNote + stylizeReport(textReport(runId, events, rows, cwd)) + comparison;
|
|
509
|
+
return bundleNote + stylizeReport(textReport(runId, events, rows, cwd, suite)) + comparison;
|
|
460
510
|
}
|
|
@@ -17,6 +17,7 @@ import { isPidLive, runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
|
17
17
|
import { normalizeGateOutcome } from "../../run/outcome.js";
|
|
18
18
|
import { desiredPanes } from "../../run/reconcile.js";
|
|
19
19
|
import { normalizeStallSnapshot } from "../../run/stall.js";
|
|
20
|
+
import { formatSpan, wallBudget, wallBudgetFacts } from "../../run/wall-budget.js";
|
|
20
21
|
import { armWatchSupervision, observeNamedRun, WATCH_OWNER_ENV, readSupervision, supervisionText } from "../../run/supervision.js";
|
|
21
22
|
import { deriveRunCockpitData, } from "../../tui/cockpit/derive.js";
|
|
22
23
|
import { cellWidth, fitCells, wrapCells } from "../../tui/cockpit/width.js";
|
|
@@ -1155,6 +1156,7 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1155
1156
|
...(hotPhase ? { hotPhase } : {}),
|
|
1156
1157
|
...(runId ? { runId } : {}),
|
|
1157
1158
|
workerPhases: [...phases.values()].filter((phase) => phase.phase === "worker"),
|
|
1159
|
+
events,
|
|
1158
1160
|
};
|
|
1159
1161
|
}
|
|
1160
1162
|
// TTY: the operator-approved task table. The daemon gives this surface a ~110-column pane first;
|
|
@@ -1364,8 +1366,22 @@ binaryVersion, now = Date.now(), animationFrame = 0, workerLiveness = new Map(),
|
|
|
1364
1366
|
...(hotPhase ? { hotPhase } : {}),
|
|
1365
1367
|
...(runId ? { runId } : {}),
|
|
1366
1368
|
workerPhases: [...phases.values()].filter((phase) => phase.phase === "worker"),
|
|
1369
|
+
events,
|
|
1367
1370
|
};
|
|
1368
1371
|
};
|
|
1372
|
+
// OBS-1201: the one-shot print closes with where the run's wall went (src/run/wall-budget.ts). A
|
|
1373
|
+
// window of zero length measured nothing, so it prints nothing; watch frames are untouched. Rows wrap
|
|
1374
|
+
// to the board width, so narrowing costs rows, never columns.
|
|
1375
|
+
const wallBudgetLines = (events) => {
|
|
1376
|
+
const budget = wallBudget(events);
|
|
1377
|
+
if (!budget || budget.wallMs <= 0)
|
|
1378
|
+
return [];
|
|
1379
|
+
const columns = Math.max(40, process.stdout.columns ?? 120);
|
|
1380
|
+
return [
|
|
1381
|
+
` wall budget ${formatSpan(budget.wallMs)} · each instant once, by priority`,
|
|
1382
|
+
...wallBudgetFacts(budget).map(([label, fact]) => ` ${label} ${fact}`),
|
|
1383
|
+
].flatMap((line) => wrapCells(line, columns, { continuationPrefix: " " }));
|
|
1384
|
+
};
|
|
1369
1385
|
/**
|
|
1370
1386
|
* The compact form a statusline calls, so journal interpretation happens ONCE, inside the product.
|
|
1371
1387
|
* (An operator-local bash reimplementation counted `tip-verify` events instead of reading the
|
|
@@ -1401,7 +1417,9 @@ export async function status(argv, cwd = process.cwd(), opts = {}) {
|
|
|
1401
1417
|
// The task-table frame owns its own two-row brand lockup; the four-row global banner would
|
|
1402
1418
|
// create the second header the approved replacement removes.
|
|
1403
1419
|
if (!argv.includes("--watch")) {
|
|
1404
|
-
|
|
1420
|
+
const frame = renderFrame(cwd, binaryVersion, opts.now?.() ?? Date.now(), 0, undefined, false, namedRunId);
|
|
1421
|
+
const wall = wallBudgetLines(frame.events);
|
|
1422
|
+
return wall.length ? [frame.content, "", ...wall].join("\n") : frame.content;
|
|
1405
1423
|
}
|
|
1406
1424
|
const eventStream = argv.some((arg) => arg === "--events" || arg === "--jsonl" || arg === "--decision-events");
|
|
1407
1425
|
const webhookUrl = opts.webhookUrl
|
package/dist/compile/native.js
CHANGED
|
@@ -911,6 +911,13 @@ acceptance is required on every task (a nested list of observable outcomes).
|
|
|
911
911
|
- Enumerating one axis exhaustively is what hides the others. A spec that guards PARTIAL coverage
|
|
912
912
|
site-by-site, member-by-member, can be defeated wholesale by CONDITIONAL coverage, which leaves
|
|
913
913
|
every enumeration satisfied. After you enumerate, ask what a single flag would do to the whole set.
|
|
914
|
+
- A GOAL STATING AN INVARIANT OVER "every", "never" OR "any" CARRIES ITS CLOSED CASE TABLE AS A
|
|
915
|
+
CRITERION: the in-scope consumers, bridges, operations and event sequences it ranges over, each a
|
|
916
|
+
row a test exercises. An unenumerated invariant is repaired one edge per review round while every
|
|
917
|
+
round re-judges the accumulated diff (OBS-1019 add.2). The table binds author and reviewer, never a
|
|
918
|
+
worker's claim: each review material binds its declared class to the goal clause or criterion it
|
|
919
|
+
violates through input-to-consequence evidence marked executed, static or blocked. A worker's own
|
|
920
|
+
table alone never silences a real finding, and blocked evidence is never a pass.
|
|
914
921
|
- EVERY CRITERION NAMES THE PAIR IT DISCRIMINATES: the correct case that MUST PASS, and the
|
|
915
922
|
neighbouring plausible-wrong or false-clean case that MUST FAIL. Two easy examples that both pass are
|
|
916
923
|
not discrimination — they are two ways of being green, and the wrong half is the whole point: it is
|
package/dist/config/config.d.ts
CHANGED
|
@@ -181,13 +181,13 @@ export declare function effectiveReviewPolicy(files: ReadonlyArray<string>, revi
|
|
|
181
181
|
export declare function criticalPathHits(files: ReadonlyArray<string>, criticalPaths?: ReadonlyArray<string>): string[];
|
|
182
182
|
export declare const ExecutionPolicySchema: z.ZodObject<{
|
|
183
183
|
boundedInfrastructure: z.ZodDefault<z.ZodBoolean>;
|
|
184
|
-
repairSelection: z.
|
|
184
|
+
repairSelection: z.ZodOptional<z.ZodBoolean>;
|
|
185
185
|
taskExecutionLimitMs: z.ZodNumber;
|
|
186
186
|
}, z.core.$strict>;
|
|
187
187
|
export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
188
188
|
executionPolicy: z.ZodOptional<z.ZodObject<{
|
|
189
189
|
boundedInfrastructure: z.ZodDefault<z.ZodBoolean>;
|
|
190
|
-
repairSelection: z.
|
|
190
|
+
repairSelection: z.ZodOptional<z.ZodBoolean>;
|
|
191
191
|
taskExecutionLimitMs: z.ZodNumber;
|
|
192
192
|
}, z.core.$strict>>;
|
|
193
193
|
concurrency: z.ZodNumber;
|
|
@@ -309,6 +309,8 @@ export declare const TickmarkrConfigSchema: z.ZodObject<{
|
|
|
309
309
|
tipTest: z.ZodOptional<z.ZodString>;
|
|
310
310
|
lint: z.ZodOptional<z.ZodString>;
|
|
311
311
|
diffCap: z.ZodOptional<z.ZodNumber>;
|
|
312
|
+
evidenceQuotaBytes: z.ZodOptional<z.ZodNumber>;
|
|
313
|
+
repairSelection: z.ZodOptional<z.ZodBoolean>;
|
|
312
314
|
byShape: z.ZodOptional<z.ZodOptional<z.ZodRecord<z.ZodEnum<{
|
|
313
315
|
plan: "plan";
|
|
314
316
|
spec: "spec";
|
|
@@ -374,6 +376,7 @@ export type TickmarkrConfig = z.infer<typeof TickmarkrConfigSchema>;
|
|
|
374
376
|
export declare class ConfigError extends Error {
|
|
375
377
|
constructor(message: string);
|
|
376
378
|
}
|
|
379
|
+
export declare const DEFAULT_EVIDENCE_QUOTA_BYTES: number;
|
|
377
380
|
export declare const DEFAULT_CONFIG: TickmarkrConfig;
|
|
378
381
|
export declare function globalConfigDir(): string;
|
|
379
382
|
export declare function overlayPreferShapes(repoRoot: string, opts?: {
|
|
@@ -395,12 +398,22 @@ export declare function loadConfigWithMode(repoRoot: string, opts?: {
|
|
|
395
398
|
cfg: TickmarkrConfig;
|
|
396
399
|
mode: ModeResolution;
|
|
397
400
|
};
|
|
401
|
+
/** OBS-1182: tiers.<adapter>.modelOverrides as the layers under the repo overlay resolve them. */
|
|
402
|
+
export type LowerLayerModelOverrides = Record<string, Record<string, Record<string, unknown>>>;
|
|
398
403
|
/** OBS-1182: tiers.<adapter>.modelOverrides as the layers under the repo overlay (defaults + global)
|
|
399
404
|
* merge them. Read raw, never schema-validated: a lower layer may only validate beside repo fields
|
|
400
|
-
* (global `vendor: null` completed by a repo vendor) and its override metadata is still inherited.
|
|
405
|
+
* (global `vendor: null` completed by a repo vendor) and its override metadata is still inherited.
|
|
406
|
+
* OBS-1188: an unparseable global file, or a non-map where this path expects a map, is an error —
|
|
407
|
+
* never an empty lower layer, which would silently drop the metadata a write must re-mask. */
|
|
401
408
|
export declare function lowerLayerModelOverrides(opts?: {
|
|
402
409
|
globalDir?: string;
|
|
403
|
-
}):
|
|
410
|
+
}): {
|
|
411
|
+
ok: true;
|
|
412
|
+
overrides: LowerLayerModelOverrides;
|
|
413
|
+
} | {
|
|
414
|
+
ok: false;
|
|
415
|
+
error: string;
|
|
416
|
+
};
|
|
404
417
|
export declare function loadConfig(repoRoot: string, opts?: {
|
|
405
418
|
globalDir?: string;
|
|
406
419
|
}): TickmarkrConfig;
|