tickmarkr 2.6.2 → 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 +12 -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.d.ts +6 -3
- package/dist/cli/commands/approve.js +43 -6
- package/dist/cli/commands/doctor.d.ts +8 -2
- package/dist/cli/commands/doctor.js +6 -1
- package/dist/cli/commands/fleet.js +264 -59
- package/dist/cli/commands/init.js +5 -3
- package/dist/cli/commands/plan.js +13 -8
- package/dist/cli/commands/report.d.ts +40 -2
- package/dist/cli/commands/report.js +278 -11
- 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 +49 -4
- package/dist/cli/commands/verify.js +332 -121
- package/dist/compile/native.js +137 -0
- package/dist/config/config.d.ts +40 -9
- package/dist/config/config.js +133 -15
- 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 +23 -1
- package/dist/route/router.js +54 -16
- 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 +349 -79
- package/dist/run/interactive-seed.d.ts +4 -0
- package/dist/run/interactive-seed.js +35 -9
- package/dist/run/journal.d.ts +123 -1
- package/dist/run/journal.js +480 -17
- 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/live-store.d.ts +6 -0
- package/dist/tui/cockpit/live-store.js +36 -11
- 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-auto/SKILL.md +2 -2
- package/skills/tickmarkr-loop/SKILL.md +17 -5
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +4 -1
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
|
|
|
@@ -208,6 +216,8 @@ worker. Other parks have different permitted decisions:
|
|
|
208
216
|
| Human gate / other non-gate park | Plain approve records permission to dispatch. |
|
|
209
217
|
| Attempt cap | Plain approve grants a fresh attempt budget; prior routing exclusions remain. |
|
|
210
218
|
| Infrastructure | Plain approve or `--recheck`; recheck reruns the declared battery and satisfies no gate. |
|
|
219
|
+
| 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. |
|
|
220
|
+
| Ordinary stall (no `reapFailure`) | Plain approve only — it dispatches a worker; `--recheck` refuses. |
|
|
211
221
|
| Failed review gate | `--waive` satisfies only that identified gate; `--uphold` funds one fixed attempt carrying findings; `--recheck` reruns the battery. |
|
|
212
222
|
| Other failed gate | `--waive` or `--recheck`; plain approve refuses. |
|
|
213
223
|
| Tombstone / gate failure without identifying evidence | Diagnostic only; no invented decision. |
|
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): {
|
|
@@ -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";
|
|
@@ -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
|
|
@@ -59,6 +61,7 @@ sourceIndexes) {
|
|
|
59
61
|
index: i, line: sourceIndexes?.[i] === undefined ? physicalLine(events, i) : sourceIndexes[i] + 1, ts: typeof e.ts === "string" ? e.ts : undefined, kind, reason,
|
|
60
62
|
approveCommand: typeof e.data.approveCommand === "string" ? e.data.approveCommand : undefined,
|
|
61
63
|
failedGate: failedGateBeforePark(events, taskId, i), tombstone: isTombstonePark(kind, reason),
|
|
64
|
+
...(kind === "stall" && typeof e.data.reapFailure === "string" ? { reapFailure: e.data.reapFailure } : {}),
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
67
|
return undefined;
|
|
@@ -83,8 +86,9 @@ export function newestFailure(events, taskId, sourceIndexes) {
|
|
|
83
86
|
return undefined;
|
|
84
87
|
}
|
|
85
88
|
/**
|
|
86
|
-
* FINAL §3.3's decision menu as data: human-gate/attempt-cap/other non-gate parks → approve; infra
|
|
87
|
-
*
|
|
89
|
+
* FINAL §3.3's decision menu as data: human-gate/attempt-cap/other non-gate parks → approve; infra, and
|
|
90
|
+
* a stall park that recorded a reapFailure (OBS-1202), → approve or recheck; review gate-fail →
|
|
91
|
+
* waive/uphold/recheck; other gate-fail → waive/recheck; a
|
|
88
92
|
* gate-fail park with no failed-gate evidence, or a tombstone → nothing (a diagnostic, never a
|
|
89
93
|
* fabricated verb). The refusals in `approve` below enforce the same table; this is the one place a
|
|
90
94
|
* surface may read it from, so what a menu offers and what the command accepts cannot drift.
|
|
@@ -99,6 +103,10 @@ export function permittedDecisionVerbs(park) {
|
|
|
99
103
|
}
|
|
100
104
|
if (park.kind === "infra")
|
|
101
105
|
return ["approve", "recheck"];
|
|
106
|
+
// OBS-1202: an unreadable reap census parks finished work; recheck re-verifies that census and gates the
|
|
107
|
+
// harvested commits with no worker. An ordinary stall recorded no census failure: approve alone.
|
|
108
|
+
if (park.kind === "stall" && park.reapFailure !== undefined)
|
|
109
|
+
return ["approve", "recheck"];
|
|
102
110
|
// OBS-1084: an authoring park whose newest gate row is a red tool gate is a runner report (a load
|
|
103
111
|
// flake in an unowned suite), so the battery re-runs on the preserved ref with no worker.
|
|
104
112
|
if (park.kind === "authoring" && isToolGate(park.failedGate))
|
|
@@ -196,6 +204,7 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
196
204
|
const gateFailPark = park?.kind === "gate-fail";
|
|
197
205
|
const infraPark = park?.kind === "infra" || park?.kind === "diff-cap";
|
|
198
206
|
const authoringRunnerPark = park?.kind === "authoring" && isToolGate(park.failedGate);
|
|
207
|
+
const reapFailurePark = park?.kind === "stall" && park.reapFailure !== undefined;
|
|
199
208
|
const failedGate = gateFailPark || authoringRunnerPark ? park?.failedGate : undefined;
|
|
200
209
|
const parkBinding = requireBound(runId, taskId, bound, park?.ts === undefined ? undefined : { line: park.line, ts: park.ts }, "park");
|
|
201
210
|
if (namedGate !== undefined && namedGate !== park?.failedGate) {
|
|
@@ -263,7 +272,10 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
263
272
|
return disposition(cwd, runId, "fund-fixed-attempt", `upheld the reviewer for ${taskId} in ${runId} — by ${by}`, serialization.contended);
|
|
264
273
|
}
|
|
265
274
|
if (recheck) {
|
|
266
|
-
if (
|
|
275
|
+
if (park?.kind === "stall" && !reapFailurePark) {
|
|
276
|
+
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`);
|
|
277
|
+
}
|
|
278
|
+
if ((!gateFailPark || !failedGate) && !infraPark && !authoringRunnerPark && !reapFailurePark) {
|
|
267
279
|
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
280
|
}
|
|
269
281
|
journal.append("task-approved", taskId, {
|
|
@@ -276,14 +288,38 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
276
288
|
// carry gate so their durable release row identifies the red tool gate that made recheck admissible.
|
|
277
289
|
...(failedGate ? { failedGate } : {}),
|
|
278
290
|
...(authoringRunnerPark && failedGate ? { gate: failedGate } : {}),
|
|
291
|
+
// OBS-1202: the daemon re-verifies the parked attempt's owned census before any gate runs.
|
|
292
|
+
...(reapFailurePark ? { reapFailure: park.reapFailure } : {}),
|
|
279
293
|
...(reviewRoundCeiling === undefined ? {} : { reviewRoundCeiling }),
|
|
280
294
|
});
|
|
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);
|
|
295
|
+
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
296
|
}
|
|
283
297
|
if (waive) {
|
|
284
298
|
if (!gateFailPark || !failedGate) {
|
|
285
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`);
|
|
286
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
|
+
});
|
|
287
323
|
journal.append("task-approved", taskId, {
|
|
288
324
|
by,
|
|
289
325
|
...(reason ? { reason } : {}),
|
|
@@ -292,8 +328,9 @@ export async function approve(argv, cwd = process.cwd()) {
|
|
|
292
328
|
gate: failedGate,
|
|
293
329
|
park: parkBinding,
|
|
294
330
|
...(reviewRoundCeiling === undefined ? {} : { reviewRoundCeiling }),
|
|
331
|
+
obligation,
|
|
295
332
|
});
|
|
296
|
-
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);
|
|
297
334
|
}
|
|
298
335
|
if (gateFailPark) {
|
|
299
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";
|
|
@@ -173,6 +175,8 @@ export declare function doctorProbePreflight(cwd?: string, cfg?: {
|
|
|
173
175
|
tipTest?: string | undefined;
|
|
174
176
|
lint?: string | undefined;
|
|
175
177
|
diffCap?: number | undefined;
|
|
178
|
+
evidenceQuotaBytes?: number | undefined;
|
|
179
|
+
repairSelection?: boolean | undefined;
|
|
176
180
|
byShape?: Partial<Record<"plan" | "spec" | "implement" | "tests" | "docs" | "migration" | "ui" | "refactor" | "chore", {
|
|
177
181
|
[x: string]: unknown;
|
|
178
182
|
acceptance?: boolean | undefined;
|
|
@@ -206,8 +210,8 @@ export declare function doctorProbePreflight(cwd?: string, cfg?: {
|
|
|
206
210
|
};
|
|
207
211
|
executionPolicy?: {
|
|
208
212
|
boundedInfrastructure: boolean;
|
|
209
|
-
repairSelection: boolean;
|
|
210
213
|
taskExecutionLimitMs: number;
|
|
214
|
+
repairSelection?: boolean | undefined;
|
|
211
215
|
} | undefined;
|
|
212
216
|
setup?: string | undefined;
|
|
213
217
|
cost?: {
|
|
@@ -298,6 +302,8 @@ export declare function cachedDoctorDiagnostics(cwd?: string, adapters?: WorkerA
|
|
|
298
302
|
tipTest?: string | undefined;
|
|
299
303
|
lint?: string | undefined;
|
|
300
304
|
diffCap?: number | undefined;
|
|
305
|
+
evidenceQuotaBytes?: number | undefined;
|
|
306
|
+
repairSelection?: boolean | undefined;
|
|
301
307
|
byShape?: Partial<Record<"plan" | "spec" | "implement" | "tests" | "docs" | "migration" | "ui" | "refactor" | "chore", {
|
|
302
308
|
[x: string]: unknown;
|
|
303
309
|
acceptance?: boolean | undefined;
|
|
@@ -331,8 +337,8 @@ export declare function cachedDoctorDiagnostics(cwd?: string, adapters?: WorkerA
|
|
|
331
337
|
};
|
|
332
338
|
executionPolicy?: {
|
|
333
339
|
boundedInfrastructure: boolean;
|
|
334
|
-
repairSelection: boolean;
|
|
335
340
|
taskExecutionLimitMs: number;
|
|
341
|
+
repairSelection?: boolean | undefined;
|
|
336
342
|
} | undefined;
|
|
337
343
|
setup?: string | undefined;
|
|
338
344
|
cost?: {
|
|
@@ -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)));
|