tickmarkr 2.6.5 → 2.6.7
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 +7 -3
- package/dist/adapters/claude-code.js +8 -2
- package/dist/adapters/codex-commit-check.d.ts +16 -4
- package/dist/adapters/codex-commit-check.js +150 -26
- package/dist/adapters/codex.d.ts +1 -0
- package/dist/adapters/codex.js +20 -8
- package/dist/adapters/types.d.ts +4 -1
- package/dist/cli/commands/beat.d.ts +6 -1
- package/dist/cli/commands/beat.js +123 -76
- package/dist/cli/commands/doctor.js +3 -1
- package/dist/cli/commands/fleet.js +368 -85
- package/dist/cli/commands/init.js +23 -9
- package/dist/cli/commands/plan.js +12 -1
- package/dist/cli/commands/verify.js +37 -9
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +10 -5
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/config/fleet-overlay.d.ts +14 -0
- package/dist/config/fleet-overlay.js +154 -25
- package/dist/drivers/orca.d.ts +17 -3
- package/dist/drivers/orca.js +64 -20
- package/dist/gates/baseline.d.ts +9 -0
- package/dist/gates/baseline.js +14 -3
- package/dist/gates/cache.js +1 -1
- package/dist/gates/review.js +15 -0
- package/dist/gates/run-gates.d.ts +20 -7
- package/dist/gates/run-gates.js +345 -210
- package/dist/gates/test-manifest.d.ts +11 -0
- package/dist/gates/test-manifest.js +151 -111
- package/dist/gates/timeout-shaped.d.ts +3 -0
- package/dist/gates/timeout-shaped.js +10 -0
- package/dist/run/beat-lifecycle.d.ts +154 -0
- package/dist/run/beat-lifecycle.js +825 -0
- package/dist/run/daemon.js +202 -126
- package/dist/run/git-trust.d.ts +27 -0
- package/dist/run/git-trust.js +178 -0
- package/dist/run/git.js +34 -14
- package/dist/run/journal.js +15 -3
- package/dist/run/lease.d.ts +23 -0
- package/dist/run/lease.js +232 -38
- package/dist/run/merge.js +2 -2
- package/dist/run/stall.js +20 -1
- package/dist/run/supervision.d.ts +8 -0
- package/dist/run/supervision.js +24 -4
- package/dist/run/verification-job.d.ts +51 -0
- package/dist/run/verification-job.js +137 -0
- package/dist/tui/ink/fleet-app.d.ts +35 -5
- package/dist/tui/ink/fleet-app.js +244 -124
- package/package.json +1 -1
- package/skills/tickmarkr-auto/SKILL.md +19 -10
- package/skills/tickmarkr-loop/SKILL.md +19 -10
- package/skills/tickmarkr-overseer/SKILL.md +25 -16
- package/skills/tickmarkr-overseer/scripts/classify-vitest-log.sh +58 -16
- package/skills/tickmarkr-overseer/scripts/grade-ci.sh +19 -6
- package/skills/tickmarkr-overseer/scripts/watch-parks.sh +121 -64
|
@@ -130,13 +130,24 @@ you were handed the whole picture — list the repository root, open every guida
|
|
|
130
130
|
predate a project rename, so **search by CONCEPT, not by the current product name.** A memory nobody opens
|
|
131
131
|
is worse than none: every seat assumes the lesson is recorded somewhere and no seat looks.
|
|
132
132
|
- **The gates are the product.** Seven, defined in \`src/graph/schema.ts\`:
|
|
133
|
-
\`build test lint evidence scope acceptance review\`. **That is DECLARATION order, not execution order
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
\`build test lint evidence scope acceptance review\`. **That is DECLARATION order, not execution order.**
|
|
134
|
+
The cheap checks run first and stop at their first red: \`build\`, \`lint\`, then \`evidence\` and \`scope\`.
|
|
135
|
+
A fresh or semantic-repair candidate then runs \`acceptance\` ‖ \`review\` CONCURRENTLY (\`run-gates.ts\`,
|
|
136
|
+
*"judge ‖ review"*) before any test payload: a decisive semantic red ends the round with zero diagnostic
|
|
137
|
+
or full test-gate runs (a required acceptance oracle still executes); otherwise ONE full verification job
|
|
138
|
+
follows, with no selected screen. Only an attributed behavioral test-red repair may buy one diagnostic
|
|
139
|
+
first — every required failing file plus conservatively selected tests reaching the diff — admitted only
|
|
140
|
+
on comparable harness timing with ratio <= 0.15 AND estimated cost <= 60000 ms; its behavioral red stops
|
|
141
|
+
the round before semantics, otherwise semantics precede the full job. Disabled, unsupported, over-cap or
|
|
142
|
+
unknown-cost selection skips the diagnostic instead of running a full suite before semantics, and a
|
|
143
|
+
killed diagnostic is never green. A seatless or no-verdict review lets acceptance finish and publish and
|
|
144
|
+
the full job run in-round, and stays unsatisfied until review-only recovery; a seatless acceptance
|
|
145
|
+
cancels its sibling, records the unrun test (\`testOwed\`) and every enabled gate still owed (an unrecorded
|
|
146
|
+
review included), cannot merge and parks infra; an actual cancellation or throw awaits cleanup and leaves
|
|
147
|
+
no mergeable partial set. Test-only verification and an explicit full recheck keep their full scope with
|
|
148
|
+
no invented semantics and no diagnostic. Nothing merges until the FULL suite has passed on the exact
|
|
149
|
+
merge-candidate commit; an exact full green answers only after identity, environment, cleanliness and
|
|
150
|
+
current complete-manifest checks. The first five are MANDATORY; only \`acceptance\` and
|
|
140
151
|
\`review\` may be omitted per task. Implementations are in \`src/gates/\` — \`baseline.ts\` (build/test/lint,
|
|
141
152
|
diffed against a recorded baseline so pre-existing failures are forgiven), \`evidence.ts\`, \`scope.ts\`,
|
|
142
153
|
\`acceptance.ts\` (its judge reads the DIFF and every criterion must cite a changed hunk), \`review.ts\`
|
|
@@ -639,7 +650,8 @@ export async function init(argv, cwd = process.cwd(), io = {}) {
|
|
|
639
650
|
const interactive = input.isTTY === true && output.isTTY === true && !(values.yes ?? false);
|
|
640
651
|
// Act 1 — preferences. Only when the repo config does not exist yet: init never rewrites an
|
|
641
652
|
// operator's config; re-tuning is `tickmarkr fleet` (act 3 below still runs). Act 1 writes only
|
|
642
|
-
// project execution preferences; model choices are act 3's, saved
|
|
653
|
+
// project execution preferences; model choices are act 3's, saved by Fleet's one destination rule:
|
|
654
|
+
// the USER overlay (B2), except a membership edit the repo overlay holds (T10).
|
|
643
655
|
if (!repoConfigExists) {
|
|
644
656
|
if (interactive) {
|
|
645
657
|
emitBanner();
|
|
@@ -677,7 +689,9 @@ export async function init(argv, cwd = process.cwd(), io = {}) {
|
|
|
677
689
|
// Act 3 — fleet. The compact discovery surface prints BETWEEN the acts so the operator reads
|
|
678
690
|
// what the fleet browser (and its presets overlay, raised on the first Shapes entry) ranks
|
|
679
691
|
// with; the final summary then carries a pointer, not a repeat. Its confirmation is the shared
|
|
680
|
-
// Fleet actuator's:
|
|
692
|
+
// Fleet actuator's: a preset (routing.mode) lands in the user overlay (gdir), and so does every
|
|
693
|
+
// choice except a routing.allow/deny membership edit this repo overlay holds — T10 writes that one
|
|
694
|
+
// where it is effective, and the review names the destination before y.
|
|
681
695
|
if (interactive) {
|
|
682
696
|
output.write(`${doc}\n`);
|
|
683
697
|
doc = "discovery shown above — full matrix any time with `tickmarkr doctor`";
|
|
@@ -19,7 +19,8 @@ import { auditNamedTestOracles, listVitestTests } from "../../gates/acceptance.j
|
|
|
19
19
|
import { carriedAuthorVendors, gateReviewerFloor, modelId, modelProvider, pickReviewer } from "../../gates/review.js";
|
|
20
20
|
import { Journal, loadRoutingProfile, readProfileCursor, recordedGraphDefinitionHash, RUNS_WINDOW } from "../../run/journal.js";
|
|
21
21
|
import { harnessLine, resolveHarness } from "../harness.js";
|
|
22
|
-
import { codexCommitHeadline } from "../../adapters/codex-commit-check.js";
|
|
22
|
+
import { codexCommitHeadline, codexEscapeRoleLine } from "../../adapters/codex-commit-check.js";
|
|
23
|
+
import { codex } from "../../adapters/codex.js";
|
|
23
24
|
import { channelKey, shq } from "../../adapters/types.js";
|
|
24
25
|
import { shGit } from "../../run/git.js";
|
|
25
26
|
import { runLockRunId, runStatusLine } from "../../run/lock.js";
|
|
@@ -279,6 +280,16 @@ export async function plan(argv, cwd = process.cwd(), adapters = allAdapters(),
|
|
|
279
280
|
const entrySeats = routingEntrySeatLines(cfg);
|
|
280
281
|
if (entrySeats.length)
|
|
281
282
|
lines.push(...entrySeats, "");
|
|
283
|
+
// v2.6.6 T9 (K): Codex judge/review/consult seats launch with the worker grant (llm.ts, consult.ts), so a
|
|
284
|
+
// doctor-probed escape blocks them too; worker rows carry their own headline below.
|
|
285
|
+
const codexRoles = [
|
|
286
|
+
...(cfg.judge.adapter === codex.id ? ["judge"] : []),
|
|
287
|
+
...(pools.review.some((c) => c.adapter === codex.id) ? ["review"] : []),
|
|
288
|
+
...(cfg.consult.adapter === codex.id || pools.consult.some((c) => c.adapter === codex.id) ? ["consult"] : []),
|
|
289
|
+
];
|
|
290
|
+
const codexEscape = codexEscapeRoleLine(codexRoles, adapters, health);
|
|
291
|
+
if (codexEscape)
|
|
292
|
+
lines.push(codexEscape, "");
|
|
282
293
|
// OBS-1185: the floor route() resolved for THIS task — a task hint over the configured/mode floor.
|
|
283
294
|
const derivation = (t) => {
|
|
284
295
|
const floor = resolvedFloor(t, cfg);
|
|
@@ -102,6 +102,37 @@ export function excludeAuthorProvider(channels, author) {
|
|
|
102
102
|
const provider = modelProvider(author.model, author.vendor);
|
|
103
103
|
return [author, ...channels.filter((candidate) => candidate !== author && modelProvider(candidate.model, candidate.vendor) !== provider)];
|
|
104
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* G (D-874): `--author` names who wrote the diff so review can EXCLUDE it — it never asks for a seat. So
|
|
107
|
+
* it resolves against installed identities (an installed adapter's doctor-listed or configured model),
|
|
108
|
+
* not against the review pool, whose role allow/deny, model-auth and tier gates say nothing about
|
|
109
|
+
* authorship. A pool member resolves to itself, unchanged. Anything else is an author-only identity: its
|
|
110
|
+
* vendor is the configured declaration (the model's override, else the adapter entry's; the adapter's own
|
|
111
|
+
* only when config has no entry), never inferred, and an unseeded model carries no tier of its own — it
|
|
112
|
+
* holds the human sentinel's frontier floor, so the reviewer floor is never lowered. Same-vendor, so it
|
|
113
|
+
* can never be seated. An identity nothing declares refuses by name.
|
|
114
|
+
*/
|
|
115
|
+
function authorIdentity(claim, cfg, adapters, health, pool) {
|
|
116
|
+
const [adapter = "", ...rest] = claim.split(":");
|
|
117
|
+
const model = rest.join(":");
|
|
118
|
+
const member = pool.find((c) => c.adapter === adapter && c.model === model);
|
|
119
|
+
if (member)
|
|
120
|
+
return member;
|
|
121
|
+
const a = adapters.find((x) => x.id === adapter);
|
|
122
|
+
const h = health[adapter];
|
|
123
|
+
const seeded = a?.channels(cfg).find((c) => c.model === model);
|
|
124
|
+
const entry = cfg.tiers[adapter];
|
|
125
|
+
const vendor = seeded?.vendor ?? (entry ? entry.modelOverrides?.[model]?.vendor ?? entry.vendor : a?.vendor);
|
|
126
|
+
const why = !a ? `no registered adapter "${adapter}"`
|
|
127
|
+
: !h?.installed ? `doctor does not record ${adapter} as installed`
|
|
128
|
+
: !seeded && !h.models?.includes(model) ? `${adapter} neither lists "${model}" in doctor nor configures it`
|
|
129
|
+
: !vendor ? `no vendor is declared for ${adapter}:${model}`
|
|
130
|
+
: undefined;
|
|
131
|
+
if (why) {
|
|
132
|
+
throw new Error(`--author ${claim} does not name a discoverable author identity (${why}) — name an installed adapter's doctor-listed or configured model as <adapter>:<model>, or human`);
|
|
133
|
+
}
|
|
134
|
+
return seeded ?? { adapter, vendor: vendor, model, channel: entry?.modelOverrides?.[model]?.channel ?? entry?.channel ?? "sub", tier: "frontier" };
|
|
135
|
+
}
|
|
105
136
|
function recordedMerges(repoRoot) {
|
|
106
137
|
const runs = join(repoRoot, ".tickmarkr", "runs");
|
|
107
138
|
if (!existsSync(runs))
|
|
@@ -288,6 +319,9 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
|
|
|
288
319
|
const pools = rolePools(cfg, adapters, health);
|
|
289
320
|
judgeChannels = pools.judge;
|
|
290
321
|
channels = pools.review;
|
|
322
|
+
// Resolved before either branch so an undiscoverable claim refuses here, ahead of any capture —
|
|
323
|
+
// even where recorded authors bind and the claim is otherwise ignored.
|
|
324
|
+
const claimed = values.author && values.author !== "human" ? authorIdentity(values.author, cfg, adapters, health, channels) : undefined;
|
|
291
325
|
if (owed.length) {
|
|
292
326
|
// The run's lifetime patch authors bind the reviewer's exclusions; --author never overrides
|
|
293
327
|
// them. An author the pool cannot resolve leaves review unseatable, which fails closed.
|
|
@@ -298,15 +332,9 @@ export async function verify(argv, cwd = process.cwd(), options = {}) {
|
|
|
298
332
|
if (first)
|
|
299
333
|
author = { adapter: first.adapter, model: first.model, channel: first.channel, tier: first.tier };
|
|
300
334
|
}
|
|
301
|
-
else if (
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const c = channels.find((ch) => ch.adapter === adapter && ch.model === model);
|
|
305
|
-
if (!c) {
|
|
306
|
-
throw new Error(`--author ${values.author} does not name a discoverable review channel — one of: ${channels.map(channelKey).join(", ") || "(none)"}`);
|
|
307
|
-
}
|
|
308
|
-
author = { adapter: c.adapter, model: c.model, channel: c.channel, tier: c.tier };
|
|
309
|
-
channels = excludeAuthorProvider(channels, c);
|
|
335
|
+
else if (claimed) {
|
|
336
|
+
author = { adapter: claimed.adapter, model: claimed.model, channel: claimed.channel, tier: claimed.tier };
|
|
337
|
+
channels = excludeAuthorProvider(channels, claimed);
|
|
310
338
|
}
|
|
311
339
|
else {
|
|
312
340
|
channels = [...channels, HUMAN_CHANNEL];
|
package/dist/cli/help.d.ts
CHANGED
package/dist/cli/help.js
CHANGED
|
@@ -148,17 +148,22 @@ export const COMMAND_HELP = {
|
|
|
148
148
|
],
|
|
149
149
|
},
|
|
150
150
|
beat: {
|
|
151
|
-
usage: "beat <orchestrator|orchestrator-context|overseer|overseer-context|watch> --seat <identity> [options]",
|
|
152
|
-
description: "
|
|
151
|
+
usage: "beat <start|stop|status> <orchestrator|orchestrator-context|overseer|overseer-context|watch> --seat <identity> | beat <tier> --seat <identity> [options]",
|
|
152
|
+
description: "start launches one detached --loop beat per tier and exits 0 only after reading back that child's own advancing beat; stop retires only that recorded generation and reads back DISARMED; status is read-only and exits nonzero for STALE, MISMATCH or UNREADABLE. The legacy <tier> form writes one supervision beat using the recorded arm: stood-down arms stay DISARMED until explicitly re-armed; stopped beats become STALE.",
|
|
153
153
|
options: {
|
|
154
|
-
"--seat <identity>": "Required: identify the supervising pane or agent.",
|
|
154
|
+
"--seat <identity>": "Required for start, stop and every write: identify the supervising pane or agent; optional for status.",
|
|
155
155
|
"--new-arm": "Create a new durable arm, allowing beats to resume after stand-down; the hand-off step for a seat taking over a stood-down tier (combine with --arm-id and --pct on a context tier).",
|
|
156
|
-
"--loop": "Create a new durable arm and beat in this process every 10 seconds; exit within one interval after stand-down.",
|
|
156
|
+
"--loop": "Create a new durable arm and beat in this process every 10 seconds; exit nonzero with its reason within one interval after stand-down, supersession or a refused write.",
|
|
157
157
|
"--arm-id <identity>": "Name a newly created arm; with --pct, identify the context observation arm. Never refuses a tick.",
|
|
158
158
|
"--pct <0..100>": "Report current context consumption percentage.",
|
|
159
159
|
"--threshold-pct <0..100>": "Set the context warning threshold (default 75).",
|
|
160
160
|
"--stand-down": "Record an explicit handoff and stand down this tier. A later one-shot tick on the old arm exits non-zero; context observations still raise and discharge the clear duty.",
|
|
161
|
-
|
|
161
|
+
"--status": "Legacy spelling of beat status <tier>: the same read-only result, writing nothing.",
|
|
162
|
+
}, examples: [
|
|
163
|
+
"beat start overseer --seat supervisor", "beat status overseer --seat supervisor", "beat stop overseer --seat supervisor",
|
|
164
|
+
"beat overseer --seat supervisor", "beat overseer --seat supervisor --loop", "beat overseer --seat supervisor --stand-down",
|
|
165
|
+
"beat overseer --seat supervisor --new-arm", "beat overseer --status",
|
|
166
|
+
],
|
|
162
167
|
},
|
|
163
168
|
version: {
|
|
164
169
|
usage: "version [--dist]", description: "Print the installed package version. Top-level aliases: --version and -v.",
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ export declare const COMMANDS: {
|
|
|
45
45
|
eval: typeof evalCommand;
|
|
46
46
|
};
|
|
47
47
|
export type RegisteredCommand = keyof typeof COMMANDS;
|
|
48
|
-
export declare const USAGE = "tickmarkr \u2014 spec-driven orchestration harness for AI coding agents\nusage: tickmarkr <command>\n init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs\n doctor re-probe adapters, herdr, auth; print capability matrix (--fix writes the test-runner ignore when a safe edit exists)\n fleet interactive fleet editor (fleet --print for CI drift checks)\n compile <src> spec \u2192 .tickmarkr/graph.json (fails without acceptance criteria)\n scope <intent> preview locally with --preview; draft after confirmation or --yes (--force to overwrite)\n plan dry-run routing table + cost estimate + floor lints\n eval discover and validate fixtures, seed isolated temp repos, then clean them up\n run execute the graph (--concurrency N --driver auto|herdr|subprocess|orca --route-strict; orca runs only when named)\n status live run state (--watch --events: JSON documents on stdout, keepalives on stderr; 2>&1 corrupts the stream)\n stats all-run channel delivery, red, rescue, author and reviewer statistics\n verify run the gate battery standalone against merge-base(--base, HEAD)..HEAD \u2014 verdict/JSON on stdout, progress on stderr; 2>&1 corrupts the verdict stream (--base main --criteria <file> | --task <id> [--files <glob>] [--author adapter:model] [--no-review] [--json])\n resume <id> continue a run from its journal\n report <id> cost/quality report (--md writes Markdown to stdout; redirect to save a record)\n profile show learned routing profile (profile reset = forget history via cursor, keeps telemetry)\n ui open Home, Run or Evidence (--view home|run|evidence; --setup <id> opens Run Parks)\n unlock remove a stale/garbage run lock (refuses if the holder is alive)\n beat <tier>
|
|
48
|
+
export declare const USAGE = "tickmarkr \u2014 spec-driven orchestration harness for AI coding agents\nusage: tickmarkr <command>\n init guided setup + doctor; init --agent [--force] [--docs] adds agent skills/docs\n doctor re-probe adapters, herdr, auth; print capability matrix (--fix writes the test-runner ignore when a safe edit exists)\n fleet interactive fleet editor (fleet --print for CI drift checks)\n compile <src> spec \u2192 .tickmarkr/graph.json (fails without acceptance criteria)\n scope <intent> preview locally with --preview; draft after confirmation or --yes (--force to overwrite)\n plan dry-run routing table + cost estimate + floor lints\n eval discover and validate fixtures, seed isolated temp repos, then clean them up\n run execute the graph (--concurrency N --driver auto|herdr|subprocess|orca --route-strict; orca runs only when named)\n status live run state (--watch --events: JSON documents on stdout, keepalives on stderr; 2>&1 corrupts the stream)\n stats all-run channel delivery, red, rescue, author and reviewer statistics\n verify run the gate battery standalone against merge-base(--base, HEAD)..HEAD \u2014 verdict/JSON on stdout, progress on stderr; 2>&1 corrupts the verdict stream (--base main --criteria <file> | --task <id> [--files <glob>] [--author adapter:model] [--no-review] [--json])\n resume <id> continue a run from its journal\n report <id> cost/quality report (--md writes Markdown to stdout; redirect to save a record)\n profile show learned routing profile (profile reset = forget history via cursor, keeps telemetry)\n ui open Home, Run or Evidence (--view home|run|evidence; --setup <id> opens Run Parks)\n unlock remove a stale/garbage run lock (refuses if the holder is alive)\n beat <tier> supervision beats for orchestrator|orchestrator-context|overseer|overseer-context|watch, --seat <identity> required: beat start|stop|status <tier> owns one detached loop per tier (status is read-only); the legacy beat <tier> records one beat (--stand-down to hand off), and status reads the tier STALE once the beats stop\n approve <id> <task> release a park (--uphold sides with the reviewer and funds a fixed attempt; --by <name> --reason <text>); takes effect on resume\n version print the installed version (--dist adds build location and fingerprint)\n\nUse tickmarkr <command> --help (or -h) for all options and examples.\nUse tickmarkr profile <operation> --help for nested profile help.";
|
|
49
49
|
export declare function dispatch(cmd: string | undefined, argv: string[], commands?: CommandMap): Promise<{
|
|
50
50
|
out: string;
|
|
51
51
|
code: number;
|
package/dist/cli/index.js
CHANGED
|
@@ -46,7 +46,7 @@ usage: tickmarkr <command>
|
|
|
46
46
|
profile show learned routing profile (profile reset = forget history via cursor, keeps telemetry)
|
|
47
47
|
ui open Home, Run or Evidence (--view home|run|evidence; --setup <id> opens Run Parks)
|
|
48
48
|
unlock remove a stale/garbage run lock (refuses if the holder is alive)
|
|
49
|
-
beat <tier>
|
|
49
|
+
beat <tier> supervision beats for orchestrator|orchestrator-context|overseer|overseer-context|watch, --seat <identity> required: beat start|stop|status <tier> owns one detached loop per tier (status is read-only); the legacy beat <tier> records one beat (--stand-down to hand off), and status reads the tier STALE once the beats stop
|
|
50
50
|
approve <id> <task> release a park (--uphold sides with the reviewer and funds a fixed attempt; --by <name> --reason <text>); takes effect on resume
|
|
51
51
|
version print the installed version (--dist adds build location and fingerprint)
|
|
52
52
|
|
|
@@ -21,6 +21,14 @@ type FleetOverlayWriteFields = {
|
|
|
21
21
|
};
|
|
22
22
|
universe?: FleetUniverseRow[];
|
|
23
23
|
lowerMap?: Record<string, MapEntry>;
|
|
24
|
+
preservedAllow?: {
|
|
25
|
+
adapters?: string[];
|
|
26
|
+
models?: string[];
|
|
27
|
+
};
|
|
28
|
+
lowerAllow?: {
|
|
29
|
+
adapters?: string[];
|
|
30
|
+
models?: string[];
|
|
31
|
+
};
|
|
24
32
|
};
|
|
25
33
|
type EffortFree = FleetEditable & {
|
|
26
34
|
efforts?: undefined;
|
|
@@ -40,6 +48,12 @@ export declare function fleetFirstTouchProvenance(note: string, firstTouch: Flee
|
|
|
40
48
|
/** Apply only fields fleet authored to the parsed YAML document. Untouched nodes retain their
|
|
41
49
|
* keys, ordering, scalar style, and comments; fresh provenance is written directly on its tier node. */
|
|
42
50
|
export declare function renderFleetOverlayWrite(priorBytes: string, write: FleetOverlayWrite): string;
|
|
51
|
+
/** T10: the 1-based line of the key that holds `path` in these overlay bytes — the path's own key, the
|
|
52
|
+
* key whose `null` masks it from above, or a `<<` merge key on it (D-987: the write there refuses) — else
|
|
53
|
+
* undefined (the overlay neither declares nor masks it). */
|
|
54
|
+
export declare function overlayHoldingLine(bytes: string, path: readonly string[]): number | undefined;
|
|
55
|
+
/** T10: does `path` hold a mapping in these overlay bytes — neither absent nor a `null` mask? */
|
|
56
|
+
export declare function overlayHoldsMap(bytes: string, path: readonly string[]): boolean;
|
|
43
57
|
/** Build the repo overlay fragment fleet would write for edits since session start. */
|
|
44
58
|
export declare function fleetRepoOverlayFromDelta(initial: FleetEditable, edited: FleetEditable, existingRepo?: Record<string, unknown>, firstTouches?: Readonly<Record<string, FleetFirstTouch>>, universe?: FleetUniverseRow[]): Record<string, unknown>;
|
|
45
59
|
export declare function repoOverlayYaml(overlay: Record<string, unknown>): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Fleet-overlay mutation, serialization, and diff rendering for the `tickmarkr fleet` write path.
|
|
2
|
-
import { isAlias, isMap, isNode, isScalar, isSeq, parseDocument, stringify, visit } from "yaml";
|
|
3
|
-
import { DENY_SCOPES, universeCovers, universeEntryMatches } from "./config.js";
|
|
2
|
+
import { isAlias, isCollection, isMap, isNode, isScalar, isSeq, parseDocument, stringify, visit } from "yaml";
|
|
3
|
+
import { ConfigError, DENY_SCOPES, universeCovers, universeEntryMatches } from "./config.js";
|
|
4
4
|
// OBS-1099 add.1: the writer ranges over the schema-derived scopes. The flat (all-seats) scopes
|
|
5
5
|
// take part in the membership/allow-form write; every nested scope (routing.deny.workers.*) is a
|
|
6
6
|
// literal deny list written on its own. ponytail: "flat" = directly under routing.deny. Read
|
|
@@ -64,9 +64,12 @@ function residualDeny(universe, entries) {
|
|
|
64
64
|
// list keeps its node. A staged entry the allow form cannot express as a membership key (outside the
|
|
65
65
|
// probe universe, or a bare-model/identity spelling) is written verbatim too; canonical keys the
|
|
66
66
|
// session added ride the allow form alone.
|
|
67
|
+
// T10 (D-973): read as the loader reads these bytes (readYaml parses with yaml's defaults, as toJS does
|
|
68
|
+
// here) — aliases at the leaf, its items and every ancestor resolve, and `<<` merges only in a document
|
|
69
|
+
// whose YAML version merges — so an aliased list's entries are authored entries too.
|
|
67
70
|
function authoredEntries(doc, path) {
|
|
68
|
-
const
|
|
69
|
-
return
|
|
71
|
+
const value = path.reduce((node, key) => (node !== null && typeof node === "object" ? node[key] : undefined), doc.toJS());
|
|
72
|
+
return Array.isArray(value) ? value.flatMap((item) => (item === null || typeof item !== "object" ? [String(item)] : [])) : [];
|
|
70
73
|
}
|
|
71
74
|
function flatDenyAfterWrite(doc, path, after, universe) {
|
|
72
75
|
const authored = authoredEntries(doc, path);
|
|
@@ -112,8 +115,10 @@ function unpackFleetProvenance(provenance) {
|
|
|
112
115
|
}
|
|
113
116
|
function setScalarPreservingComment(doc, path, value, authoredComment) {
|
|
114
117
|
const existing = doc.getIn(path, true);
|
|
115
|
-
if (isScalar(existing))
|
|
118
|
+
if (isScalar(existing)) {
|
|
119
|
+
releaseAnchors(doc, path);
|
|
116
120
|
existing.value = value;
|
|
121
|
+
}
|
|
117
122
|
else
|
|
118
123
|
setAt(doc, path, doc.createNode(value));
|
|
119
124
|
if (authoredComment !== undefined) {
|
|
@@ -146,6 +151,10 @@ function setStringSequencePreservingComments(doc, path, values) {
|
|
|
146
151
|
setAt(doc, path, doc.createNode(values));
|
|
147
152
|
return;
|
|
148
153
|
}
|
|
154
|
+
// T10 owed review F2: an unchanged list is not edited, so its anchor and every alias keep their bytes
|
|
155
|
+
if (existing.items.length === values.length && existing.items.every((item, at) => isScalar(item) && String(item.value) === values[at]))
|
|
156
|
+
return;
|
|
157
|
+
releaseAnchors(doc, path); // an anchored list is edited in place: its other aliases keep the original
|
|
149
158
|
const available = [...existing.items];
|
|
150
159
|
existing.items = values.map((value) => {
|
|
151
160
|
const at = available.findIndex((item) => isScalar(item) && String(item.value) === value);
|
|
@@ -175,6 +184,30 @@ function materializeAliases(doc, path) {
|
|
|
175
184
|
doc.setIn(path.slice(0, depth), detachedCopy(doc, node, target, new Set()));
|
|
176
185
|
}
|
|
177
186
|
}
|
|
187
|
+
// T10 (D-973): a write mutates every ancestor on its path and edits or replaces the leaf with all it
|
|
188
|
+
// holds; any of those nodes may be an anchor other aliases share. Each such alias first becomes a
|
|
189
|
+
// detached copy of what it resolved to, so the write never changes another consumer's value nor
|
|
190
|
+
// leaves its alias dangling.
|
|
191
|
+
function releaseAnchors(doc, path) {
|
|
192
|
+
const held = new Set();
|
|
193
|
+
for (let depth = 1; depth <= path.length; depth++) {
|
|
194
|
+
const node = doc.getIn(path.slice(0, depth), true);
|
|
195
|
+
if (!isNode(node))
|
|
196
|
+
break;
|
|
197
|
+
for (const inner of depth < path.length ? [node] : nodesOf(node)) {
|
|
198
|
+
if (!isAlias(inner) && inner.anchor)
|
|
199
|
+
held.add(inner);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (!held.size)
|
|
203
|
+
return;
|
|
204
|
+
visit(doc, {
|
|
205
|
+
Alias: (_key, alias) => {
|
|
206
|
+
const target = alias.resolve(doc);
|
|
207
|
+
return target !== undefined && held.has(target) ? detachedCopy(doc, alias, target, new Set()) : undefined;
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
}
|
|
178
211
|
const nodesOf = (root) => {
|
|
179
212
|
const out = [];
|
|
180
213
|
visit(root, (_key, node) => {
|
|
@@ -231,26 +264,56 @@ function deleteAt(doc, path) {
|
|
|
231
264
|
return;
|
|
232
265
|
}
|
|
233
266
|
// deleteIn throws on an empty document — only delete keys that exist.
|
|
234
|
-
if (doc.getIn(path)
|
|
235
|
-
|
|
267
|
+
if (doc.getIn(path) === undefined)
|
|
268
|
+
return;
|
|
269
|
+
releaseAnchors(doc, path);
|
|
270
|
+
doc.deleteIn(path);
|
|
236
271
|
}
|
|
237
272
|
function setAt(doc, path, value) {
|
|
238
273
|
materializeAliases(doc, path); // edit a copy of an aliased mapping, never drop what it resolved to
|
|
274
|
+
releaseAnchors(doc, path);
|
|
239
275
|
for (let depth = 1; depth < path.length; depth++) {
|
|
240
276
|
const node = doc.getIn(path.slice(0, depth), true);
|
|
241
277
|
if (node === undefined)
|
|
242
278
|
break;
|
|
243
279
|
if (!isMap(node)) {
|
|
244
|
-
|
|
280
|
+
const prefix = path.slice(0, depth);
|
|
281
|
+
doc.deleteIn(prefix);
|
|
282
|
+
// T10: a null mask over membership lists stays a mask for every deny leaf — and the routing.allow
|
|
283
|
+
// block — this write does not set, so materializing one leaf never resurrects a lower layer's
|
|
284
|
+
// other deny lists or its allow form (judge/review/consult reach)
|
|
285
|
+
if (isScalar(node) && node.value === null) {
|
|
286
|
+
const under = (inner, outer) => outer.every((key, at) => inner[at] === key);
|
|
287
|
+
for (const masked of [...DENY_SCOPES.map((scope) => scope.path), ["routing", "allow"]]) {
|
|
288
|
+
if (under(masked, prefix) && !under(masked, path) && !under(path, masked))
|
|
289
|
+
doc.setIn(masked, doc.createNode(null));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
245
292
|
break;
|
|
246
293
|
}
|
|
247
294
|
}
|
|
248
295
|
doc.setIn(path, value);
|
|
249
296
|
}
|
|
297
|
+
// T10 (D-987): Fleet writes through no YAML merge key — a membership write whose edited path (the edited
|
|
298
|
+
// leaf or any map above it) takes keys through `<<` is refused, fail-closed, so nothing is published.
|
|
299
|
+
function refuseMerge(doc, priorBytes, path) {
|
|
300
|
+
let node = doc.contents;
|
|
301
|
+
for (let depth = 0; depth <= path.length && isMap(node); depth++) {
|
|
302
|
+
const merge = node.items.find((item) => isScalar(item.key) && typeof item.key.value === "symbol");
|
|
303
|
+
if (merge) {
|
|
304
|
+
const range = isNode(merge.value) ? merge.value.range : undefined;
|
|
305
|
+
const shape = range ? priorBytes.slice(range[0], range[1]).trim().replace(/\s+/g, " ") : "…";
|
|
306
|
+
throw new ConfigError(`${path.join(".")} takes keys through the YAML merge key \`<<: ${shape}\` in ${path.slice(0, depth).join(".") || "the document root"} — Fleet does not write through merge keys; inline the merged keys there by hand, then save again`);
|
|
307
|
+
}
|
|
308
|
+
node = depth < path.length ? node.get(path[depth], true) : undefined;
|
|
309
|
+
if (isAlias(node))
|
|
310
|
+
node = node.resolve(doc);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
250
313
|
function deleteEmptyMap(doc, path) {
|
|
251
314
|
const node = doc.getIn(path, true);
|
|
252
315
|
if (isMap(node) && node.items.length === 0)
|
|
253
|
-
doc
|
|
316
|
+
deleteAt(doc, path);
|
|
254
317
|
}
|
|
255
318
|
/** Apply only fields fleet authored to the parsed YAML document. Untouched nodes retain their
|
|
256
319
|
* keys, ordering, scalar style, and comments; fresh provenance is written directly on its tier node. */
|
|
@@ -266,24 +329,49 @@ export function renderFleetOverlayWrite(priorBytes, write) {
|
|
|
266
329
|
const flatChanged = new Map(FLAT_DENY_SCOPES.map((scope) => [scope.key, changed(...listsOf(write, scope))]));
|
|
267
330
|
if ([...flatChanged.values()].some(Boolean) || changed(initial.allowOut, edited.allowOut)) {
|
|
268
331
|
if (write.universe) {
|
|
332
|
+
refuseMerge(doc, priorBytes, ["routing", "allow"]);
|
|
269
333
|
// Membership write: the allow form IS the fleet; deny adapters/models scopes are tombstoned
|
|
270
334
|
// so a lower layer can never re-exclude behind the operator's back (workers untouched).
|
|
271
335
|
const form = allowFormFromExclusions(write.universe, edited);
|
|
336
|
+
const universe = write.universe;
|
|
337
|
+
const known = (entry) => universe.some((row) => entry === row.adapter || row.models.some((m) => universeEntryMatches(row, m, entry)));
|
|
338
|
+
const retainedAll = [];
|
|
339
|
+
for (const leaf of ["adapters", "models"]) {
|
|
340
|
+
const retained = [...(write.preservedAllow?.[leaf] ?? []), ...authoredEntries(doc, ["routing", "allow", leaf])]
|
|
341
|
+
.filter((entry) => !known(entry));
|
|
342
|
+
if (!retained.length)
|
|
343
|
+
continue;
|
|
344
|
+
retainedAll.push(...retained);
|
|
345
|
+
form[leaf] = sortedUnique([...form[leaf], ...retained]);
|
|
346
|
+
form.excluded = true;
|
|
347
|
+
}
|
|
348
|
+
// T10 review: a retained allow entry admits a channel the allow form cannot see, so a staged flat
|
|
349
|
+
// deny covering it stays in deny verbatim — folded into the allow form alone, the retained entry
|
|
350
|
+
// would re-admit what the operator just excluded (rail out · all over an unprobed fake:U).
|
|
351
|
+
// Either allow leaf admits a bare model (routing matches both leaves alike), so coverage never
|
|
352
|
+
// depends on which leaf retained the entry.
|
|
353
|
+
// ponytail: a bare retained entry's adapter is unknown, so any discovered adapter id covers it;
|
|
354
|
+
// an identity alias of an unprobed channel is unknowable and is matched by spelling only.
|
|
355
|
+
const coversRetained = (entry) => retainedAll.some((kept) => {
|
|
356
|
+
const colon = kept.indexOf(":");
|
|
357
|
+
if (colon !== -1) {
|
|
358
|
+
const model = kept.slice(colon + 1);
|
|
359
|
+
return universeEntryMatches({ adapter: kept.slice(0, colon), models: [model] }, model, entry);
|
|
360
|
+
}
|
|
361
|
+
return entry === kept || universe.some((row) => row.adapter === entry);
|
|
362
|
+
});
|
|
272
363
|
if (form.excluded) {
|
|
273
364
|
const allowNode = doc.getIn(["routing", "allow"], true);
|
|
274
365
|
if (allowNode !== undefined && !isMap(allowNode))
|
|
275
|
-
doc
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
deleteAt(doc, ["routing", "allow", "models"]);
|
|
366
|
+
deleteAt(doc, ["routing", "allow"]);
|
|
367
|
+
for (const leaf of ["adapters", "models"]) {
|
|
368
|
+
const path = ["routing", "allow", leaf];
|
|
369
|
+
if (form[leaf].length)
|
|
370
|
+
setStringSequencePreservingComments(doc, path, form[leaf]);
|
|
371
|
+
else if (write.lowerAllow?.[leaf] != null)
|
|
372
|
+
setStringSequencePreservingComments(doc, path, null);
|
|
373
|
+
else
|
|
374
|
+
deleteAt(doc, path);
|
|
287
375
|
}
|
|
288
376
|
// Whole fleet out: allow stays present but empty — fail-closed, nothing admitted.
|
|
289
377
|
if (doc.getIn(["routing", "allow"]) === undefined) {
|
|
@@ -291,8 +379,12 @@ export function renderFleetOverlayWrite(priorBytes, write) {
|
|
|
291
379
|
}
|
|
292
380
|
}
|
|
293
381
|
else {
|
|
294
|
-
// Whole fleet in: no restriction to express — the allow block goes away entirely
|
|
295
|
-
|
|
382
|
+
// Whole fleet in: no restriction to express — the allow block goes away entirely, or is
|
|
383
|
+
// masked when a lower layer's allow would otherwise come back (T10 A/B resurrection).
|
|
384
|
+
if (write.lowerAllow)
|
|
385
|
+
setAt(doc, ["routing", "allow"], doc.createNode(null));
|
|
386
|
+
else
|
|
387
|
+
deleteAt(doc, ["routing", "allow"]);
|
|
296
388
|
}
|
|
297
389
|
// Authored and non-canonical entries stay in deny (LEG2-T3 finding 4, round 2 finding 2).
|
|
298
390
|
// OBS-1046: only an EDITED scope is rewritten, and only when its bytes must change — an
|
|
@@ -313,7 +405,8 @@ export function renderFleetOverlayWrite(priorBytes, write) {
|
|
|
313
405
|
const stale = authored.some((entry) => admitted.includes(entry));
|
|
314
406
|
if (!touched && !stale)
|
|
315
407
|
continue;
|
|
316
|
-
|
|
408
|
+
refuseMerge(doc, priorBytes, path);
|
|
409
|
+
const remaining = [...new Set([...flatDenyAfterWrite(doc, path, after, write.universe), ...after.filter(coversRetained)])];
|
|
317
410
|
if (remaining.length) {
|
|
318
411
|
if (remaining.join("\n") !== authored.join("\n"))
|
|
319
412
|
setStringSequencePreservingComments(doc, path, remaining);
|
|
@@ -327,6 +420,7 @@ export function renderFleetOverlayWrite(priorBytes, write) {
|
|
|
327
420
|
for (const scope of FLAT_DENY_SCOPES) {
|
|
328
421
|
if (!flatChanged.get(scope.key))
|
|
329
422
|
continue;
|
|
423
|
+
refuseMerge(doc, priorBytes, scope.path);
|
|
330
424
|
const after = edited[scope.key] ?? [];
|
|
331
425
|
setStringSequencePreservingComments(doc, scope.path, after.length ? sortedUnique(after) : null);
|
|
332
426
|
}
|
|
@@ -341,6 +435,7 @@ export function renderFleetOverlayWrite(priorBytes, write) {
|
|
|
341
435
|
const [before, after] = listsOf(write, scope);
|
|
342
436
|
if (!changed(before, after))
|
|
343
437
|
continue;
|
|
438
|
+
refuseMerge(doc, priorBytes, scope.path);
|
|
344
439
|
setStringSequencePreservingComments(doc, scope.path, after.length ? sortedUnique(after) : null);
|
|
345
440
|
}
|
|
346
441
|
for (const shape of new Set([...Object.keys(initial.map), ...Object.keys(edited.map)])) {
|
|
@@ -530,8 +625,9 @@ export function renderFleetOverlayWrite(priorBytes, write) {
|
|
|
530
625
|
// byte-identical to yaml's own stringifyComment. OBS-1046: a scalar parsed from the prior bytes
|
|
531
626
|
// keeps the exact whitespace it had before its hash sign (yaml itself emits one space, so the
|
|
532
627
|
// sentinel carries the rest); only a comment fleet authored gets the two-space style.
|
|
628
|
+
// T10 (D-936): a flow collection's trailing note (`[codex] # c`) is inline too, so it keeps its gap.
|
|
533
629
|
visit(doc, (_key, node) => {
|
|
534
|
-
if (isScalar(node) && typeof node.comment === "string" && !node.comment.includes("\n")) {
|
|
630
|
+
if ((isScalar(node) || (isCollection(node) && node.flow)) && typeof node.comment === "string" && !node.comment.includes("\n")) {
|
|
535
631
|
const tail = node.range ? priorBytes.slice(node.range[1], node.range[2]) : "";
|
|
536
632
|
const gap = /^([ \t]+)#/.exec(tail)?.[1] ?? " ";
|
|
537
633
|
node.comment = `${INLINE_COMMENT_SENTINEL}${gap.slice(1)}#${node.comment}`;
|
|
@@ -547,6 +643,39 @@ export function renderFleetOverlayWrite(priorBytes, write) {
|
|
|
547
643
|
flowCollectionPadding: false,
|
|
548
644
|
});
|
|
549
645
|
}
|
|
646
|
+
/** T10: the 1-based line of the key that holds `path` in these overlay bytes — the path's own key, the
|
|
647
|
+
* key whose `null` masks it from above, or a `<<` merge key on it (D-987: the write there refuses) — else
|
|
648
|
+
* undefined (the overlay neither declares nor masks it). */
|
|
649
|
+
export function overlayHoldingLine(bytes, path) {
|
|
650
|
+
const doc = parseDocument(bytes);
|
|
651
|
+
if (doc.errors.length)
|
|
652
|
+
return undefined;
|
|
653
|
+
const lineOf = (offset) => bytes.slice(0, offset).split("\n").length;
|
|
654
|
+
let node = doc.contents;
|
|
655
|
+
let line;
|
|
656
|
+
for (const key of path) {
|
|
657
|
+
if (isAlias(node))
|
|
658
|
+
node = node.resolve(doc);
|
|
659
|
+
if (line !== undefined && isScalar(node) && node.value === null)
|
|
660
|
+
return line; // a mask at or above
|
|
661
|
+
if (!isMap(node))
|
|
662
|
+
return undefined;
|
|
663
|
+
const merge = node.items.find((item) => isScalar(item.key) && typeof item.key.value === "symbol");
|
|
664
|
+
const pair = merge ?? node.items.find((item) => isScalar(item.key) && String(item.key.value) === key);
|
|
665
|
+
if (!pair || !isScalar(pair.key) || !pair.key.range)
|
|
666
|
+
return undefined;
|
|
667
|
+
line = lineOf(pair.key.range[0]);
|
|
668
|
+
if (merge)
|
|
669
|
+
return line;
|
|
670
|
+
node = pair.value;
|
|
671
|
+
}
|
|
672
|
+
return line;
|
|
673
|
+
}
|
|
674
|
+
/** T10: does `path` hold a mapping in these overlay bytes — neither absent nor a `null` mask? */
|
|
675
|
+
export function overlayHoldsMap(bytes, path) {
|
|
676
|
+
const doc = parseDocument(bytes);
|
|
677
|
+
return !doc.errors.length && isMap(doc.getIn(path, true));
|
|
678
|
+
}
|
|
550
679
|
/** Build the repo overlay fragment fleet would write for edits since session start. */
|
|
551
680
|
export function fleetRepoOverlayFromDelta(initial, edited, existingRepo = {}, firstTouches = {}, universe) {
|
|
552
681
|
if (fleetEditableEquals(initial, edited))
|
package/dist/drivers/orca.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { type ExecutorDriver, type FocusTarget, type FocusResult, type NotifyOpt
|
|
|
5
5
|
/** The response families the ONE shared envelope parser serves. There is no second JSON seam. */
|
|
6
6
|
export declare const ORCA_RESPONSE_FAMILIES: readonly ["status", "create", "list", "read", "send", "wait", "show", "close", "worktree-current", "worktree-set", "hooks-status", "split"];
|
|
7
7
|
export type OrcaFamily = (typeof ORCA_RESPONSE_FAMILIES)[number];
|
|
8
|
-
export declare const ORCA_FIXTURE_VERSION = "1.4.
|
|
8
|
+
export declare const ORCA_FIXTURE_VERSION = "1.4.218";
|
|
9
9
|
export declare const ORCA_CLI_COMMAND_ENV = "ORCA_CLI_COMMAND";
|
|
10
10
|
export declare const STALE_HANDLE_CODE = "terminal_handle_stale";
|
|
11
11
|
export declare const TERMINAL_GONE_CODE = "terminal_gone";
|
|
@@ -176,6 +176,17 @@ export interface OrcaDriverOpts {
|
|
|
176
176
|
probeStalenessMs?: number;
|
|
177
177
|
launchingHandle?: string;
|
|
178
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* F (D-832): what one proof page's OWN metadata says about the scrollback it was read from — the closed
|
|
181
|
+
* seam checkoutProven decides paging and absence by. `complete`: explicit `limited:false` and
|
|
182
|
+
* `truncated:false` with cursors that agree — nothing lies past this page. `partial`: explicit booleans,
|
|
183
|
+
* one of them true, cursors that agree — more scrollback exists. `unread`: everything else — a flag
|
|
184
|
+
* missing or not a boolean, a cursor that is not a string, `limited:false` with a next cursor short of
|
|
185
|
+
* the latest one (the contradictory "last" page), a blind stream page
|
|
186
|
+
* (isBlindStreamPage), or a source that is not the stream (`screen-unavailable`). Missing metadata is
|
|
187
|
+
* never inferred complete: its page's bytes still count as read evidence, but no absence rests on it.
|
|
188
|
+
*/
|
|
189
|
+
export declare function classifyProofPage(term: Record<string, unknown>): "complete" | "partial" | "unread";
|
|
179
190
|
export declare class OrcaDriver implements ExecutorDriver {
|
|
180
191
|
id: string;
|
|
181
192
|
interactive: boolean;
|
|
@@ -265,8 +276,11 @@ export declare class OrcaDriver implements ExecutorDriver {
|
|
|
265
276
|
* and none carries a trace of the mark; `unread` — every other exit: PROOF_PAGES reached, a limited
|
|
266
277
|
* read with no (or the same) next cursor or a truncated anchor without one, a read that threw, a read
|
|
267
278
|
* the ceiling cut off or never let start (the anchor included), or a page that landed late (even one
|
|
268
|
-
* carrying the right proof), a malformed page (its tail
|
|
269
|
-
*
|
|
279
|
+
* carrying the right proof), a malformed page (its tail or cursors), a blind stream page
|
|
280
|
+
* (isBlindStreamPage), or — F (D-832) — any page read whose paging metadata classifyProofPage cannot
|
|
281
|
+
* call complete or partial (flags missing, not boolean or contradicted; even when they were omitted,
|
|
282
|
+
* that page's bytes stay evidence and its matching proof still proves). Unread output is unknown —
|
|
283
|
+
* never an empty page, never absence.
|
|
270
284
|
* Without a deadline (reconcile, recovery) only `proven` is consumed.
|
|
271
285
|
*/
|
|
272
286
|
private checkoutProven;
|