tickmarkr 2.6.3 → 2.6.4
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 +10 -2
- package/dist/adapters/codex.d.ts +2 -1
- package/dist/adapters/codex.js +26 -5
- package/dist/adapters/model-lints.d.ts +1 -0
- package/dist/adapters/model-lints.js +48 -1
- package/dist/adapters/model-windows.d.ts +2 -1
- package/dist/adapters/model-windows.js +18 -7
- package/dist/adapters/types.d.ts +6 -0
- package/dist/cli/commands/approve.js +27 -2
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +6 -1
- package/dist/cli/commands/fleet.js +241 -74
- package/dist/cli/commands/init.js +5 -3
- package/dist/cli/commands/report.d.ts +39 -2
- package/dist/cli/commands/report.js +227 -10
- package/dist/cli/commands/resume.js +2 -4
- package/dist/cli/commands/run.d.ts +11 -0
- package/dist/cli/commands/run.js +33 -5
- package/dist/cli/commands/status.js +30 -3
- package/dist/cli/commands/verify.js +332 -121
- package/dist/compile/native.js +130 -0
- package/dist/config/config.d.ts +25 -7
- package/dist/config/config.js +92 -11
- package/dist/route/router.d.ts +11 -0
- package/dist/route/router.js +29 -8
- package/dist/run/daemon.js +8 -3
- package/dist/run/journal.d.ts +97 -1
- package/dist/run/journal.js +337 -2
- package/dist/tui/cockpit/live-store.d.ts +6 -0
- package/dist/tui/cockpit/live-store.js +36 -11
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +2 -2
- package/skills/tickmarkr-loop/SKILL.md +10 -4
package/README.md
CHANGED
|
@@ -183,8 +183,16 @@ can enact the release at its next task boundary; a different live run must end b
|
|
|
183
183
|
one resumes. Keep any resume refusal and its remediation visible, including a deny/prefer
|
|
184
184
|
config conflict; repair the source/config as directed, never edit the compiled graph to
|
|
185
185
|
force success. After resume, CURRENT TIP is PENDING until fresh evidence arrives. Completion
|
|
186
|
-
requires the latest run-end, a nonfailed known tip result,
|
|
187
|
-
`blocked` and `pending` buckets
|
|
186
|
+
requires the latest run-end, a nonfailed known tip result, empty `failed`, `human`,
|
|
187
|
+
`blocked` and `pending` buckets, and owed checks outstanding empty AND known in CURRENT
|
|
188
|
+
`tickmarkr status <runId>` (`outstanding 0`); the completed case has 3/3 recorded merges.
|
|
189
|
+
Empty buckets alone are not completion (D-660): 3/3 merged with every bucket empty and the
|
|
190
|
+
tip passed, after the operator waived T2's review, leaves one accepted-risk review check owed.
|
|
191
|
+
`run`/`resume` exit 0 on execution alone, their final line says
|
|
192
|
+
`execution complete; outstanding 1 (T2 review)` rather than `verified`, and status reads
|
|
193
|
+
`outstanding 1`. That run completes only when `tickmarkr verify --record <runId>` discharges
|
|
194
|
+
the check and CURRENT status reads `outstanding 0` — a discharge after run-end counts, while the
|
|
195
|
+
run-end record keeps its old count; `outstanding unknown` never completes. An unrelated
|
|
188
196
|
graph says “not comparable” and supplies no borrowed denominator. Historical GATES RAN
|
|
189
197
|
does not establish current completion.
|
|
190
198
|
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type TrustDialog, type TrustVerdict, type WorkerAdapter } from "./types.js";
|
|
1
|
+
import { type ModelRetirement, type TrustDialog, type TrustVerdict, type WorkerAdapter } from "./types.js";
|
|
2
2
|
export declare function readCodexModelsCache(path?: string): {
|
|
3
3
|
models: string[];
|
|
4
4
|
fetchedAt?: string;
|
|
5
|
+
retirements?: Record<string, ModelRetirement | null>;
|
|
5
6
|
};
|
|
6
7
|
export declare const CODEX_TRUST_DIALOG: TrustDialog;
|
|
7
8
|
export declare const CODEX_INPUT_BOX: import("./types.js").InputBox;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -61,20 +61,39 @@ function codexSessionDayDirs(sessions, sinceMs) {
|
|
|
61
61
|
// Fails OPEN to { models: [] } on missing/corrupt cache. Version drift observed (installed CLI
|
|
62
62
|
// 0.143.0 vs cache client_version 0.144.0, 2026-07-10) — hence the defensive try/catch and no shape
|
|
63
63
|
// assumptions. CODEX_HOME is codex's own relocation env. fetchedAt is the honest codex knowledge age.
|
|
64
|
+
// v2.6.4 T2 (B1a): each listed entry's `upgrade` is the CLI's own retirement notice (0.159.0 cache,
|
|
65
|
+
// 2026-09-29: gpt-5.5 → {model: gpt-5.6-sol, retirement_at: 2026-10-14T19:00:00Z}; every other listed
|
|
66
|
+
// id carries upgrade: null). null records known clean; a malformed or absent `upgrade` records nothing,
|
|
67
|
+
// so that id stays unknown. An unreadable cache returns no retirements at all (unknown, never clean).
|
|
64
68
|
export function readCodexModelsCache(path) {
|
|
65
69
|
const p = path ?? join(process.env.CODEX_HOME || join(homedir(), ".codex"), "models_cache.json");
|
|
66
70
|
try {
|
|
67
71
|
const d = JSON.parse(readFileSync(p, "utf8"));
|
|
68
|
-
const
|
|
69
|
-
.filter((m) => m?.visibility === "list" && typeof m.slug === "string" && m.slug.length > 0)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
const listed = (d.models ?? [])
|
|
73
|
+
.filter((m) => m?.visibility === "list" && typeof m.slug === "string" && m.slug.length > 0 && MODEL_ID_RE.test(m.slug));
|
|
74
|
+
const retirements = {};
|
|
75
|
+
for (const m of listed) {
|
|
76
|
+
const notice = codexRetirement(m.upgrade);
|
|
77
|
+
if (notice !== undefined)
|
|
78
|
+
retirements[m.slug] = notice;
|
|
79
|
+
}
|
|
80
|
+
return { models: listed.map((m) => m.slug), fetchedAt: typeof d.fetched_at === "string" ? d.fetched_at : undefined, retirements };
|
|
73
81
|
}
|
|
74
82
|
catch {
|
|
75
83
|
return { models: [] };
|
|
76
84
|
}
|
|
77
85
|
}
|
|
86
|
+
function codexRetirement(upgrade) {
|
|
87
|
+
if (upgrade === null)
|
|
88
|
+
return null;
|
|
89
|
+
const u = upgrade;
|
|
90
|
+
if (typeof u?.retirement_at !== "string" || !Number.isFinite(Date.parse(u.retirement_at)))
|
|
91
|
+
return undefined;
|
|
92
|
+
if (u.model === undefined)
|
|
93
|
+
return { retiresAt: u.retirement_at };
|
|
94
|
+
// the successor id comes from an external file and is rendered in lints — same charset gate as slugs
|
|
95
|
+
return typeof u.model === "string" && MODEL_ID_RE.test(u.model) ? { retiresAt: u.retirement_at, successor: u.model } : undefined;
|
|
96
|
+
}
|
|
78
97
|
// tickmarkr worktrees keep their gitdir under the MAIN repo's .git/worktrees/<name> — outside the
|
|
79
98
|
// workspace-write sandbox root — so git commit dies on index.lock (incident run-20260709-104447
|
|
80
99
|
// P87-08: "Unable to create …/.git/worktrees/…/index.lock"). Expanded by the pane shell at the
|
|
@@ -272,6 +291,8 @@ export const codex = {
|
|
|
272
291
|
// v1.5 MODEL-05: codex reads an offline cache, so "now" would re-stamp an ancient cache fresh and the
|
|
273
292
|
// 30-day staleness lint could never fire. Surface the cache's own fetched_at so doctor stamps the real age.
|
|
274
293
|
listModelsFetchedAt: () => readCodexModelsCache().fetchedAt,
|
|
294
|
+
// v2.6.4 T2 (B1a): the cache's per-model retirement notices, persisted beside models in doctor.json.
|
|
295
|
+
listModelsRetirements: () => readCodexModelsCache().retirements,
|
|
275
296
|
collectUsage(cwd, sinceMs) {
|
|
276
297
|
try {
|
|
277
298
|
const real = realpathSync(cwd);
|
|
@@ -81,6 +81,7 @@ export declare function modelLints(cfg: TickmarkrConfig, health: Record<string,
|
|
|
81
81
|
tty?: boolean;
|
|
82
82
|
stateDir?: string;
|
|
83
83
|
overlayPreferShapes?: ReadonlySet<string>;
|
|
84
|
+
now?: Date;
|
|
84
85
|
}): string[];
|
|
85
86
|
export declare function formatModelAuthLine(excluded: {
|
|
86
87
|
key: string;
|
|
@@ -478,10 +478,55 @@ export function preferEntryLints(cfg, health, mapShapes = new Set()) {
|
|
|
478
478
|
sweep("consult.prefer", cfg.consult.prefer);
|
|
479
479
|
return lints;
|
|
480
480
|
}
|
|
481
|
+
// v2.6.4 T2 (B1a): doctor.json is read without a schema (compat), so a stored notice is re-checked here —
|
|
482
|
+
// anything but null (known clean) or a dated notice reads as unknown, never as clean.
|
|
483
|
+
function storedRetirement(record, model) {
|
|
484
|
+
if (!record || typeof record !== "object")
|
|
485
|
+
return undefined;
|
|
486
|
+
const r = record[model];
|
|
487
|
+
if (r === null)
|
|
488
|
+
return null;
|
|
489
|
+
const n = r;
|
|
490
|
+
if (typeof n?.retiresAt !== "string" || !Number.isFinite(Date.parse(n.retiresAt)))
|
|
491
|
+
return undefined;
|
|
492
|
+
if (n.successor === undefined)
|
|
493
|
+
return { retiresAt: n.retiresAt };
|
|
494
|
+
return typeof n.successor === "string" && MODEL_ID_RE.test(n.successor) ? { retiresAt: n.retiresAt, successor: n.successor } : undefined;
|
|
495
|
+
}
|
|
496
|
+
// Configured AND listed models only — a configured id the CLI stopped listing already carries the
|
|
497
|
+
// tombstone advisory. The CLI's notice is quoted as-is: its successor is the CLI's upgrade target, never
|
|
498
|
+
// a tier or benchmark claim, and no model name or visibility is read as one. Advisory: routing unchanged.
|
|
499
|
+
function retirementLints(id, listedConfigured, record, nowMs) {
|
|
500
|
+
const lints = [];
|
|
501
|
+
const unknown = [];
|
|
502
|
+
for (const model of listedConfigured) {
|
|
503
|
+
const notice = storedRetirement(record, model);
|
|
504
|
+
if (notice === undefined) {
|
|
505
|
+
unknown.push(model);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (!notice)
|
|
509
|
+
continue; // listed with no notice: known clean
|
|
510
|
+
const successor = notice.successor
|
|
511
|
+
? `successor ${notice.successor} (named by the CLI — not a tier claim; classify it per benchmark policy)`
|
|
512
|
+
: "no successor named";
|
|
513
|
+
lints.push(nowMs >= Date.parse(notice.retiresAt)
|
|
514
|
+
? `${id}: ${model} retired ${notice.retiresAt} per the CLI's notice; ${successor} — tombstone it (${model}: null overlay) or verify the id (advisory — routing unchanged)`
|
|
515
|
+
: `${id}: ${model} retires ${notice.retiresAt} per the CLI's notice; ${successor} — tombstone it (${model}: null overlay) before then (advisory — routing unchanged)`);
|
|
516
|
+
}
|
|
517
|
+
if (unknown.length) {
|
|
518
|
+
const why = record && typeof record === "object"
|
|
519
|
+
? "the CLI's notice is absent or unreadable"
|
|
520
|
+
: "doctor record carries no CLI retirement notices; rerun tickmarkr doctor";
|
|
521
|
+
lints.push(`${id}: retirement unknown for ${unknown.join(", ")} — ${why}`);
|
|
522
|
+
}
|
|
523
|
+
return lints;
|
|
524
|
+
}
|
|
481
525
|
// Diffs detected models (doctor.json) against configured tiers, both directions, per installed adapter.
|
|
482
526
|
// No ` ! ` prefix here — the consumer (doctor rows / plan lints) owns that. Pre-v1.5 doctor.json (models:[], no
|
|
483
527
|
// modelsDetectedAt) is the compat baseline: `?.`/`?? []` everywhere, no zod (would reject old files).
|
|
484
528
|
export function modelLints(cfg, health, adapters, opts) {
|
|
529
|
+
const nowMs = (opts?.now ?? new Date()).getTime();
|
|
485
530
|
const cap = opts?.tty ? TTY_LINT_CAP : LINT_CAP;
|
|
486
531
|
const doctorRef = opts?.tty ? doctorJsonRef(opts.stateDir ?? DEFAULT_STATE_DIR) : "";
|
|
487
532
|
const lints = [];
|
|
@@ -517,6 +562,8 @@ export function modelLints(cfg, health, adapters, opts) {
|
|
|
517
562
|
lints.push(`${id}: tiers lists ${model} — CLI no longer reports it; tombstone it (${model}: null overlay) or verify the id`);
|
|
518
563
|
}
|
|
519
564
|
}
|
|
565
|
+
if (adapter.listModelsRetirements)
|
|
566
|
+
lints.push(...retirementLints(id, configured.filter((m) => detected.includes(m)), h?.modelRetirements, nowMs));
|
|
520
567
|
const extra = collapseUnclassified(detected, new Set(configured));
|
|
521
568
|
if (extra.length) {
|
|
522
569
|
const shown = extra.slice(0, cap).map((row) => row.model).join(", ");
|
|
@@ -525,7 +572,7 @@ export function modelLints(cfg, health, adapters, opts) {
|
|
|
525
572
|
}
|
|
526
573
|
const at = h?.modelsDetectedAt;
|
|
527
574
|
if (at) {
|
|
528
|
-
const days = Math.floor((
|
|
575
|
+
const days = Math.floor((nowMs - Date.parse(at)) / DAY_MS); // completed days — never overstate age
|
|
529
576
|
if (days >= MODEL_STALE_DAYS)
|
|
530
577
|
lints.push(`${id}: model knowledge is ${days} days old — rerun tickmarkr doctor`);
|
|
531
578
|
}
|
|
@@ -15,7 +15,8 @@ export type ModelWindowResolution = {
|
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
17
|
* Validate cited model-window claims before production code can consume them.
|
|
18
|
-
* These values are vendor-published claims,
|
|
18
|
+
* These values are vendor-published claims (a vendor page, or a vendor CLI's own model cache), not
|
|
19
|
+
* measurements made by tickmarkr.
|
|
19
20
|
*/
|
|
20
21
|
export declare function loadModelWindowClaims(input: unknown): readonly ModelWindowClaim[];
|
|
21
22
|
/** The closed, production-readable catalog of cited context-window claims. */
|
|
@@ -9,7 +9,8 @@ const ModelWindowClaimSchema = z
|
|
|
9
9
|
.strict();
|
|
10
10
|
/**
|
|
11
11
|
* Validate cited model-window claims before production code can consume them.
|
|
12
|
-
* These values are vendor-published claims,
|
|
12
|
+
* These values are vendor-published claims (a vendor page, or a vendor CLI's own model cache), not
|
|
13
|
+
* measurements made by tickmarkr.
|
|
13
14
|
*/
|
|
14
15
|
export function loadModelWindowClaims(input) {
|
|
15
16
|
const claims = z.array(ModelWindowClaimSchema).parse(input);
|
|
@@ -22,8 +23,15 @@ export function loadModelWindowClaims(input) {
|
|
|
22
23
|
return Object.freeze(claims.map((claim) => Object.freeze(claim)));
|
|
23
24
|
}
|
|
24
25
|
const ANTHROPIC_SOURCE = "https://platform.claude.com/docs/en/about-claude/models/overview";
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
// v2.6.4 T3 (D-717): the Codex rows cite the installed CLI's own models_cache.json snapshot, not the API
|
|
27
|
+
// docs. That file has no public URL, so the source names it in its own scheme, snapshot instant included.
|
|
28
|
+
// Every listed entry there carried context_window=272000 — the budget the codex CLI launches with. The
|
|
29
|
+
// API docs' 1,050,000 (formerly cited here) describes the API surface; it is superseded for these CLI
|
|
30
|
+
// seeds, not called false. These windows are the CLI's self-report, not a tickmarkr measurement. Only the
|
|
31
|
+
// window comes from this snapshot: the codex tiers seeded beside it (config.ts) rest on the provider's
|
|
32
|
+
// descriptions and the retained workhorse tier policy, not on any measured benchmark.
|
|
33
|
+
const CODEX_CLI_CACHE_SOURCE = "codex-cli:models_cache.json#fetched_at=2026-09-29T16:09Z";
|
|
34
|
+
const CODEX_CLI_CACHE_READ_DATE = "2026-09-29";
|
|
27
35
|
const CURSOR_SOURCE = "https://cursor.com/help/ai-features/max-mode";
|
|
28
36
|
const ZAI_SOURCE = "https://z.ai/blog/glm-5.2";
|
|
29
37
|
const XAI_SOURCE = "https://docs.x.ai/developers/models/grok-4.5";
|
|
@@ -37,10 +45,13 @@ const VENDORED_MODEL_WINDOW_CLAIMS = [
|
|
|
37
45
|
{ modelId: "opus", window: 1_000_000, source: ANTHROPIC_SOURCE, readDate: READ_DATE },
|
|
38
46
|
{ modelId: "sonnet", window: 1_000_000, source: ANTHROPIC_SOURCE, readDate: READ_DATE },
|
|
39
47
|
{ modelId: "haiku", window: 200_000, source: ANTHROPIC_SOURCE, readDate: READ_DATE },
|
|
40
|
-
{ modelId: "gpt-
|
|
41
|
-
{ modelId: "gpt-
|
|
42
|
-
{ modelId: "gpt-
|
|
43
|
-
{ modelId: "gpt-5.6-
|
|
48
|
+
{ modelId: "gpt-6-astra", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
49
|
+
{ modelId: "gpt-6-sol", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
50
|
+
{ modelId: "gpt-6-luna", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
51
|
+
{ modelId: "gpt-5.6-sol", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
52
|
+
{ modelId: "gpt-5.5", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
53
|
+
{ modelId: "gpt-5.6-terra", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
54
|
+
{ modelId: "gpt-5.6-luna", window: 272_000, source: CODEX_CLI_CACHE_SOURCE, readDate: CODEX_CLI_CACHE_READ_DATE },
|
|
44
55
|
{ modelId: "composer-2.5", window: 200_000, source: CURSOR_SOURCE, readDate: READ_DATE },
|
|
45
56
|
{ modelId: "composer-2.5-fast", window: 200_000, source: CURSOR_SOURCE, readDate: READ_DATE },
|
|
46
57
|
{ modelId: "claude-fable-5-1", window: 1_000_000, source: ANTHROPIC_SOURCE, readDate: OBS_871_READ_DATE },
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -36,6 +36,10 @@ export interface ModelAuth {
|
|
|
36
36
|
probedAt: string;
|
|
37
37
|
identity?: string;
|
|
38
38
|
}
|
|
39
|
+
export interface ModelRetirement {
|
|
40
|
+
retiresAt: string;
|
|
41
|
+
successor?: string;
|
|
42
|
+
}
|
|
39
43
|
export interface AuthHealth {
|
|
40
44
|
installed: boolean;
|
|
41
45
|
authed: boolean;
|
|
@@ -46,6 +50,7 @@ export interface AuthHealth {
|
|
|
46
50
|
servable?: string[];
|
|
47
51
|
modelAuth?: Record<string, ModelAuth>;
|
|
48
52
|
modelIdentities?: Record<string, string>;
|
|
53
|
+
modelRetirements?: Record<string, ModelRetirement | null>;
|
|
49
54
|
}
|
|
50
55
|
export declare function modelAuthed(health: AuthHealth | undefined, model: string, allowUnverifiedModels?: boolean): boolean;
|
|
51
56
|
export interface Invocation {
|
|
@@ -190,6 +195,7 @@ export interface WorkerAdapter {
|
|
|
190
195
|
parse(output: string, nonce: string): WorkerResult;
|
|
191
196
|
listModels?(): Promise<string[]>;
|
|
192
197
|
listModelsFetchedAt?(): string | undefined;
|
|
198
|
+
listModelsRetirements?(): Record<string, ModelRetirement | null> | undefined;
|
|
193
199
|
collectUsage?(cwd: string, sinceMs: number): TokenUsage | undefined;
|
|
194
200
|
contextUsage?(session: SessionRef): ContextUsage | null;
|
|
195
201
|
readSessionTranscript?(session: SessionRef): {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { graphDefinitionHash, loadGraph, saveGraph, taskDefinitionFingerprint } from "../../graph/graph.js";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { userInfo } from "node:os";
|
|
4
|
+
import { allAdapters } from "../../adapters/registry.js";
|
|
5
|
+
import { channelKey } from "../../adapters/types.js";
|
|
4
6
|
import { loadConfig } from "../../config/config.js";
|
|
5
7
|
import { integrationBranch } from "../../run/merge.js";
|
|
6
8
|
import { separabilityErrors, surfaceErrors, taskBudgetErrors } from "../../compile/collateral.js";
|
|
7
9
|
import { GATE_NAMES } from "../../graph/schema.js";
|
|
8
|
-
import { applyScopeAmendments, bindingToken, engagementComparable, engagementReleased, failedGateBeforePark, parseBindingToken, physicalLine, ATTEMPT_CAP_RELEASE, GATE_SATISFIED_RELEASE, Journal, RECHECK_RELEASE, REVIEW_UPHELD_RELEASE } from "../../run/journal.js";
|
|
10
|
+
import { applyScopeAmendments, bindingToken, captureOwedCheck, engagementComparable, engagementReleased, failedGateBeforePark, parseBindingToken, physicalLine, ATTEMPT_CAP_RELEASE, GATE_SATISFIED_RELEASE, Journal, RECHECK_RELEASE, REVIEW_UPHELD_RELEASE } from "../../run/journal.js";
|
|
9
11
|
export const APPROVAL_DISPOSITIONS = ["dispatch", "waive-gate", "re-dispatch", "fund-fixed-attempt", "fresh-budget"];
|
|
10
12
|
/**
|
|
11
13
|
* What each disposition's release actually buys — the clause every operator-facing sentence about
|
|
@@ -296,6 +298,28 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
296
298
|
if (!gateFailPark || !failedGate) {
|
|
297
299
|
throw new Error(`--waive applies to a gate-fail park; ${taskId}'s newest park is ${String(lastHuman?.data.kind ?? "none")} with failed gate ${failedGate ?? "none"} — refusing`);
|
|
298
300
|
}
|
|
301
|
+
// C1: the waiver's owed check rides the SAME row — one append, captured synchronously so a live
|
|
302
|
+
// daemon's boundary never observes the decision without its obligation (or a second write).
|
|
303
|
+
// The seats a review discharge may resolve to are this repository's declared channels, recorded on
|
|
304
|
+
// the row so every later fold reads the same table whatever process or checkout it runs in.
|
|
305
|
+
let integrationRef = "unknown";
|
|
306
|
+
let declared;
|
|
307
|
+
let owedTask;
|
|
308
|
+
try {
|
|
309
|
+
const cfg = loadConfig(cwd);
|
|
310
|
+
integrationRef = integrationBranch(cfg, runId);
|
|
311
|
+
declared = allAdapters().flatMap((a) => a.channels(cfg)).map((c) => ({ key: channelKey(c), vendor: c.vendor }));
|
|
312
|
+
}
|
|
313
|
+
catch { /* an unreadable config binds no range and declares no seat: unknown */ }
|
|
314
|
+
try {
|
|
315
|
+
owedTask = loadGraph(cwd).tasks.find((t) => t.id === taskId);
|
|
316
|
+
}
|
|
317
|
+
catch { /* recorded as unknown criteria and scope */ }
|
|
318
|
+
const obligation = captureOwedCheck({
|
|
319
|
+
cwd, runId, taskId, gate: failedGate, events, acceptance: owedTask?.acceptance, files: owedTask?.files, declared,
|
|
320
|
+
cause: reason ?? `operator waived failed ${failedGate} gate`, // the row's own park field binds the park
|
|
321
|
+
taskRef: `${integrationRef}--${taskId}`, integrationRef,
|
|
322
|
+
});
|
|
299
323
|
journal.append("task-approved", taskId, {
|
|
300
324
|
by,
|
|
301
325
|
...(reason ? { reason } : {}),
|
|
@@ -304,8 +328,9 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
304
328
|
gate: failedGate,
|
|
305
329
|
park: parkBinding,
|
|
306
330
|
...(reviewRoundCeiling === undefined ? {} : { reviewRoundCeiling }),
|
|
331
|
+
obligation,
|
|
307
332
|
});
|
|
308
|
-
return disposition(cwd, runId, "waive-gate", `waived failed gate ${failedGate} for ${taskId} in ${runId} — by ${by}`, serialization.contended);
|
|
333
|
+
return disposition(cwd, runId, "waive-gate", `waived failed gate ${failedGate} for ${taskId} in ${runId} — by ${by}; owes ${failedGate} check ${obligation.id}${obligation.known ? "" : " (debt unknown)"}`, serialization.contended);
|
|
309
334
|
}
|
|
310
335
|
if (gateFailPark) {
|
|
311
336
|
const choices = [`--waive (disposition waive-gate)`, `--recheck (disposition re-dispatch)`];
|
|
@@ -34,6 +34,8 @@ export type DoctorOpts = {
|
|
|
34
34
|
resolveOrcaBinary?: (cwd: string) => string | undefined;
|
|
35
35
|
/** Test seam for the runner-owned JSON listing used by the acceptance-oracle report row. */
|
|
36
36
|
listTests?: (cwd: string) => Promise<VitestListResult>;
|
|
37
|
+
/** Clock for model-knowledge lints (retirement notices, knowledge age); absent = the wall clock. */
|
|
38
|
+
now?: () => Date;
|
|
37
39
|
};
|
|
38
40
|
type OrcaCapability = {
|
|
39
41
|
verdict: "pass" | "fail" | "warn";
|
|
@@ -590,6 +590,11 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
|
|
|
590
590
|
// no fetch timestamp, so it still stamps now — its staleness lint is best-effort until it surfaces one.
|
|
591
591
|
if (health[a.id].models.length)
|
|
592
592
|
health[a.id].modelsDetectedAt = a.listModelsFetchedAt?.() ?? new Date().toISOString();
|
|
593
|
+
// v2.6.4 T2 (B1a): the CLI's retirement notices ride into doctor.json beside the list they describe;
|
|
594
|
+
// an unreadable source leaves the field absent, which every reader treats as unknown.
|
|
595
|
+
const retirements = a.listModelsRetirements?.();
|
|
596
|
+
if (retirements)
|
|
597
|
+
health[a.id].modelRetirements = retirements;
|
|
593
598
|
}
|
|
594
599
|
catch { /* fail open: leave models as-is, doctor stays healthy */ }
|
|
595
600
|
}
|
|
@@ -784,7 +789,7 @@ export async function doctor(_argv, cwd = process.cwd(), adapters = allAdapters(
|
|
|
784
789
|
const liveBenchStale = liveBenchStalenessFinding(opts.catalogNow?.() ?? new Date());
|
|
785
790
|
if (liveBenchStale)
|
|
786
791
|
lintRows.push(attentionRow(liveBenchStale));
|
|
787
|
-
lintRows.push(...modelLints(cfg, health, adapters, { tty: ttyVisual(), stateDir: stateDirName(cwd), overlayPreferShapes: overlayPreferShapes(cwd) }).map(attentionRow));
|
|
792
|
+
lintRows.push(...modelLints(cfg, health, adapters, { tty: ttyVisual(), stateDir: stateDirName(cwd), overlayPreferShapes: overlayPreferShapes(cwd), now: opts.now?.() }).map(attentionRow));
|
|
788
793
|
const excluded = excludedChannels(cfg, adapters, health);
|
|
789
794
|
if (excluded.length)
|
|
790
795
|
lintRows.push(attentionRow(exclusionLine(excluded)));
|