toolroll 0.9.52 → 0.9.53
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/CHANGELOG.md +8 -0
- package/README.md +55 -80
- package/dist/agentconfig.d.ts +11 -36
- package/dist/agentconfig.js +3 -62
- package/dist/approval-policy.d.ts +1 -1
- package/dist/approval-policy.js +1 -1
- package/dist/board.d.ts +3 -19
- package/dist/board.js +4 -42
- package/dist/browser/workspace.js +18 -18
- package/dist/browser-workspace.d.ts +2 -1
- package/dist/builder.d.ts +9 -40
- package/dist/builder.js +50 -257
- package/dist/chat-actions.js +3 -14
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-decide.js +1 -3
- package/dist/chat-delivery.d.ts +0 -2
- package/dist/chat-delivery.js +0 -1
- package/dist/chat-quiet.js +2 -2
- package/dist/chat-task-actions.d.ts +1 -1
- package/dist/chat-task-actions.js +0 -2
- package/dist/claim.d.ts +79 -244
- package/dist/claim.js +688 -1023
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +10 -2
- package/dist/contracts/cli.d.ts +0 -13
- package/dist/contracts/cli.js +0 -2
- package/dist/contracts/console-api.d.ts +5 -10
- package/dist/contracts/console-api.js +4 -10
- package/dist/contracts/flow.d.ts +50 -0
- package/dist/contracts/flow.js +26 -1
- package/dist/contracts/lead-tools.d.ts +0 -13
- package/dist/contracts/lead-tools.js +3 -3
- package/dist/contracts/store-json.d.ts +1 -25
- package/dist/contracts/store-json.js +3 -13
- package/dist/converse.d.ts +1 -11
- package/dist/converse.js +6 -38
- package/dist/coordinator.d.ts +0 -1
- package/dist/coordinator.js +3 -5
- package/dist/demo.js +16 -26
- package/dist/desktop-update-gate.js +0 -1
- package/dist/desktop-update.js +1 -3
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/dispose.d.ts +2 -3
- package/dist/dispose.js +17 -92
- package/dist/envelope.js +0 -3
- package/dist/exec.d.ts +0 -64
- package/dist/exec.js +3 -356
- package/dist/exhaustion.d.ts +22 -76
- package/dist/exhaustion.js +14 -140
- package/dist/export.js +4 -4
- package/dist/flow-engine.js +1 -1
- package/dist/flow-schedule.d.ts +154 -0
- package/dist/flow-schedule.js +321 -0
- package/dist/flow-share.js +1 -1
- package/dist/flow-triggers.d.ts +6 -1
- package/dist/flow-triggers.js +65 -10
- package/dist/flows-cli.js +8 -4
- package/dist/flows-ui.js +2 -0
- package/dist/flows.js +1 -1
- package/dist/guides.js +9 -29
- package/dist/invoke.d.ts +1 -24
- package/dist/invoke.js +17 -259
- package/dist/lead-status.js +2 -4
- package/dist/lead-voice.js +1 -1
- package/dist/mate-doors.d.ts +1 -2
- package/dist/mate-doors.js +12 -25
- package/dist/mate-tools.d.ts +2 -2
- package/dist/mate-tools.js +10 -22
- package/dist/modes.d.ts +6 -7
- package/dist/modes.js +7 -13
- package/dist/operate-remote-arguments.js +2 -6
- package/dist/operate-remote.js +6 -7
- package/dist/operate.d.ts +0 -6
- package/dist/operate.js +222 -2247
- package/dist/phase-routing.d.ts +13 -23
- package/dist/phase-routing.js +13 -35
- package/dist/plan-auto.js +2 -2
- package/dist/planner-source.js +2 -1
- package/dist/policy.d.ts +1 -4
- package/dist/policy.js +0 -8
- package/dist/process-recovery-eligibility.js +2 -3
- package/dist/project-delete.js +3 -15
- package/dist/proposal.d.ts +4 -24
- package/dist/proposal.js +3 -88
- package/dist/provider-auth.d.ts +3 -3
- package/dist/provider-auth.js +4 -6
- package/dist/provider.d.ts +11 -25
- package/dist/provider.js +4 -36
- package/dist/push.js +1 -1
- package/dist/recipe-ui.js +6 -6
- package/dist/recipes.d.ts +3 -3
- package/dist/recipes.js +27 -23
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +1 -4
- package/dist/scope.d.ts +11 -65
- package/dist/scope.js +17 -82
- package/dist/serve.js +15 -24
- package/dist/server/chat.js +7 -30
- package/dist/server/flows.js +11 -10
- package/dist/server/guards.js +0 -1
- package/dist/server/pages-assets.js +1 -1
- package/dist/server/pages.js +0 -2
- package/dist/server/people-tokens.js +0 -4
- package/dist/server/route-table.d.ts +1 -1
- package/dist/server/route-table.js +2 -17
- package/dist/server/settings.js +1 -8
- package/dist/server/shared.d.ts +28 -212
- package/dist/server/shared.js +71 -780
- package/dist/server/task-views.js +9 -76
- package/dist/server/tasks.d.ts +1 -50
- package/dist/server/tasks.js +36 -946
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.d.ts +0 -2
- package/dist/slack-chat.js +0 -1
- package/dist/store.d.ts +105 -1340
- package/dist/store.js +706 -4010
- package/dist/surface.js +10 -24
- package/dist/task-composer.d.ts +1 -1
- package/dist/task-composer.js +1 -1
- package/dist/task-control.d.ts +3 -10
- package/dist/task-control.js +2 -7
- package/dist/teammate-desk.js +1 -1
- package/dist/teammates-ui.js +1 -1
- package/dist/telegram-mate.d.ts +0 -3
- package/dist/telegram-mate.js +0 -1
- package/dist/templates.d.ts +11 -7
- package/dist/templates.js +12 -9
- package/dist/toolroll-update.d.ts +10 -0
- package/dist/toolroll-update.js +56 -4
- package/dist/work-index.js +2 -6
- package/dist/workspace-ui.js +1 -1
- package/package.json +1 -1
- package/scripts/recovery-canary.mjs +3 -3
- package/dist/contest.d.ts +0 -318
- package/dist/contest.js +0 -753
- package/dist/held.d.ts +0 -150
- package/dist/held.js +0 -799
- package/dist/liveness.d.ts +0 -30
- package/dist/liveness.js +0 -42
- package/dist/pricing.d.ts +0 -45
- package/dist/pricing.js +0 -77
- package/dist/routine.d.ts +0 -228
- package/dist/routine.js +0 -764
- package/dist/supervisor.mjs +0 -297
|
@@ -740,7 +740,6 @@ export type BrowserResultView = {
|
|
|
740
740
|
} | null;
|
|
741
741
|
noRun: string | null;
|
|
742
742
|
panel: BrowserResultPanel | null;
|
|
743
|
-
contest: string;
|
|
744
743
|
notes: {
|
|
745
744
|
author: string;
|
|
746
745
|
at: string;
|
|
@@ -1016,6 +1015,8 @@ export type BrowserFlowTrigger = {
|
|
|
1016
1015
|
checkable: boolean;
|
|
1017
1016
|
/** A button shared as a public form (its link works). */
|
|
1018
1017
|
shared: boolean;
|
|
1018
|
+
/** A schedule's standing order (v115): the terms each run files, in words — shown on demand. */
|
|
1019
|
+
order?: string[];
|
|
1019
1020
|
};
|
|
1020
1021
|
/** A flow's canvas: zones, cards, and what this person may change. */
|
|
1021
1022
|
export type BrowserFlowView = {
|
package/dist/builder.d.ts
CHANGED
|
@@ -28,30 +28,7 @@ export declare function approvedCommandShell(command: string, platform?: NodeJS.
|
|
|
28
28
|
* must not turn an ordinary product failure into environment recovery.
|
|
29
29
|
*/
|
|
30
30
|
export declare function verificationExecutableMissing(result: ExecResult, platform?: NodeJS.Platform): boolean;
|
|
31
|
-
/** The attended dispatch (Parity II Phase 2): the authorization is the
|
|
32
|
-
* authority, the coordinator takes ownership at the spawn point, and the
|
|
33
|
-
* builder returns `held` without settling. */
|
|
34
|
-
export type AttendedDispatch = {
|
|
35
|
-
authorization: import("./store.js").AttendedAuthorization;
|
|
36
|
-
coordinator: import("./held.js").HeldSessionCoordinator;
|
|
37
|
-
upIncarnation: string;
|
|
38
|
-
socketDir: string;
|
|
39
|
-
releaseWorktree: (path: string) => Promise<unknown>;
|
|
40
|
-
dispose: {
|
|
41
|
-
repo: string;
|
|
42
|
-
origin: string;
|
|
43
|
-
provider: string;
|
|
44
|
-
model: string | null;
|
|
45
|
-
policy?: import("./dispose.js").DisposePolicy;
|
|
46
|
-
};
|
|
47
|
-
starter?: import("./exec.js").HeldSessionStart extends never ? never : typeof import("./exec.js").startClaudeHeldSession;
|
|
48
|
-
graceMs?: number;
|
|
49
|
-
/** v28: the operator's session cap, enforced in the custody transaction. */
|
|
50
|
-
maxHeldSessions?: number;
|
|
51
|
-
onDisposed?: import("./held.js").HeldLaunchArgs["onDisposed"];
|
|
52
|
-
};
|
|
53
31
|
export type BuildRequest = {
|
|
54
|
-
attended?: AttendedDispatch;
|
|
55
32
|
taskId: string;
|
|
56
33
|
taskRef: number;
|
|
57
34
|
runner: string;
|
|
@@ -70,10 +47,6 @@ export type BuildRequest = {
|
|
|
70
47
|
* back to the unauthenticated touch, exactly as before.
|
|
71
48
|
*/
|
|
72
49
|
runnerToken?: string;
|
|
73
|
-
/** A tournament contestant's OWN approved profile (v24): under the joint
|
|
74
|
-
* race approval, this — not the scope's single snapshot — is what the
|
|
75
|
-
* dispatch proof holds the invocation to. */
|
|
76
|
-
contestProfile?: ExecutionProfile;
|
|
77
50
|
/**
|
|
78
51
|
* Real time, read repeatedly. `now` is one instant and a build is not: a
|
|
79
52
|
* lease heartbeated with the timestamp the build started at is a lease that
|
|
@@ -91,8 +64,8 @@ export type BuildRequest = {
|
|
|
91
64
|
* gateway refuses a paid call whose run is missing or already finished.
|
|
92
65
|
*/
|
|
93
66
|
runId: number;
|
|
94
|
-
/** Claude's native dollar cap for this attempt
|
|
95
|
-
*
|
|
67
|
+
/** Claude's native dollar cap for this attempt: the harness stops
|
|
68
|
+
* itself at this figure. Absent = uncapped. */
|
|
96
69
|
maxBudgetUsd?: number;
|
|
97
70
|
/** Fires with the provider's process-group id the moment it exists —
|
|
98
71
|
* the worker-process ledger records it (v14). */
|
|
@@ -136,7 +109,7 @@ export type BuildRequest = {
|
|
|
136
109
|
/**
|
|
137
110
|
* What the agent handed over when it parked: the validated decision and the
|
|
138
111
|
* evidence rows already written for it. The caller seals it with
|
|
139
|
-
* `
|
|
112
|
+
* `finalize` (a `park` ending) — nothing here has touched the claim or the task.
|
|
140
113
|
*/
|
|
141
114
|
export type ParkPackage = {
|
|
142
115
|
decision: ParsedDecision;
|
|
@@ -148,9 +121,6 @@ export type BuildResult = {
|
|
|
148
121
|
committed: boolean;
|
|
149
122
|
/** The agent said no-change and the tree proves it: done, nothing to publish. */
|
|
150
123
|
noChange?: boolean;
|
|
151
|
-
/** The session is HELD (attended road): the coordinator owns run,
|
|
152
|
-
* lease, and worktree from here — the caller settles NOTHING. */
|
|
153
|
-
held?: true;
|
|
154
124
|
branch: string;
|
|
155
125
|
summary: string;
|
|
156
126
|
} | {
|
|
@@ -165,9 +135,9 @@ export type BuildResult = {
|
|
|
165
135
|
};
|
|
166
136
|
export type BuildRefusal = "skills-unavailable"
|
|
167
137
|
/** Sprint 8: the organisation policy stops this provider, model or permission level. */
|
|
168
|
-
| "policy" | "unapproved" | "scope-changed" | "stale-approval" | "mode-ended" | "
|
|
138
|
+
| "policy" | "unapproved" | "scope-changed" | "stale-approval" | "mode-ended" | "capability" | "no-claim" | "not-yours" | "not-leased" | "protected-branch" | "wrong-branch" | "moved-branch" | "fenced" | "agent" | "agent-reported" | "no-op"
|
|
169
139
|
/** Run 2085: the agent stopped before its handoff; its work was kept for the retry. */
|
|
170
|
-
| "no-handoff" | "moved-head" | "timeout" | "git" | "commit-failure" | "malformed-decision" | "provider-init" | "setup" | "revision-brief" | "external" | "stopped" | "plan-revised" | "plan-revision-blocked" | "provider-unattested" | "provider-protocol" | "
|
|
140
|
+
| "no-handoff" | "moved-head" | "timeout" | "git" | "commit-failure" | "malformed-decision" | "provider-init" | "setup" | "revision-brief" | "external" | "stopped" | "plan-revised" | "plan-revision-blocked" | "provider-unattested" | "provider-protocol" | "runner-custody" | "auth-mode" | "route-authority" | "containment";
|
|
171
141
|
/** Long enough for real work; short enough that a stuck build ends the same night. */
|
|
172
142
|
export declare const DEFAULT_BUILD_TIMEOUT_MS: number;
|
|
173
143
|
export declare const DEFAULT_MAX_TURNS = 40;
|
|
@@ -208,7 +178,7 @@ export declare const SETUP_ENV_DENYLIST: readonly string[];
|
|
|
208
178
|
export declare function redactSecretText(text: string): string;
|
|
209
179
|
/** The same blanking with no length cap, for a log that keeps its length (a flow script's output). */
|
|
210
180
|
export declare function redactSecretAssignments(text: string): string;
|
|
211
|
-
export declare function proveApprovedProfile(scope: Scope | null,
|
|
181
|
+
export declare function proveApprovedProfile(scope: Scope | null, given: {
|
|
212
182
|
provider: string;
|
|
213
183
|
model: string | undefined;
|
|
214
184
|
maxTurns: number | undefined;
|
|
@@ -236,10 +206,9 @@ export declare function proveApprovedProfile(scope: Scope | null, contestProfile
|
|
|
236
206
|
*/
|
|
237
207
|
export declare function build(store: Store, request: BuildRequest): Promise<BuildResult>;
|
|
238
208
|
/**
|
|
239
|
-
* Everything the post-provider settlement closes over
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* never a paraphrase. `fenced()` reads the pulse's live flag: settlement
|
|
209
|
+
* Everything the post-provider settlement closes over: an explicit record,
|
|
210
|
+
* so the prepared-candidate road runs THE SAME settlement the agent road
|
|
211
|
+
* runs — one state machine, never a paraphrase. `fenced()` reads the pulse's live flag: settlement
|
|
243
212
|
* decisions are about NOW, not about the moment of capture.
|
|
244
213
|
*/
|
|
245
214
|
/** The prepared candidate must be a commit this repository holds and must
|
package/dist/builder.js
CHANGED
|
@@ -47,10 +47,10 @@ import { stopRequestedFor, stopWords, underStopWatch } from "./task-control.js";
|
|
|
47
47
|
import { witnessedRunner } from "./process-custody.js";
|
|
48
48
|
import { runWithIsolatedDatabase } from "./child-database.js";
|
|
49
49
|
import { recordWorktreeProcess, saveWorkPatch } from "./worktree.js";
|
|
50
|
-
import { approvalOf, digestOf, profileDigestOf,
|
|
50
|
+
import { approvalOf, digestOf, profileDigestOf, routeParityProblem } from "./scope.js";
|
|
51
51
|
import { legOf, routeDigestOf, routeFromJson } from "./phase-routing.js";
|
|
52
52
|
import { execFileSync } from "node:child_process";
|
|
53
|
-
import { currentClaim,
|
|
53
|
+
import { currentClaim, heartbeat, SYNC_MAX_AGE_MS, finalize } from "./claim.js";
|
|
54
54
|
import { missingCapability } from "./dispatch.js";
|
|
55
55
|
import { heartbeat as runnerHeartbeat } from "./runner.js";
|
|
56
56
|
import { MARKER as LEASE_MARKER } from "./worktree.js";
|
|
@@ -173,7 +173,7 @@ export function redactSecretAssignments(text) {
|
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
175
|
* The last-mile profile proof (v24, foundations findings 6/17): given the
|
|
176
|
-
* scope (or a
|
|
176
|
+
* scope (or a profile pinned beside it), verify the approval
|
|
177
177
|
* record and hold the invocation to EXACTLY the sealed terms. Returns the
|
|
178
178
|
* effective parameters — the snapshot's values — so an unset request field
|
|
179
179
|
* can never float, and refuses divergence in words.
|
|
@@ -214,13 +214,13 @@ function providerVersionOf(provider) {
|
|
|
214
214
|
PROVIDER_VERSIONS.set(provider, version);
|
|
215
215
|
return version;
|
|
216
216
|
}
|
|
217
|
-
export function proveApprovedProfile(scope,
|
|
217
|
+
export function proveApprovedProfile(scope, given) {
|
|
218
218
|
// The raw terms first (raw authority repair): a scope whose stored terms
|
|
219
219
|
// do not read back exactly proves nothing — not the filtered reading.
|
|
220
|
-
if (
|
|
220
|
+
if (scope !== null && scope.termsProblem != null) {
|
|
221
221
|
return { ok: false, message: `the scope's stored terms cannot be read exactly (${scope.termsProblem}) — re-file the scope and approve it again (stale-approval)` };
|
|
222
222
|
}
|
|
223
|
-
const snapshot =
|
|
223
|
+
const snapshot = scope?.approvedProfile ?? null;
|
|
224
224
|
if (snapshot === null) {
|
|
225
225
|
return {
|
|
226
226
|
ok: false,
|
|
@@ -231,7 +231,7 @@ export function proveApprovedProfile(scope, contestProfile, given) {
|
|
|
231
231
|
// the column is bookkeeping, the recomputation is the proof. Grandfathered
|
|
232
232
|
// v1 approvals rederive without the profile (their signed bytes) and are
|
|
233
233
|
// held to the snapshot pinned at migration.
|
|
234
|
-
if (
|
|
234
|
+
if (scope !== null) {
|
|
235
235
|
const rederived = (scope.digestVersion ?? 1) >= 2
|
|
236
236
|
? digestOf({
|
|
237
237
|
goal: scope.goal,
|
|
@@ -310,15 +310,8 @@ export function proveApprovedProfile(scope, contestProfile, given) {
|
|
|
310
310
|
export async function build(store, request) {
|
|
311
311
|
const { taskId, taskRef, runner, worktree, branch, now, permissionMode = "auto", skipPermissions = false, provider = "claude", model, maxTurns = DEFAULT_MAX_TURNS, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS, agent, git = run, } = request;
|
|
312
312
|
const scope = store.getScope(taskId);
|
|
313
|
-
// The run row, for the chain-entry binding (E3d): a run created by the
|
|
314
|
-
// cycle roads carries chain_cycle/chain_index/entry_digest/auth_mode, and
|
|
315
|
-
// the dispatch proof below re-derives every one of them.
|
|
316
|
-
const chainRun = store.getRun(request.runId);
|
|
317
313
|
const approval = approvalOf(scope);
|
|
318
|
-
|
|
319
|
-
? request.attended
|
|
320
|
-
: undefined;
|
|
321
|
-
if (!approval.approved && attended === undefined) {
|
|
314
|
+
if (!approval.approved) {
|
|
322
315
|
return approval.reason === "changed"
|
|
323
316
|
? {
|
|
324
317
|
ok: false,
|
|
@@ -335,7 +328,7 @@ export async function build(store, request) {
|
|
|
335
328
|
// finding 2): a mode-sealed approval re-proves its signature still
|
|
336
329
|
// stands here too — the claim roads already refuse, and this covers a
|
|
337
330
|
// custom driver calling build directly with a stale claim.
|
|
338
|
-
if (
|
|
331
|
+
if (!store.modeApprovalLive(taskRef, request.now)) {
|
|
339
332
|
return {
|
|
340
333
|
ok: false,
|
|
341
334
|
reason: "mode-ended",
|
|
@@ -347,111 +340,24 @@ export async function build(store, request) {
|
|
|
347
340
|
// permissions, limits — with the approved digest REDERIVED from the live
|
|
348
341
|
// fields plus the snapshot, never trusted as a column. The profile is
|
|
349
342
|
// the authority: request fields left unset take its values; request
|
|
350
|
-
// fields that DIVERGE refuse, typed, naming what moved.
|
|
351
|
-
// proves against its own race-approved profile.
|
|
343
|
+
// fields that DIVERGE refuse, typed, naming what moved.
|
|
352
344
|
let effective;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
catch {
|
|
363
|
-
pinnedJson = null;
|
|
364
|
-
}
|
|
365
|
-
const pinned = profileFromJson(pinnedJson);
|
|
366
|
-
if (pinned === null) {
|
|
367
|
-
return { ok: false, reason: "stale-authorization", message: `${taskId}: the authorization's pinned profile cannot be rehydrated` };
|
|
368
|
-
}
|
|
369
|
-
effective = {
|
|
370
|
-
model: pinned.model,
|
|
371
|
-
maxTurns: pinned.provider === "claude" ? pinned.maxTurns : request.maxTurns,
|
|
372
|
-
timeoutMs: pinned.timeoutSeconds * 1000,
|
|
373
|
-
skipPermissions: profileWantsSkip(pinned),
|
|
374
|
-
profile: pinned,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
else if (chainRun !== null && chainRun.chainCycle != null) {
|
|
378
|
-
// THE CHAIN-ENTRY DISPATCH PROOF (E3d, review findings 5/6): a run bound
|
|
379
|
-
// to a fallback-chain entry proves against the IMMUTABLE approved chain,
|
|
380
|
-
// never the single-profile snapshot. Everything is RE-DERIVED here, none
|
|
381
|
-
// of it trusted from the caller: the chain approval must still stand
|
|
382
|
-
// (approvedChainOf proves digest freshness), the LIVE cycle must be this
|
|
383
|
-
// run's cycle — same id, open, cursor at this run's index, this run as
|
|
384
|
-
// its tail, digest matching the approved chain — and the run's pinned
|
|
385
|
-
// entry digest + auth mode must equal the approved entry at that index.
|
|
386
|
-
// Only then does the entry's WHOLE profile (and nothing else) run.
|
|
387
|
-
const chain = store.approvedChainOf(taskId);
|
|
388
|
-
if (chain === null) {
|
|
389
|
-
return { ok: false, reason: "stale-approval", message: `${taskId}: the chain approval no longer stands — re-approve (stale-approval)` };
|
|
390
|
-
}
|
|
391
|
-
// The run must belong to THE TASK being built (finding 7 + verify R7):
|
|
392
|
-
// a chain digest excludes scope text, so two tasks can share one. The
|
|
393
|
-
// task_ref equality alone is not enough — the caller supplies BOTH
|
|
394
|
-
// taskRef and taskId, so the pairing itself is re-derived from the
|
|
395
|
-
// store: the ref row's own external id must name exactly the task whose
|
|
396
|
-
// scope authorizes this build.
|
|
397
|
-
const chainOwner = store.refForId(taskRef);
|
|
398
|
-
if (chainRun.taskRef !== taskRef ||
|
|
399
|
-
chainOwner === null ||
|
|
400
|
-
chainOwner.backend !== "built-in" ||
|
|
401
|
-
chainOwner.externalId !== taskId) {
|
|
402
|
-
return { ok: false, reason: "stale-approval", message: `${taskId}: this run belongs to a different task than its dispatch claims (stale-approval)` };
|
|
403
|
-
}
|
|
404
|
-
const cycle = store.fallbackCycleFor(taskRef);
|
|
405
|
-
if (cycle === null ||
|
|
406
|
-
cycle.id !== chainRun.chainCycle ||
|
|
407
|
-
cycle.state !== "open" ||
|
|
408
|
-
cycle.cursor !== chainRun.chainIndex ||
|
|
409
|
-
cycle.tailRun !== request.runId ||
|
|
410
|
-
cycle.chainDigest !== chainDigestOf(chain)) {
|
|
411
|
-
return { ok: false, reason: "stale-approval", message: `${taskId}: this run is not the live custody of its fallback cycle — nothing spends outside the cycle (stale-approval)` };
|
|
412
|
-
}
|
|
413
|
-
const entry = chain[chainRun.chainIndex ?? -1];
|
|
414
|
-
if (entry === undefined || entryDigestOf(entry) !== chainRun.entryDigest || entry.authMode !== chainRun.authMode) {
|
|
415
|
-
return { ok: false, reason: "stale-approval", message: `${taskId}: the run's pinned entry does not match the approved chain at its index (stale-approval)` };
|
|
416
|
-
}
|
|
417
|
-
const proof = proveApprovedProfile(scope, entry.profile, {
|
|
418
|
-
provider,
|
|
419
|
-
model: request.model,
|
|
420
|
-
maxTurns: request.maxTurns,
|
|
421
|
-
timeoutMs: request.timeoutMs,
|
|
422
|
-
skipPermissions,
|
|
423
|
-
});
|
|
424
|
-
if (!proof.ok) {
|
|
425
|
-
return { ok: false, reason: "stale-approval", message: `${taskId}: ${proof.message}` };
|
|
426
|
-
}
|
|
427
|
-
effective = proof.effective;
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
// A CHAIN approval dispatches ONLY through its cycle (finding 6): an
|
|
431
|
-
// ordinary (non-contest) run on a chain scope that carries no cycle
|
|
432
|
-
// binding must never fall through to the single-profile proof — that
|
|
433
|
-
// proof cannot verify a chain digest, and a run outside the cycle would
|
|
434
|
-
// spend outside its custody.
|
|
435
|
-
if (request.contestProfile === undefined && scope?.approvalKind === "chain") {
|
|
436
|
-
return { ok: false, reason: "stale-approval", message: `${taskId}: a chain approval dispatches only through its fallback cycle — this run carries no cycle binding (stale-approval)` };
|
|
437
|
-
}
|
|
438
|
-
const proof = proveApprovedProfile(scope, request.contestProfile ?? null, {
|
|
439
|
-
provider,
|
|
440
|
-
model: request.model,
|
|
441
|
-
maxTurns: request.maxTurns,
|
|
442
|
-
timeoutMs: request.timeoutMs,
|
|
443
|
-
skipPermissions,
|
|
444
|
-
});
|
|
445
|
-
if (!proof.ok) {
|
|
446
|
-
return { ok: false, reason: "stale-approval", message: `${taskId}: ${proof.message}` };
|
|
447
|
-
}
|
|
448
|
-
effective = proof.effective;
|
|
345
|
+
const dispatchProof = proveApprovedProfile(scope, {
|
|
346
|
+
provider,
|
|
347
|
+
model: request.model,
|
|
348
|
+
maxTurns: request.maxTurns,
|
|
349
|
+
timeoutMs: request.timeoutMs,
|
|
350
|
+
skipPermissions,
|
|
351
|
+
});
|
|
352
|
+
if (!dispatchProof.ok) {
|
|
353
|
+
return { ok: false, reason: "stale-approval", message: `${taskId}: ${dispatchProof.message}` };
|
|
449
354
|
}
|
|
355
|
+
effective = dispatchProof.effective;
|
|
450
356
|
// The dispatch stamps (finding 21's order): written the moment the proof
|
|
451
357
|
// passes, before anything provider-shaped happens — the run row then says
|
|
452
358
|
// exactly which sealed terms this invocation was held to, and warm
|
|
453
359
|
// resume below can match on them honestly.
|
|
454
|
-
const provenScopeDigest =
|
|
360
|
+
const provenScopeDigest = scope?.approvedDigest ?? "";
|
|
455
361
|
const provenProfileDigest = profileDigestOf(effective.profile);
|
|
456
362
|
store.stampRun(request.runId, {
|
|
457
363
|
scopeDigest: provenScopeDigest,
|
|
@@ -461,8 +367,7 @@ export async function build(store, request) {
|
|
|
461
367
|
: {}),
|
|
462
368
|
});
|
|
463
369
|
// ROUTE PROVENANCE (v47), written once at admission: every run — a
|
|
464
|
-
// routed dispatch, a
|
|
465
|
-
// session, a pre-routing row's build — was stamped inside its own
|
|
370
|
+
// routed dispatch, a pre-routing row's build — was stamped inside its own
|
|
466
371
|
// insert (v48 integrity: there is no late stamp). What is about to
|
|
467
372
|
// spend must BE that stamp — the same provider and exact model — or
|
|
468
373
|
// execution is refused before any spawn; a run that carries none was
|
|
@@ -470,7 +375,7 @@ export async function build(store, request) {
|
|
|
470
375
|
// it.
|
|
471
376
|
{
|
|
472
377
|
const existing = store.runRoute(request.runId);
|
|
473
|
-
const sealed =
|
|
378
|
+
const sealed = store.sealedRouteOf(taskId);
|
|
474
379
|
if (existing === null) {
|
|
475
380
|
return {
|
|
476
381
|
ok: false,
|
|
@@ -506,14 +411,10 @@ export async function build(store, request) {
|
|
|
506
411
|
};
|
|
507
412
|
}
|
|
508
413
|
}
|
|
509
|
-
// THE ORGANISATION POLICY (sprint 8), the last look before money on every build road — the tick, a
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
// these terms, are refused. The sealed terms and their stamps are untouched: only what this invocation runs with.
|
|
414
|
+
// THE ORGANISATION POLICY (sprint 8), the last look before money on every build road — the tick, a
|
|
415
|
+
// direct build: a provider or model it doesn't allow never spawns; terms above its permission ceiling run lowered
|
|
416
|
+
// (said on the run). The sealed terms and their stamps are untouched: only what this invocation runs with.
|
|
513
417
|
{
|
|
514
|
-
const attendedRefused = attended === undefined ? null : store.attendedPolicyRefusal(effective.profile);
|
|
515
|
-
if (attendedRefused !== null)
|
|
516
|
-
return { ok: false, reason: "policy", message: `${taskId}: ${attendedRefused}` };
|
|
517
418
|
const verdict = store.runPolicy(effective.profile);
|
|
518
419
|
if (!verdict.ok)
|
|
519
420
|
return { ok: false, reason: "policy", message: `${taskId}: ${verdict.message}` };
|
|
@@ -624,8 +525,6 @@ export async function build(store, request) {
|
|
|
624
525
|
try {
|
|
625
526
|
codingHandoff = readCodingHandoff(store, taskId);
|
|
626
527
|
if (codingHandoff !== null) {
|
|
627
|
-
if (request.attended !== undefined)
|
|
628
|
-
throw Error("This saved coding result must use its prepared review task.");
|
|
629
528
|
const head = await git(GIT, ["--no-optional-locks", "rev-parse", "HEAD"], { cwd: worktree });
|
|
630
529
|
const actual = await git(GIT, ["--no-optional-locks", "symbolic-ref", "--short", "HEAD"], { cwd: worktree });
|
|
631
530
|
if (head.code !== 0 || actual.code !== 0 || actual.stdout.trim() !== branch)
|
|
@@ -737,7 +636,7 @@ export async function build(store, request) {
|
|
|
737
636
|
}
|
|
738
637
|
return null;
|
|
739
638
|
};
|
|
740
|
-
if (preparedCandidate === null
|
|
639
|
+
if (preparedCandidate === null) {
|
|
741
640
|
const setupFailure = await runApprovedSetup();
|
|
742
641
|
if (setupFailure !== null)
|
|
743
642
|
return setupFailure;
|
|
@@ -1161,13 +1060,6 @@ export async function build(store, request) {
|
|
|
1161
1060
|
milestones.length === 0 || planRevisionHash === null
|
|
1162
1061
|
? null
|
|
1163
1062
|
: { revision: planRevisionNumber, hash: planRevisionHash, milestones, progress, proposal }, scope === null ? [] : flowGoalCuts(store, scope.taskId, scope.goal)) + `\nCanonical signed rubric: ${rubric}. Its statement fields are exact; evidence requirements are separate fields. Do not edit this input. The lead or user reads these criteria directly; no restatement is needed.\n` + sharedDepsBrief(store, worktree);
|
|
1164
|
-
// THE HELD BRANCH (Phase 2, v2 S0d + v6 W8): ownership transfers to the
|
|
1165
|
-
// coordinator at the spawn point. Everything build() armed that its
|
|
1166
|
-
// finally would have cleared is torn down or handed over HERE — the pulse
|
|
1167
|
-
// interval dies (the coordinator heartbeats from now on; no doubled
|
|
1168
|
-
// writers) and the live-log handle rides the capture (the live window
|
|
1169
|
-
// stays streaming across the whole hold). build() returns WITHOUT
|
|
1170
|
-
// settling: the run, the lease, and the worktree are the coordinator's.
|
|
1171
1063
|
// THE PREPARED-CANDIDATE ROAD (v69): the scope names a commit, proved
|
|
1172
1064
|
// above before anything moved. The machine brings the worktree to its exact
|
|
1173
1065
|
// tree — uncommitted, as an agent would leave it — writes the handoff
|
|
@@ -1176,7 +1068,7 @@ export async function build(store, request) {
|
|
|
1176
1068
|
// gate. No provider is spawned; the lease heartbeat keeps
|
|
1177
1069
|
// running until settlement returns, exactly as it does around a provider.
|
|
1178
1070
|
const prepared = scope?.candidate ?? null;
|
|
1179
|
-
if (prepared !== null
|
|
1071
|
+
if (prepared !== null) {
|
|
1180
1072
|
try {
|
|
1181
1073
|
if (stopRequestedFor(store, request.runId, request.shouldStop))
|
|
1182
1074
|
return { ok: false, reason: "stopped", message: stopWords(store, request.runId, worktree, "The attempt was stopped before the prepared candidate was loaded.") };
|
|
@@ -1252,54 +1144,6 @@ export async function build(store, request) {
|
|
|
1252
1144
|
liveLog?.close();
|
|
1253
1145
|
}
|
|
1254
1146
|
}
|
|
1255
|
-
if (attended !== undefined) {
|
|
1256
|
-
if (pulseTimer !== undefined)
|
|
1257
|
-
clearInterval(pulseTimer);
|
|
1258
|
-
// The follow-up is NEW INSTRUCTION inside the signed terms (v2 S3c):
|
|
1259
|
-
// it rides the brief in an OPERATOR fence, after the scope text —
|
|
1260
|
-
// operator speech, exactly like turns, never widening scope.
|
|
1261
|
-
const followup = attended.authorization.followup;
|
|
1262
|
-
const heldBrief = followup === null || followup === undefined
|
|
1263
|
-
? briefText
|
|
1264
|
-
: `${briefText}\n\n=== OPERATOR FOLLOW-UP (this session continues finished attempt #${attended.authorization.parentRun ?? "?"}) ===\n${followup}\n=== END OPERATOR FOLLOW-UP ===`;
|
|
1265
|
-
const captured = {
|
|
1266
|
-
store, request, agent, git, worktree, branch, baseRevision, taskId, taskRef,
|
|
1267
|
-
runner, provider, scope, effective, answers, timeoutMs, root, mailbox, done, proof, rubric,
|
|
1268
|
-
clock, fenced: () => fencedMidBuild,
|
|
1269
|
-
plan: { proposal, revision: planRevisionNumber, authority, progress: progressState },
|
|
1270
|
-
};
|
|
1271
|
-
const launched = await attended.coordinator.launch({
|
|
1272
|
-
store,
|
|
1273
|
-
captured,
|
|
1274
|
-
authorization: attended.authorization,
|
|
1275
|
-
runId: request.runId,
|
|
1276
|
-
leaseId: request.leaseId ?? "unclaimed",
|
|
1277
|
-
runner,
|
|
1278
|
-
...(request.runnerToken === undefined ? {} : { runnerToken: request.runnerToken }),
|
|
1279
|
-
upIncarnation: attended.upIncarnation,
|
|
1280
|
-
brief: heldBrief,
|
|
1281
|
-
cwd: worktree,
|
|
1282
|
-
socketDir: attended.socketDir,
|
|
1283
|
-
releaseWorktree: attended.releaseWorktree,
|
|
1284
|
-
liveLog,
|
|
1285
|
-
omitEnv: AGENT_ENV_DENYLIST,
|
|
1286
|
-
dispose: attended.dispose,
|
|
1287
|
-
clock,
|
|
1288
|
-
...(attended.starter === undefined ? {} : { starter: attended.starter }),
|
|
1289
|
-
...(attended.graceMs === undefined ? {} : { graceMs: attended.graceMs }),
|
|
1290
|
-
...(attended.maxHeldSessions === undefined ? {} : { maxHeldSessions: attended.maxHeldSessions }),
|
|
1291
|
-
...(attended.onDisposed === undefined ? {} : { onDisposed: attended.onDisposed }),
|
|
1292
|
-
});
|
|
1293
|
-
if (!launched.ok) {
|
|
1294
|
-
liveLog?.close();
|
|
1295
|
-
return {
|
|
1296
|
-
ok: false,
|
|
1297
|
-
reason: launched.reason ?? "attended-only",
|
|
1298
|
-
message: launched.message,
|
|
1299
|
-
};
|
|
1300
|
-
}
|
|
1301
|
-
return { ok: true, held: true, committed: false, branch, summary: "the session is held — the operator is watching" };
|
|
1302
|
-
}
|
|
1303
1147
|
let invoked;
|
|
1304
1148
|
try {
|
|
1305
1149
|
invoked = await invokeAgent(store, request.runId,
|
|
@@ -1387,10 +1231,9 @@ export async function build(store, request) {
|
|
|
1387
1231
|
}
|
|
1388
1232
|
}
|
|
1389
1233
|
/**
|
|
1390
|
-
* Everything the post-provider settlement closes over
|
|
1391
|
-
*
|
|
1392
|
-
*
|
|
1393
|
-
* never a paraphrase. `fenced()` reads the pulse's live flag: settlement
|
|
1234
|
+
* Everything the post-provider settlement closes over: an explicit record,
|
|
1235
|
+
* so the prepared-candidate road runs THE SAME settlement the agent road
|
|
1236
|
+
* runs — one state machine, never a paraphrase. `fenced()` reads the pulse's live flag: settlement
|
|
1394
1237
|
* decisions are about NOW, not about the moment of capture.
|
|
1395
1238
|
*/
|
|
1396
1239
|
/** The prepared candidate must be a commit this repository holds and must
|
|
@@ -1472,10 +1315,9 @@ function ingestProgress(store, state, now) {
|
|
|
1472
1315
|
* The post-provider state machine, extracted verbatim from build(): the
|
|
1473
1316
|
* timeout/init/agent classification, the synchronous fence re-proof, park
|
|
1474
1317
|
* ingestion (with its repair turns), the branch and HEAD laws, handoff
|
|
1475
|
-
* validation, evidence capture, and the commit. build() calls it
|
|
1476
|
-
*
|
|
1477
|
-
*
|
|
1478
|
-
* a run completes through THIS function or not at all.
|
|
1318
|
+
* validation, evidence capture, and the commit. build() calls it for the
|
|
1319
|
+
* agent road and the prepared-candidate road: a run completes through THIS
|
|
1320
|
+
* function or not at all.
|
|
1479
1321
|
*/
|
|
1480
1322
|
/** The handoff's route line (v47): the run's stamped provenance, or nothing
|
|
1481
1323
|
* for a run that opened before routes existed. */
|
|
@@ -1565,7 +1407,7 @@ export async function settleProviderOutcome(captured, result) {
|
|
|
1565
1407
|
// before anything commits: a park never commits — whatever work is in
|
|
1566
1408
|
// progress stays in the worktree, preserved for the resume — and this
|
|
1567
1409
|
// function only assembles the package. Sealing it against the lease is
|
|
1568
|
-
// `
|
|
1410
|
+
// `finalize` (a `park` ending), one transaction, in the caller's hands.
|
|
1569
1411
|
store.setRunPhase(request.runId, "validating-handoff");
|
|
1570
1412
|
if (result.sessionId !== null) {
|
|
1571
1413
|
store.stampRun(request.runId, { sessionId: result.sessionId });
|
|
@@ -1817,7 +1659,7 @@ export async function settleProviderOutcome(captured, result) {
|
|
|
1817
1659
|
// result is that commit, sealed below against the task's original base.
|
|
1818
1660
|
const made = finishesKeptWork
|
|
1819
1661
|
? await keptWorkResult(captured, baseRevision, handoff.conclusion)
|
|
1820
|
-
: await commit(git, worktree, branch, taskId, scope, handoff.conclusion,
|
|
1662
|
+
: await commit(git, worktree, branch, taskId, scope, handoff.conclusion, scope?.candidate ?? null, words => store.addRunNote(request.runId, "Toolroll", words, clock()));
|
|
1821
1663
|
if (made.ok && made.parked === undefined && made.committed) {
|
|
1822
1664
|
const newHead = await git(GIT, ["--no-optional-locks", "rev-parse", "HEAD"], { cwd: worktree });
|
|
1823
1665
|
if (newHead.code === 0) {
|
|
@@ -1984,8 +1826,8 @@ function settleRevisionProposal(captured, binding) {
|
|
|
1984
1826
|
deliverable: store.refForId(taskRef)?.deliverable ?? "branch",
|
|
1985
1827
|
};
|
|
1986
1828
|
const classification = classifyRevisionAuthority(binding.authority, now);
|
|
1987
|
-
const sealed =
|
|
1988
|
-
|
|
1829
|
+
const sealed = finalize(store, request.leaseId, {
|
|
1830
|
+
kind: "revision",
|
|
1989
1831
|
runId: request.runId,
|
|
1990
1832
|
taskId,
|
|
1991
1833
|
taskRef,
|
|
@@ -2567,8 +2409,8 @@ async function startsFromKeptWork(captured, baseRevision, pinnedBase) {
|
|
|
2567
2409
|
}
|
|
2568
2410
|
/** The accepted result of an attempt that finished kept work with nothing left to change. An exact prepared candidate must still match it. */
|
|
2569
2411
|
async function keptWorkResult(captured, head, summary) {
|
|
2570
|
-
const { git, worktree, branch,
|
|
2571
|
-
const candidate =
|
|
2412
|
+
const { git, worktree, branch, scope } = captured;
|
|
2413
|
+
const candidate = scope?.candidate ?? null;
|
|
2572
2414
|
if (candidate !== null) {
|
|
2573
2415
|
const exact = await git(GIT, ["--no-optional-locks", "diff", "--quiet", candidate, head, "--"], { cwd: worktree });
|
|
2574
2416
|
if (exact.code !== 0)
|
|
@@ -2615,8 +2457,7 @@ async function resumeUnhandedWork(captured, sessionId) {
|
|
|
2615
2457
|
const waiting = () => !existsSync(join(worktree, done)) &&
|
|
2616
2458
|
!existsSync(join(worktree, mailbox)) &&
|
|
2617
2459
|
(captured.plan === undefined || !existsSync(join(worktree, captured.plan.proposal)));
|
|
2618
|
-
|
|
2619
|
-
if (request.attended !== undefined || !waiting() || !canResumeSession(captured, sessionId))
|
|
2460
|
+
if (!waiting() || !canResumeSession(captured, sessionId))
|
|
2620
2461
|
return null;
|
|
2621
2462
|
const changes = await unhandedChanges(git, worktree);
|
|
2622
2463
|
if (changes === null || changes.length === 0)
|
|
@@ -2705,7 +2546,7 @@ async function keepUnhandedWork(captured, sessionId) {
|
|
|
2705
2546
|
if (!saved.ok) {
|
|
2706
2547
|
return { ok: false, reason: "no-handoff", message: `${NO_HANDOFF_WORDS} Its changes stay uncommitted in ${worktree}; ${saved.message}.` };
|
|
2707
2548
|
}
|
|
2708
|
-
if (
|
|
2549
|
+
if (!canResumeSession(captured, sessionId) && scope !== null && !stopRequestedFor(store, request.runId, request.shouldStop)) {
|
|
2709
2550
|
const made = await commit(git, worktree, branch, taskId, scope, WIP_COMMIT_WORDS);
|
|
2710
2551
|
if (made.ok && "committed" in made && made.committed) {
|
|
2711
2552
|
return { ok: false, reason: "no-handoff", message: `${NO_HANDOFF_WORDS} It was saved as a work-in-progress commit on ${branch}.` };
|
|
@@ -2811,13 +2652,10 @@ async function ingestPark(args) {
|
|
|
2811
2652
|
// approval froze. The repair model comes from the sealed profile
|
|
2812
2653
|
// ("inherit" = the build's exact model); a disagreement with the sealed
|
|
2813
2654
|
// route, or an unreadable route on a routed row, refuses the repair in
|
|
2814
|
-
// words rather than mending under an agent nobody approved.
|
|
2815
|
-
//
|
|
2816
|
-
//
|
|
2817
|
-
//
|
|
2818
|
-
// fallback repair against the approved chain entry's exact repair model
|
|
2819
|
-
// under the parent's route digest, the chain binding inherited verbatim
|
|
2820
|
-
// below — so no repair turn can open under a lineage nobody approved.
|
|
2655
|
+
// words rather than mending under an agent nobody approved. A legacy
|
|
2656
|
+
// parent's repair stays `legacy`. Admission (v48) then proves the stamp
|
|
2657
|
+
// again inside the run's own transaction, so no repair turn can open
|
|
2658
|
+
// under a lineage nobody approved.
|
|
2821
2659
|
const repairModel = repairModelOf(args.profile, request);
|
|
2822
2660
|
for (let turn = 0; turn < REPAIR_TURNS && (resumableRepair ? sessionId !== undefined : true); turn++) {
|
|
2823
2661
|
// The lease is re-proved around every repair turn: extended going in,
|
|
@@ -2828,7 +2666,7 @@ async function ingestPark(args) {
|
|
|
2828
2666
|
return { fenced: true };
|
|
2829
2667
|
}
|
|
2830
2668
|
// v105: a repair turn spends too — a budget used up since the build began stops further turns (billed to a key).
|
|
2831
|
-
// It bills as the attempt it mends did
|
|
2669
|
+
// It bills as the attempt it mends did.
|
|
2832
2670
|
if (store.budgetGate(clock())({ ...store.budgetSubject(request.taskRef), agents: [{ provider: repairProvider, billing: store.runBilling(request.runId) ?? store.agentsFor([repairProvider])[0].billing }] }).over !== null)
|
|
2833
2671
|
break;
|
|
2834
2672
|
// Sprint 8: nor does a repair run on a provider or model the organisation policy doesn't allow.
|
|
@@ -2838,11 +2676,6 @@ async function ingestPark(args) {
|
|
|
2838
2676
|
if (!admitted.ok)
|
|
2839
2677
|
return admitted;
|
|
2840
2678
|
const repairRun = admitted.runId;
|
|
2841
|
-
// A repair turn inherits its parent's chain binding VERBATIM (Codex E3d
|
|
2842
|
-
// review, finding 2) — inside its own admission (v48 authority repair), so the pinned
|
|
2843
|
-
// entry, auth mode included, follows the custody from the first byte
|
|
2844
|
-
// of the row and the mending turn spends under exactly the credential
|
|
2845
|
-
// the operator approved for this entry.
|
|
2846
2679
|
const spoken = await invokeAgent(store, repairRun, { provider: repairProvider, model: repairModel }, {
|
|
2847
2680
|
phase: "repair",
|
|
2848
2681
|
brief: resumableRepair
|
|
@@ -2936,8 +2769,8 @@ function admitProtocolRepair(store, request, profile, sessionId, clock) {
|
|
|
2936
2769
|
const repairScope = store.getScope(request.taskId);
|
|
2937
2770
|
const repairSealed = repairScope !== null && repairScope.routeEra != null ? store.sealedRouteOf(request.taskId) : null;
|
|
2938
2771
|
const repairModel = repairModelOf(args.profile, request);
|
|
2939
|
-
let repairChosen =
|
|
2940
|
-
if (repairSealed !== null
|
|
2772
|
+
let repairChosen = "legacy";
|
|
2773
|
+
if (repairSealed !== null) {
|
|
2941
2774
|
if (!repairSealed.ok) {
|
|
2942
2775
|
return { ok: false, problems: [{ reason: "route-unreadable", message: `the repair cannot run: ${repairSealed.detail}` }] };
|
|
2943
2776
|
}
|
|
@@ -2961,47 +2794,7 @@ function admitProtocolRepair(store, request, profile, sessionId, clock) {
|
|
|
2961
2794
|
};
|
|
2962
2795
|
let repairRun;
|
|
2963
2796
|
try {
|
|
2964
|
-
|
|
2965
|
-
// A repair turn under an approved FALLBACK entry is admitted by the
|
|
2966
|
-
// one fallback road (v48 integrity): every fact the parent's
|
|
2967
|
-
// binding states — cycle, index, digest, auth mode, provider, the
|
|
2968
|
-
// exact repair model, the sealed-profile mirror — is presented
|
|
2969
|
-
// and re-proved against the approved chain and the live cycle,
|
|
2970
|
-
// with the parent as the live tail, before any row exists.
|
|
2971
|
-
const parentRun = store.getRun(runId);
|
|
2972
|
-
const chain = store.approvedChainOf(request.taskId);
|
|
2973
|
-
const mirror = repairScope?.approvedProfile ?? null;
|
|
2974
|
-
const entry = parentRun !== null && parentRun.chainIndex != null && chain !== null ? chain[parentRun.chainIndex] : undefined;
|
|
2975
|
-
if (parentRun === null || parentRun.chainCycle == null || parentRun.chainIndex == null || parentRun.entryDigest == null || parentRun.authMode == null || chain === null || mirror === null || entry === undefined || repairModel === null) {
|
|
2976
|
-
return { ok: false, problems: [{ reason: "route-unreadable", message: `the repair cannot run: run #${runId}'s fallback binding cannot be restated against the approved chain — nothing mends outside the cycle` }] };
|
|
2977
|
-
}
|
|
2978
|
-
const admitted = store.admitFallback({
|
|
2979
|
-
kind: "repair",
|
|
2980
|
-
parentRun: runId,
|
|
2981
|
-
cycleId: parentRun.chainCycle,
|
|
2982
|
-
expectCursor: parentRun.chainIndex,
|
|
2983
|
-
expectTail: runId,
|
|
2984
|
-
entryDigest: parentRun.entryDigest,
|
|
2985
|
-
authMode: parentRun.authMode,
|
|
2986
|
-
repairModel: entry.profile.repairModel === "inherit" ? entry.profile.model : entry.profile.repairModel,
|
|
2987
|
-
approved: { chainDigest: chainDigestOf(chain), profile: mirror },
|
|
2988
|
-
run: {
|
|
2989
|
-
taskRef: request.taskRef,
|
|
2990
|
-
leaseId: request.leaseId ?? "unclaimed",
|
|
2991
|
-
runner: request.runner,
|
|
2992
|
-
branch: request.branch,
|
|
2993
|
-
worktree,
|
|
2994
|
-
provider: repairProvider,
|
|
2995
|
-
model: repairModel,
|
|
2996
|
-
...(resumableRepair && sessionId !== undefined ? { sessionId } : {}),
|
|
2997
|
-
},
|
|
2998
|
-
route: repairStamp,
|
|
2999
|
-
}, clock());
|
|
3000
|
-
if (!admitted.ok)
|
|
3001
|
-
return { ok: false, problems: [{ reason: "route-mismatch", message: `the repair cannot run: ${admitted.problem}` }] };
|
|
3002
|
-
repairRun = admitted.runId;
|
|
3003
|
-
}
|
|
3004
|
-
else {
|
|
2797
|
+
{
|
|
3005
2798
|
// THE REPAIR ADMISSION (atomic authority closure): the turn mends
|
|
3006
2799
|
// exactly this live build attempt under its own runner and lease —
|
|
3007
2800
|
// proved in the store, value-shaped, zero rows on refusal.
|