toolroll 0.9.76 → 0.9.77
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 +192 -0
- package/README.md +11 -4
- package/THIRD_PARTY_NOTICES.md +5 -5
- package/dist/agent-fence.js +28 -8
- package/dist/assignment.js +3 -0
- package/dist/browser/THIRD_PARTY_NOTICES.txt +5 -5
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -31
- package/dist/builder.d.ts +14 -0
- package/dist/builder.js +140 -41
- package/dist/chat-decide.js +4 -0
- package/dist/check-failure.d.ts +92 -0
- package/dist/check-failure.js +267 -0
- package/dist/check-progress.d.ts +28 -1
- package/dist/check-progress.js +105 -6
- package/dist/check-report.d.ts +52 -6
- package/dist/check-report.js +193 -8
- package/dist/checkout-cleanup.js +2 -2
- package/dist/cli.js +8 -5
- package/dist/coding-handoff.d.ts +1 -0
- package/dist/coding-handoff.js +20 -11
- package/dist/commit-extract.d.ts +6 -0
- package/dist/commit-extract.js +63 -8
- package/dist/console-chrome.d.ts +41 -0
- package/dist/console-chrome.js +166 -0
- package/dist/contracts/release-check.d.ts +12 -0
- package/dist/contracts/release-check.js +17 -0
- package/dist/contracts/verification-receipt.d.ts +20 -2
- package/dist/contracts/verification-receipt.js +15 -3
- package/dist/contracts/verification-view.d.ts +8 -0
- package/dist/controller-supervisor.d.ts +8 -0
- package/dist/controller-supervisor.js +33 -1
- package/dist/demo.js +3 -2
- package/dist/deploy-gate.js +5 -0
- package/dist/dispose.d.ts +3 -0
- package/dist/dispose.js +6 -3
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +10 -2
- package/dist/envelope.js +2 -1
- package/dist/evidence.js +4 -1
- package/dist/exec.d.ts +7 -0
- package/dist/exec.js +7 -1
- package/dist/git-floor.d.ts +16 -0
- package/dist/git-floor.js +36 -0
- package/dist/grant.js +5 -1
- package/dist/guides.js +4 -3
- package/dist/operate.d.ts +13 -0
- package/dist/operate.js +773 -319
- package/dist/prepared-evidence.d.ts +2 -1
- package/dist/prepared-evidence.js +8 -3
- package/dist/project-knowledge.js +6 -2
- package/dist/project-learning.js +2 -2
- package/dist/proof.js +6 -0
- package/dist/read-executor-worker.js +8 -4
- package/dist/read-executor.d.ts +18 -1
- package/dist/read-executor.js +8 -0
- package/dist/release-adapters.d.ts +48 -1
- package/dist/release-adapters.js +343 -10
- package/dist/release-authority.js +6 -2
- package/dist/release-auto.d.ts +38 -7
- package/dist/release-auto.js +131 -15
- package/dist/release-card.d.ts +15 -3
- package/dist/release-card.js +25 -9
- package/dist/release-ci-hold.d.ts +41 -0
- package/dist/release-ci-hold.js +53 -0
- package/dist/release-cli.js +21 -12
- package/dist/release-coverage.js +5 -2
- package/dist/release-recheck.d.ts +1 -1
- package/dist/release-recheck.js +6 -1
- package/dist/release.d.ts +108 -11
- package/dist/release.js +294 -39
- package/dist/review-context.js +8 -6
- package/dist/runner.js +2 -1
- package/dist/sandbox-runtime.d.ts +8 -0
- package/dist/sandbox-runtime.js +14 -2
- package/dist/scope.js +7 -1
- package/dist/secret-mint.d.ts +1 -0
- package/dist/secret-mint.js +14 -0
- package/dist/serve.js +101 -51
- package/dist/server/chat.js +5 -5
- package/dist/server/render-tasks.d.ts +2 -0
- package/dist/server/render-tasks.js +2 -1
- package/dist/server/request-context.d.ts +6 -0
- package/dist/server/runtime.d.ts +2 -0
- package/dist/server/task-views.js +7 -2
- package/dist/setup-guide.d.ts +2 -2
- package/dist/storage.d.ts +5 -0
- package/dist/storage.js +28 -1
- package/dist/store.d.ts +10 -0
- package/dist/store.js +25 -1
- package/dist/task-base.d.ts +4 -0
- package/dist/task-base.js +22 -2
- package/dist/teardown-git.d.ts +52 -3
- package/dist/teardown-git.js +143 -31
- package/dist/test-infra.js +1 -1
- package/dist/up-processes.d.ts +137 -0
- package/dist/up-processes.js +165 -0
- package/dist/up-role-child.d.ts +1 -0
- package/dist/up-role-child.js +77 -0
- package/dist/verification-evidence.d.ts +1 -0
- package/dist/verification-evidence.js +13 -1
- package/package.json +2 -1
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The console's shared chrome (the sidebar's project states, the Tasks count, the crew list): computed at most once
|
|
3
|
+
* per workspace revision for one reader's exact access, and readable on a read worker (read-executor.ts) as well as on
|
|
4
|
+
* the request thread. Cache metadata only: nothing here grants access. Every entry's key carries every authorization
|
|
5
|
+
* input its computation reads (the reader, their admitted projects, whether they see unplaced work, the projects their
|
|
6
|
+
* sidebar may list), so a value read for one scope is never served to another.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
import { NO_COUNTS, projectStates, STATE_WORDS } from "./agent-state.js";
|
|
10
|
+
import { boundProjectSummary } from "./browser-workspace.js";
|
|
11
|
+
import { WORK_INDEX_MAX_LIMIT, workIndexPage } from "./work-index.js";
|
|
12
|
+
/** Tasks the rail lists under a project; the rest are a count that links to the project's tasks. */
|
|
13
|
+
const RAIL_TASKS = 6;
|
|
14
|
+
/** Asks a project names for the chime (its count is complete beyond them). */
|
|
15
|
+
const ASKS_PER_PROJECT = 100;
|
|
16
|
+
/** The sidebar's projects with their states (agent-state.ts) for one reader: the page's first read and the summary
|
|
17
|
+
* room's snapshots are this one function, so the two never disagree. Never marks anything seen. Work with no project
|
|
18
|
+
* yet is its own entry when this reader may see it and something counts there. */
|
|
19
|
+
export function projectSummaryOf(store, now, access, actor, projects, evidenceRoot) {
|
|
20
|
+
const states = new Map(projectStates(store, access, actor, now, evidenceRoot, projects.map(one => one.path)).map(one => [one.repo, one]));
|
|
21
|
+
// A row that needs a person or failed says what the Tasks list says it needs (its own next step); a row at work says
|
|
22
|
+
// what its agent did last; a finished one says Done.
|
|
23
|
+
const asking = [...states.values()].flatMap(one => one.families.slice(0, RAIL_TASKS)).filter(one => one.state === "needs-you" || one.state === "failed").map(one => one.rootId);
|
|
24
|
+
const needWords = new Map();
|
|
25
|
+
if (asking.length > 0) {
|
|
26
|
+
try {
|
|
27
|
+
// Every row the rail may list, in pages the index allows.
|
|
28
|
+
for (let at = 0; at < asking.length; at += WORK_INDEX_MAX_LIMIT)
|
|
29
|
+
for (const item of workIndexPage(store, now, access, { roots: asking.slice(at, at + WORK_INDEX_MAX_LIMIT), limit: WORK_INDEX_MAX_LIMIT, ...(evidenceRoot === undefined ? {} : { root: evidenceRoot }) }).items)
|
|
30
|
+
needWords.set(item.rootId, item.status.detail !== "" ? item.status.detail : item.status.label);
|
|
31
|
+
}
|
|
32
|
+
catch { /* the rows keep their state's words */ }
|
|
33
|
+
}
|
|
34
|
+
// The chime's subjects: an opaque key per obligation and version, scoped to this reader (no words, no ids in it).
|
|
35
|
+
const keyOf = (...parts) => createHash("sha256").update([actor, ...parts].join("\u0000")).digest("base64url").slice(0, 22);
|
|
36
|
+
// Newest first, so a new ask always has a key however many older ones wait (those were heard when they were new).
|
|
37
|
+
const asksOf = (state) => [
|
|
38
|
+
...(state?.families ?? []).flatMap(one => one.ask === undefined ? [] : [{ at: one.ask.at, ask: { key: keyOf("task", one.rootId, one.ask.version), task: one.rootId, run: one.ask.run, at: one.ask.at } }]),
|
|
39
|
+
...(state?.flowAsks ?? []).map(one => ({ at: one.at, ask: { key: keyOf("flow", one.card, one.stage, one.entry), task: null, run: null, at: one.at } })),
|
|
40
|
+
].sort((a, b) => b.at.localeCompare(a.at)).slice(0, ASKS_PER_PROJECT).map(one => one.ask);
|
|
41
|
+
const railOf = (state) => {
|
|
42
|
+
const families = state?.families ?? [];
|
|
43
|
+
const tasks = families.slice(0, RAIL_TASKS).flatMap((one) => {
|
|
44
|
+
const task = store.getTask(one.rootId);
|
|
45
|
+
const line = one.state === "needs-you" || one.state === "failed" ? needWords.get(one.rootId) ?? null : one.line;
|
|
46
|
+
return task === null ? [] : [{ id: one.rootId, title: task.title, href: `/t/${encodeURIComponent(one.rootId)}`, state: one.state, line: line ?? STATE_WORDS[one.state] }];
|
|
47
|
+
});
|
|
48
|
+
return { tasks, more: Math.max(0, families.length - RAIL_TASKS), asks: asksOf(state) };
|
|
49
|
+
};
|
|
50
|
+
const rows = projects.map(one => {
|
|
51
|
+
const state = states.get(one.path);
|
|
52
|
+
return { path: one.path, name: one.name, href: `/work?project=${encodeURIComponent(one.path)}`, state: state?.state ?? "idle", counts: state?.counts ?? { ...NO_COUNTS }, ...railOf(state) };
|
|
53
|
+
});
|
|
54
|
+
const unplaced = states.get(null);
|
|
55
|
+
if (unplaced !== undefined && unplaced.state !== "idle")
|
|
56
|
+
rows.push({ path: null, name: "No project", href: "/work", state: unplaced.state, counts: unplaced.counts, ...railOf(unplaced) });
|
|
57
|
+
return boundProjectSummary(rows);
|
|
58
|
+
}
|
|
59
|
+
/** One access, canonically and in full (every field it has, so a field added later keys too): the same reader and
|
|
60
|
+
* scope always spell the same key, whatever order its projects came in. */
|
|
61
|
+
export function accessKey(access) {
|
|
62
|
+
const record = access;
|
|
63
|
+
return JSON.stringify(Object.keys(record).sort().map(name => {
|
|
64
|
+
const value = record[name];
|
|
65
|
+
return [name, Array.isArray(value) ? [...new Set(value.map(String))].sort() : value === undefined ? null : value];
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Values read from the database, each kept while the workspace revision (workspace-revision.ts: every meaningful
|
|
70
|
+
* commit, from any process, moves it) is the one it was read at, and never past the revision's own clock boundary
|
|
71
|
+
* (a lease or liveness window ending) or `maxAgeMs`, whichever is first — time alone (an agent going quiet) shows
|
|
72
|
+
* within that bound. A value is stored under the revision read before it was computed, so a commit that lands while it
|
|
73
|
+
* is computed only makes it miss sooner. Callers must not change a value they were handed.
|
|
74
|
+
*/
|
|
75
|
+
export class RevisionCache {
|
|
76
|
+
#entries = new Map();
|
|
77
|
+
#revision;
|
|
78
|
+
#clock;
|
|
79
|
+
#maxEntries;
|
|
80
|
+
#maxAgeMs;
|
|
81
|
+
constructor(revision, clock, options = {}) {
|
|
82
|
+
this.#revision = revision;
|
|
83
|
+
this.#clock = clock;
|
|
84
|
+
this.#maxEntries = options.maxEntries ?? 128;
|
|
85
|
+
this.#maxAgeMs = options.maxAgeMs ?? 30_000;
|
|
86
|
+
}
|
|
87
|
+
/** The value for `key` at the current revision, or undefined. */
|
|
88
|
+
peek(key) {
|
|
89
|
+
const entry = this.#entries.get(key);
|
|
90
|
+
if (entry === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
let revision;
|
|
93
|
+
try {
|
|
94
|
+
revision = this.#revision.current();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
if (entry.revision !== revision || this.#clock().getTime() >= entry.expiresAt) {
|
|
100
|
+
this.#entries.delete(key);
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return entry.value;
|
|
104
|
+
}
|
|
105
|
+
/** The cached value, or `compute()` now (stored for the next reader). Errors are never cached. */
|
|
106
|
+
read(key, compute) {
|
|
107
|
+
const hit = this.peek(key);
|
|
108
|
+
if (hit !== undefined)
|
|
109
|
+
return hit;
|
|
110
|
+
const started = this.#started();
|
|
111
|
+
const value = compute();
|
|
112
|
+
if (started !== null)
|
|
113
|
+
this.#store(key, started, value);
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
/** The cached value, or one read asynchronously (a read worker) and stored. A failure answers undefined and leaves
|
|
117
|
+
* the key empty, so the request thread computes it as before. */
|
|
118
|
+
async fill(key, compute) {
|
|
119
|
+
const hit = this.peek(key);
|
|
120
|
+
if (hit !== undefined)
|
|
121
|
+
return hit;
|
|
122
|
+
const started = this.#started();
|
|
123
|
+
if (started === null)
|
|
124
|
+
return undefined;
|
|
125
|
+
let value;
|
|
126
|
+
try {
|
|
127
|
+
value = await compute();
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
// A worker can finish after an approval changed or a lease expired. Its caller must not pin that old answer
|
|
133
|
+
// into a request even though the shared cache would reject it on the next lookup.
|
|
134
|
+
try {
|
|
135
|
+
if (this.#revision.current() !== started.revision || this.#clock().getTime() >= started.expiresAt)
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
this.#store(key, started, value);
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
#started() {
|
|
145
|
+
try {
|
|
146
|
+
const now = this.#clock();
|
|
147
|
+
return { revision: this.#revision.current(), expiresAt: Math.min(this.#revision.expiresAt(now), now.getTime() + this.#maxAgeMs) };
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
#store(key, started, value) {
|
|
154
|
+
this.#entries.delete(key);
|
|
155
|
+
if (this.#entries.size >= this.#maxEntries)
|
|
156
|
+
this.#entries.delete(this.#entries.keys().next().value);
|
|
157
|
+
this.#entries.set(key, { ...started, value });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** The keys the console's chrome reads are kept under (serve.ts and its read-worker prefetch spell them the same). */
|
|
161
|
+
export const countsKey = (access) => `counts\u0000${accessKey(access)}`;
|
|
162
|
+
export const summaryKey = (actor, access, projects) => `summary\u0000${actor}\u0000${accessKey(access)}\u0000${JSON.stringify(projects.map(one => [one.path, one.name]))}`;
|
|
163
|
+
export const indexKey = (access, options) => {
|
|
164
|
+
const record = options;
|
|
165
|
+
return `index\u0000${accessKey(access)}\u0000${JSON.stringify(Object.keys(record).sort().map(name => [name, record[name] ?? null]))}`;
|
|
166
|
+
};
|
|
@@ -43,6 +43,10 @@ export declare const checkReportSchema: z.ZodObject<{
|
|
|
43
43
|
ran: z.ZodBoolean;
|
|
44
44
|
code: z.ZodNullable<z.ZodInt>;
|
|
45
45
|
timedOut: z.ZodBoolean;
|
|
46
|
+
startedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
47
|
+
signal: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
48
|
+
stopped: z.ZodOptional<z.ZodBoolean>;
|
|
49
|
+
spawnFailed: z.ZodOptional<z.ZodBoolean>;
|
|
46
50
|
}, z.core.$strict>>;
|
|
47
51
|
units: z.ZodNullable<z.ZodObject<{
|
|
48
52
|
reusedFromCI: z.ZodBoolean;
|
|
@@ -81,6 +85,7 @@ export declare const checkReportSchema: z.ZodObject<{
|
|
|
81
85
|
scripted: "scripted";
|
|
82
86
|
}>;
|
|
83
87
|
finished: z.ZodBoolean;
|
|
88
|
+
inventoried: z.ZodOptional<z.ZodBoolean>;
|
|
84
89
|
groups: z.ZodArray<z.ZodObject<{
|
|
85
90
|
group: z.ZodString;
|
|
86
91
|
state: z.ZodEnum<{
|
|
@@ -102,6 +107,7 @@ export declare const checkReportSchema: z.ZodObject<{
|
|
|
102
107
|
}, z.core.$strict>>;
|
|
103
108
|
upgrades: z.ZodNullable<z.ZodObject<{
|
|
104
109
|
finished: z.ZodBoolean;
|
|
110
|
+
inventoried: z.ZodOptional<z.ZodBoolean>;
|
|
105
111
|
versions: z.ZodArray<z.ZodObject<{
|
|
106
112
|
version: z.ZodString;
|
|
107
113
|
state: z.ZodEnum<{
|
|
@@ -130,6 +136,8 @@ export declare const checkInventorySchema: z.ZodObject<{
|
|
|
130
136
|
unit: "unit";
|
|
131
137
|
upgrade: "upgrade";
|
|
132
138
|
}>>;
|
|
139
|
+
ciSubstitute: z.ZodOptional<z.ZodBoolean>;
|
|
140
|
+
release: z.ZodOptional<z.ZodBoolean>;
|
|
133
141
|
}, z.core.$strict>;
|
|
134
142
|
export type CheckInventory = z.infer<typeof checkInventorySchema>;
|
|
135
143
|
/** The sealed inventory artifact: what was sealed before the run, and what the run declared. */
|
|
@@ -148,6 +156,8 @@ export declare const sealedInventorySchema: z.ZodObject<{
|
|
|
148
156
|
unit: "unit";
|
|
149
157
|
upgrade: "upgrade";
|
|
150
158
|
}>>;
|
|
159
|
+
ciSubstitute: z.ZodOptional<z.ZodBoolean>;
|
|
160
|
+
release: z.ZodOptional<z.ZodBoolean>;
|
|
151
161
|
}, z.core.$strict>;
|
|
152
162
|
declared: z.ZodObject<{
|
|
153
163
|
groups: z.ZodArray<z.ZodObject<{
|
|
@@ -229,6 +239,8 @@ export type CheckHandshake = {
|
|
|
229
239
|
mode: "full" | "targeted";
|
|
230
240
|
plan: TargetedPlan | null;
|
|
231
241
|
watch: string[];
|
|
242
|
+
owed?: CheckPart[];
|
|
243
|
+
ciSubstitute?: boolean;
|
|
232
244
|
};
|
|
233
245
|
/** A unit test: its file and full name. */
|
|
234
246
|
export declare const unitIdentity: (file: string, testName: string) => string;
|
|
@@ -38,6 +38,12 @@ export const checkReportSchema = versioned(1, {
|
|
|
38
38
|
ran: z.boolean(),
|
|
39
39
|
code: z.int().min(0).max(255).nullable(),
|
|
40
40
|
timedOut: z.boolean(),
|
|
41
|
+
/** When it started, the signal that ended it, whether the check stopped it (another part's failure or the check's
|
|
42
|
+
* own limit) and whether it could start at all — each apart from its code. Absent from an older orchestrator. */
|
|
43
|
+
startedAt: z.string().max(40).nullable().optional(),
|
|
44
|
+
signal: z.string().max(20).nullable().optional(),
|
|
45
|
+
stopped: z.boolean().optional(),
|
|
46
|
+
spawnFailed: z.boolean().optional(),
|
|
41
47
|
})).max(CHECK_PARTS.length),
|
|
42
48
|
/** The unit suite, from the Vitest main process's fd-3 lines: every file's outcome, and the identities that failed,
|
|
43
49
|
* were skipped, passed on a retry or were asked about. Null when the unit part didn't run. */
|
|
@@ -65,6 +71,9 @@ export const checkReportSchema = versioned(1, {
|
|
|
65
71
|
script: z.enum(JOURNEY_SCRIPTS),
|
|
66
72
|
kind: z.enum(["scripted", "real"]),
|
|
67
73
|
finished: z.boolean(),
|
|
74
|
+
/** The runner listed its groups before any started (an empty list is an explicit "none of this kind"); absent or
|
|
75
|
+
* false, the run has no inventory and nothing it lacks can be told from nothing it owed. */
|
|
76
|
+
inventoried: z.boolean().optional(),
|
|
68
77
|
groups: z.array(z.strictObject({
|
|
69
78
|
group: groupName,
|
|
70
79
|
state: z.enum(["passed", "failed", "not-run"]),
|
|
@@ -77,6 +86,8 @@ export const checkReportSchema = versioned(1, {
|
|
|
77
86
|
})).max(4),
|
|
78
87
|
upgrades: z.strictObject({
|
|
79
88
|
finished: z.boolean(),
|
|
89
|
+
/** The upgrade path listed its versions before any ran. */
|
|
90
|
+
inventoried: z.boolean().optional(),
|
|
80
91
|
versions: z.array(z.strictObject({ version: versionName, state: z.enum(["passed", "failed", "not-run"]) })).max(500),
|
|
81
92
|
}).nullable(),
|
|
82
93
|
});
|
|
@@ -88,7 +99,13 @@ export function readCheckReport(input) {
|
|
|
88
99
|
* sealed beside these after it ran. */
|
|
89
100
|
export const checkInventorySchema = versioned(1, {
|
|
90
101
|
units: z.array(repoPath).max(20_000),
|
|
102
|
+
/** The parts the check owes. For a full check the plane decides them before it runs (check-report.ts
|
|
103
|
+
* fullObligationsOf) and they ride on the handshake; every one needs an outcome. */
|
|
91
104
|
parts: z.array(z.enum(CHECK_PARTS)).max(CHECK_PARTS.length),
|
|
105
|
+
/** Whether CI's run of the exact commit may stand in for the local unit suite. Absent is no. */
|
|
106
|
+
ciSubstitute: z.boolean().optional(),
|
|
107
|
+
/** Whether the plane prepared it as a release's check (sealed with the attempt; absent on older inventories). */
|
|
108
|
+
release: z.boolean().optional(),
|
|
92
109
|
});
|
|
93
110
|
/** The sealed inventory artifact: what was sealed before the run, and what the run declared. */
|
|
94
111
|
export const sealedInventorySchema = versioned(1, {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { z } from "zod";
|
|
15
15
|
import { type ContractResult } from "./contract.js";
|
|
16
|
-
export declare const VERIFICATION_FAILURES: readonly ["spawn-failed", "timed-out", "dependency-missing", "setup-stale", "setup-failed", "own-install-failed", "tracked-files-changed", "setup-changed-files", "checkout-moved", "cleanliness-unavailable", "dependency-still-missing", "retry-spawn-failed", "retry-timed-out", "custody-lost", "coverage-incomplete"];
|
|
16
|
+
export declare const VERIFICATION_FAILURES: readonly ["spawn-failed", "timed-out", "dependency-missing", "setup-stale", "setup-failed", "own-install-failed", "tracked-files-changed", "setup-changed-files", "checkout-moved", "cleanliness-unavailable", "dependency-still-missing", "retry-spawn-failed", "retry-timed-out", "custody-lost", "coverage-incomplete", "no-result", "stopped-by-toolroll", "baseline-unverifiable"];
|
|
17
17
|
/**
|
|
18
18
|
* What the repository's approved verification command did: not configured; configured but the run could not attempt
|
|
19
19
|
* it (`attemptFailed`, which downgrades to `short` rather than `refuted`: "we could not check" is not "the claim is
|
|
@@ -26,12 +26,14 @@ export declare const verifyCommandFactsSchema: z.ZodUnion<readonly [z.ZodObject<
|
|
|
26
26
|
ran: z.ZodLiteral<false>;
|
|
27
27
|
attemptFailed: z.ZodLiteral<true>;
|
|
28
28
|
failure: z.ZodOptional<z.ZodEnum<{
|
|
29
|
+
"baseline-unverifiable": "baseline-unverifiable";
|
|
29
30
|
"checkout-moved": "checkout-moved";
|
|
30
31
|
"cleanliness-unavailable": "cleanliness-unavailable";
|
|
31
32
|
"coverage-incomplete": "coverage-incomplete";
|
|
32
33
|
"custody-lost": "custody-lost";
|
|
33
34
|
"dependency-missing": "dependency-missing";
|
|
34
35
|
"dependency-still-missing": "dependency-still-missing";
|
|
36
|
+
"no-result": "no-result";
|
|
35
37
|
"own-install-failed": "own-install-failed";
|
|
36
38
|
"retry-spawn-failed": "retry-spawn-failed";
|
|
37
39
|
"retry-timed-out": "retry-timed-out";
|
|
@@ -39,6 +41,7 @@ export declare const verifyCommandFactsSchema: z.ZodUnion<readonly [z.ZodObject<
|
|
|
39
41
|
"setup-failed": "setup-failed";
|
|
40
42
|
"setup-stale": "setup-stale";
|
|
41
43
|
"spawn-failed": "spawn-failed";
|
|
44
|
+
"stopped-by-toolroll": "stopped-by-toolroll";
|
|
42
45
|
"timed-out": "timed-out";
|
|
43
46
|
"tracked-files-changed": "tracked-files-changed";
|
|
44
47
|
}>>;
|
|
@@ -47,6 +50,11 @@ export declare const verifyCommandFactsSchema: z.ZodUnion<readonly [z.ZodObject<
|
|
|
47
50
|
ran: z.ZodLiteral<true>;
|
|
48
51
|
exitCode: z.ZodInt;
|
|
49
52
|
setupReplayed: z.ZodOptional<z.ZodLiteral<true>>;
|
|
53
|
+
firstAttempt: z.ZodOptional<z.ZodObject<{
|
|
54
|
+
class: z.ZodLiteral<"launch">;
|
|
55
|
+
program: z.ZodString;
|
|
56
|
+
exitCode: z.ZodInt;
|
|
57
|
+
}, z.core.$strict>>;
|
|
50
58
|
}, z.core.$strict>]>;
|
|
51
59
|
export type VerifyCommandFacts = z.infer<typeof verifyCommandFactsSchema>;
|
|
52
60
|
/** The approved verification grant the gate ran, exactly as the store held it. */
|
|
@@ -76,7 +84,7 @@ export declare const receiptLogSchema: z.ZodObject<{
|
|
|
76
84
|
}>>;
|
|
77
85
|
}, z.core.$strict>;
|
|
78
86
|
export declare const RECEIPT_VERSION = 3;
|
|
79
|
-
export declare const RECEIPT_COVERAGES: readonly ["full", "composed", "partial", "identity-removed"];
|
|
87
|
+
export declare const RECEIPT_COVERAGES: readonly ["full", "composed", "partial", "identity-removed", "no-result"];
|
|
80
88
|
export type ReceiptCoverage = typeof RECEIPT_COVERAGES[number];
|
|
81
89
|
/** The receipt as read: a reused gate names its source run and digest; a direct one names neither. */
|
|
82
90
|
export declare const verificationReceiptSchema: z.ZodObject<{
|
|
@@ -109,12 +117,14 @@ export declare const verificationReceiptSchema: z.ZodObject<{
|
|
|
109
117
|
ran: z.ZodLiteral<false>;
|
|
110
118
|
attemptFailed: z.ZodLiteral<true>;
|
|
111
119
|
failure: z.ZodOptional<z.ZodEnum<{
|
|
120
|
+
"baseline-unverifiable": "baseline-unverifiable";
|
|
112
121
|
"checkout-moved": "checkout-moved";
|
|
113
122
|
"cleanliness-unavailable": "cleanliness-unavailable";
|
|
114
123
|
"coverage-incomplete": "coverage-incomplete";
|
|
115
124
|
"custody-lost": "custody-lost";
|
|
116
125
|
"dependency-missing": "dependency-missing";
|
|
117
126
|
"dependency-still-missing": "dependency-still-missing";
|
|
127
|
+
"no-result": "no-result";
|
|
118
128
|
"own-install-failed": "own-install-failed";
|
|
119
129
|
"retry-spawn-failed": "retry-spawn-failed";
|
|
120
130
|
"retry-timed-out": "retry-timed-out";
|
|
@@ -122,6 +132,7 @@ export declare const verificationReceiptSchema: z.ZodObject<{
|
|
|
122
132
|
"setup-failed": "setup-failed";
|
|
123
133
|
"setup-stale": "setup-stale";
|
|
124
134
|
"spawn-failed": "spawn-failed";
|
|
135
|
+
"stopped-by-toolroll": "stopped-by-toolroll";
|
|
125
136
|
"timed-out": "timed-out";
|
|
126
137
|
"tracked-files-changed": "tracked-files-changed";
|
|
127
138
|
}>>;
|
|
@@ -130,6 +141,11 @@ export declare const verificationReceiptSchema: z.ZodObject<{
|
|
|
130
141
|
ran: z.ZodLiteral<true>;
|
|
131
142
|
exitCode: z.ZodInt;
|
|
132
143
|
setupReplayed: z.ZodOptional<z.ZodLiteral<true>>;
|
|
144
|
+
firstAttempt: z.ZodOptional<z.ZodObject<{
|
|
145
|
+
class: z.ZodLiteral<"launch">;
|
|
146
|
+
program: z.ZodString;
|
|
147
|
+
exitCode: z.ZodInt;
|
|
148
|
+
}, z.core.$strict>>;
|
|
133
149
|
}, z.core.$strict>]>;
|
|
134
150
|
log: z.ZodObject<{
|
|
135
151
|
artifactId: z.ZodInt;
|
|
@@ -147,6 +163,7 @@ export declare const verificationReceiptSchema: z.ZodObject<{
|
|
|
147
163
|
composed: "composed";
|
|
148
164
|
full: "full";
|
|
149
165
|
"identity-removed": "identity-removed";
|
|
166
|
+
"no-result": "no-result";
|
|
150
167
|
partial: "partial";
|
|
151
168
|
}>>;
|
|
152
169
|
check: z.ZodOptional<z.ZodObject<{
|
|
@@ -157,6 +174,7 @@ export declare const verificationReceiptSchema: z.ZodObject<{
|
|
|
157
174
|
report: z.ZodNullable<z.ZodString>;
|
|
158
175
|
inventory: z.ZodNullable<z.ZodString>;
|
|
159
176
|
exitCode: z.ZodOptional<z.ZodNullable<z.ZodInt>>;
|
|
177
|
+
retried: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
|
|
160
178
|
}, z.core.$strict>>;
|
|
161
179
|
composed: z.ZodOptional<z.ZodObject<{
|
|
162
180
|
base: z.ZodInt;
|
|
@@ -30,6 +30,12 @@ export const VERIFICATION_FAILURES = [
|
|
|
30
30
|
"custody-lost",
|
|
31
31
|
// A release check that didn't cover what it owed, or lost a required test (release-once.md): never a pass.
|
|
32
32
|
"coverage-incomplete",
|
|
33
|
+
// A Full release check with no complete, valid result (check-report.ts fullCheckProblem): an error, never a pass.
|
|
34
|
+
"no-result",
|
|
35
|
+
// Toolroll's own shutdown stopped the check (its hard stop): it never finished.
|
|
36
|
+
"stopped-by-toolroll",
|
|
37
|
+
// A Full check whose recorded last release couldn't be verified: what it owed is unknown, so it never ran.
|
|
38
|
+
"baseline-unverifiable",
|
|
33
39
|
];
|
|
34
40
|
/**
|
|
35
41
|
* What the repository's approved verification command did: not configured; configured but the run could not attempt
|
|
@@ -39,7 +45,10 @@ export const VERIFICATION_FAILURES = [
|
|
|
39
45
|
export const verifyCommandFactsSchema = z.union([
|
|
40
46
|
z.strictObject({ configured: z.literal(false) }),
|
|
41
47
|
z.strictObject({ configured: z.literal(true), ran: z.literal(false), attemptFailed: z.literal(true), failure: z.enum(VERIFICATION_FAILURES).optional() }),
|
|
42
|
-
z.strictObject({ configured: z.literal(true), ran: z.literal(true), exitCode: z.int().min(0).max(255), setupReplayed: z.literal(true).optional()
|
|
48
|
+
z.strictObject({ configured: z.literal(true), ran: z.literal(true), exitCode: z.int().min(0).max(255), setupReplayed: z.literal(true).optional(),
|
|
49
|
+
// The first attempt of a check rerun after the approved setup, classified on its own (eval-harness review 2,
|
|
50
|
+
// finding 5): only a launch failure — its program missing — is ever replayed.
|
|
51
|
+
firstAttempt: z.strictObject({ class: z.literal("launch"), program: z.string().min(1).max(200), exitCode: z.int().min(0).max(255) }).optional() }),
|
|
43
52
|
]);
|
|
44
53
|
/** The approved verification grant the gate ran, exactly as the store held it. */
|
|
45
54
|
export const receiptCommandSchema = z.strictObject({
|
|
@@ -65,7 +74,7 @@ export const receiptLogSchema = z.strictObject({
|
|
|
65
74
|
captureStatus: z.enum(["ok", "failed"]).nullable(),
|
|
66
75
|
});
|
|
67
76
|
export const RECEIPT_VERSION = 3;
|
|
68
|
-
export const RECEIPT_COVERAGES = ["full", "composed", "partial", "identity-removed"];
|
|
77
|
+
export const RECEIPT_COVERAGES = ["full", "composed", "partial", "identity-removed", "no-result"];
|
|
69
78
|
const receiptFields = {
|
|
70
79
|
run: z.int(),
|
|
71
80
|
head: z.string(),
|
|
@@ -85,7 +94,10 @@ export const verificationReceiptSchema = versioned(RECEIPT_VERSION, {
|
|
|
85
94
|
/** Version 3: the attempt sealed before the check ran, and its sealed report (null: it gave none). */
|
|
86
95
|
check: z.strictObject({ attempt: z.enum(["full", "targeted"]), report: z.string().nullable(), inventory: z.string().nullable(),
|
|
87
96
|
/** The command's own exit, kept here when `result` reads a failed attempt because the coverage fell short. */
|
|
88
|
-
exitCode: z.int().min(0).max(255).nullable().optional()
|
|
97
|
+
exitCode: z.int().min(0).max(255).nullable().optional(),
|
|
98
|
+
/** What passed only on a retry inside the check (check-failure.ts retriedInside), sealed here from its report: null
|
|
99
|
+
* when it gave no valid report, so nobody can tell (unknown, never "no retries"). Absent on older receipts. */
|
|
100
|
+
retried: z.array(z.string().min(1).max(800)).max(6000).nullable().optional() }).optional(),
|
|
89
101
|
/** Version 3, composed: the full check A and every link the reader proves again. */
|
|
90
102
|
composed: z.strictObject({
|
|
91
103
|
base: z.int().min(1), baseReceipt: z.string(), baseReport: z.string(), authority: z.int().min(1), carries: z.array(z.int()),
|
|
@@ -37,12 +37,14 @@ export declare const legacyGateViewSchema: z.ZodObject<{
|
|
|
37
37
|
ran: z.ZodLiteral<false>;
|
|
38
38
|
attemptFailed: z.ZodLiteral<true>;
|
|
39
39
|
failure: z.ZodOptional<z.ZodEnum<{
|
|
40
|
+
"baseline-unverifiable": "baseline-unverifiable";
|
|
40
41
|
"checkout-moved": "checkout-moved";
|
|
41
42
|
"cleanliness-unavailable": "cleanliness-unavailable";
|
|
42
43
|
"coverage-incomplete": "coverage-incomplete";
|
|
43
44
|
"custody-lost": "custody-lost";
|
|
44
45
|
"dependency-missing": "dependency-missing";
|
|
45
46
|
"dependency-still-missing": "dependency-still-missing";
|
|
47
|
+
"no-result": "no-result";
|
|
46
48
|
"own-install-failed": "own-install-failed";
|
|
47
49
|
"retry-spawn-failed": "retry-spawn-failed";
|
|
48
50
|
"retry-timed-out": "retry-timed-out";
|
|
@@ -50,6 +52,7 @@ export declare const legacyGateViewSchema: z.ZodObject<{
|
|
|
50
52
|
"setup-failed": "setup-failed";
|
|
51
53
|
"setup-stale": "setup-stale";
|
|
52
54
|
"spawn-failed": "spawn-failed";
|
|
55
|
+
"stopped-by-toolroll": "stopped-by-toolroll";
|
|
53
56
|
"timed-out": "timed-out";
|
|
54
57
|
"tracked-files-changed": "tracked-files-changed";
|
|
55
58
|
}>>;
|
|
@@ -58,6 +61,11 @@ export declare const legacyGateViewSchema: z.ZodObject<{
|
|
|
58
61
|
ran: z.ZodLiteral<true>;
|
|
59
62
|
exitCode: z.ZodInt;
|
|
60
63
|
setupReplayed: z.ZodOptional<z.ZodLiteral<true>>;
|
|
64
|
+
firstAttempt: z.ZodOptional<z.ZodObject<{
|
|
65
|
+
class: z.ZodLiteral<"launch">;
|
|
66
|
+
program: z.ZodString;
|
|
67
|
+
exitCode: z.ZodInt;
|
|
68
|
+
}, z.core.$strict>>;
|
|
61
69
|
}, z.core.$strict>]>;
|
|
62
70
|
log: z.ZodObject<{
|
|
63
71
|
artifactId: z.ZodInt;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
1
2
|
export type ControllerState = {
|
|
2
3
|
phase: "starting" | "running" | "backoff" | "stopped";
|
|
3
4
|
generation: number;
|
|
@@ -27,4 +28,11 @@ export declare function superviseController(args: {
|
|
|
27
28
|
maxRetryMs?: number;
|
|
28
29
|
stableMs?: number;
|
|
29
30
|
shutdownMs?: number;
|
|
31
|
+
/** An IPC channel to each controller (fd 3), for a parent that speaks to it (`toolroll up`'s console and worker). */
|
|
32
|
+
ipc?: boolean;
|
|
33
|
+
/** Prefix every line the controller writes, sent to this process's stderr: two controllers' logs stay apart. An
|
|
34
|
+
* empty label sends its lines there unprefixed (a JSON log keeps one object a line). */
|
|
35
|
+
label?: string;
|
|
36
|
+
/** Each spawned controller's handle, before it runs: the caller listens to its messages and may signal it. */
|
|
37
|
+
onSpawn?: (child: ChildProcess, generation: number) => void;
|
|
30
38
|
}): Promise<void>;
|
|
@@ -56,13 +56,23 @@ export async function superviseController(args) {
|
|
|
56
56
|
if (args.signal.aborted)
|
|
57
57
|
break;
|
|
58
58
|
const exited = await new Promise(done => {
|
|
59
|
+
const output = args.label === undefined ? "inherit" : "pipe";
|
|
59
60
|
const owned = spawn(args.file, [...args.argv], {
|
|
60
61
|
...(args.cwd === undefined ? {} : { cwd: args.cwd }),
|
|
61
62
|
...(args.env === undefined ? {} : { env: args.env }),
|
|
62
|
-
stdio: "inherit",
|
|
63
|
+
stdio: args.ipc === true ? ["ignore", output, output, "ipc"] : args.label === undefined ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
63
64
|
windowsHide: true,
|
|
64
65
|
});
|
|
65
66
|
child = owned;
|
|
67
|
+
if (args.label !== undefined)
|
|
68
|
+
for (const stream of [owned.stdout, owned.stderr])
|
|
69
|
+
labelLines(stream, args.label);
|
|
70
|
+
try {
|
|
71
|
+
args.onSpawn?.(owned, generation);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
process.stderr.write("Controller supervisor could not attach to its controller.\n");
|
|
75
|
+
}
|
|
66
76
|
owned.once("spawn", () => {
|
|
67
77
|
emit({ phase: "running", generation, controllerPid: owned.pid ?? null });
|
|
68
78
|
if (args.signal.aborted)
|
|
@@ -102,3 +112,25 @@ export async function superviseController(args) {
|
|
|
102
112
|
emit({ phase: "stopped", generation, controllerPid: null });
|
|
103
113
|
}
|
|
104
114
|
}
|
|
115
|
+
/** Each whole line of a controller's output, prefixed with its label, on this process's stderr. */
|
|
116
|
+
function labelLines(stream, label) {
|
|
117
|
+
if (stream === null)
|
|
118
|
+
return;
|
|
119
|
+
const prefix = label === "" ? "" : `${label} `;
|
|
120
|
+
let pending = "";
|
|
121
|
+
stream.setEncoding("utf8");
|
|
122
|
+
stream.on("data", (chunk) => {
|
|
123
|
+
const lines = (pending + chunk).split("\n");
|
|
124
|
+
pending = lines.pop() ?? "";
|
|
125
|
+
// Bounded: a controller that never ends a line still reaches the log.
|
|
126
|
+
if (pending.length > 64 * 1024) {
|
|
127
|
+
lines.push(pending);
|
|
128
|
+
pending = "";
|
|
129
|
+
}
|
|
130
|
+
for (const line of lines)
|
|
131
|
+
process.stderr.write(`${prefix}${line}\n`);
|
|
132
|
+
});
|
|
133
|
+
stream.on("end", () => { if (pending !== "")
|
|
134
|
+
process.stderr.write(`${prefix}${pending}\n`); pending = ""; });
|
|
135
|
+
stream.on("error", () => { });
|
|
136
|
+
}
|
package/dist/demo.js
CHANGED
|
@@ -989,11 +989,12 @@ export function makeDemoRepos(root) {
|
|
|
989
989
|
writeFileSync(join(dir, file), content);
|
|
990
990
|
}
|
|
991
991
|
try {
|
|
992
|
-
execFileSync("git", ["init", "-q", "-b", "main"], { cwd: dir, stdio: "ignore" });
|
|
993
|
-
execFileSync("git", ["add", "-A"], { cwd: dir, stdio: "ignore" });
|
|
992
|
+
execFileSync("git", ["init", "-q", "-b", "main"], { cwd: dir, stdio: "ignore", timeout: 30_000 });
|
|
993
|
+
execFileSync("git", ["add", "-A"], { cwd: dir, stdio: "ignore", timeout: 30_000 });
|
|
994
994
|
execFileSync("git", ["-c", "user.email=demo@localhost", "-c", "user.name=demo", "commit", "-q", "-m", "seed"], {
|
|
995
995
|
cwd: dir,
|
|
996
996
|
stdio: "ignore",
|
|
997
|
+
timeout: 30_000,
|
|
997
998
|
});
|
|
998
999
|
}
|
|
999
1000
|
catch {
|
package/dist/deploy-gate.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { assignmentOf } from "./assignment.js";
|
|
9
9
|
import { liveReleaseOf, personApprovalOf, releaseApprovalOf } from "./scope.js";
|
|
10
10
|
import { verificationEvidence } from "./verification-evidence.js";
|
|
11
|
+
import { releaseCiRunningOf } from "./release-ci-hold.js";
|
|
11
12
|
/** Whether run `runId` is a deployable candidate now, read only from the plane's records and this runtime's code. */
|
|
12
13
|
export function deploymentGate(store, input) {
|
|
13
14
|
const refuse = (message) => ({ ok: false, message });
|
|
@@ -27,6 +28,10 @@ export function deploymentGate(store, input) {
|
|
|
27
28
|
return refuse(`Run ${input.runId} is not a built candidate at ${head.slice(0, 7)}.`);
|
|
28
29
|
if (scope === null || scope.approvedDigest !== scope.digest || scope.digest !== run.scopeDigest)
|
|
29
30
|
return refuse("The run's signed scope is no longer the approved one.");
|
|
31
|
+
// A person may approve during CI, including a release without carry terms. Every deployment road must wait
|
|
32
|
+
// for the plane's exact-head observation, not only the release coordinator's polling loop.
|
|
33
|
+
if (releaseCiRunningOf(store, ref.externalId) !== null)
|
|
34
|
+
return refuse("This release still waits for GitHub CI. Resume it with `toolroll release`.");
|
|
30
35
|
// One approval per release (release-once.md): a release's task deploys only while its authority stands for it — the
|
|
31
36
|
// live predicate proves the anchor's person approval (a carried commit's own seal is never a person's). Any other task
|
|
32
37
|
// needs a person's approval of its own.
|
package/dist/dispose.d.ts
CHANGED
package/dist/dispose.js
CHANGED
|
@@ -455,7 +455,10 @@ export function regateTask(store, taskId, now, approval) {
|
|
|
455
455
|
if (scope === null)
|
|
456
456
|
return { ok: false, reason: "no-attempt", message: `${taskId} has no scope` };
|
|
457
457
|
const head = last.headRevision;
|
|
458
|
-
const actor = approval.kind === "operator" ? approval.name : `mode ${approval.name}`;
|
|
458
|
+
const actor = approval.kind === "operator" ? approval.name : approval.kind === "release" ? approval.name : `mode ${approval.name}`;
|
|
459
|
+
if (approval.kind === "release" && ((scope.candidate ?? null) === null || (scope.releaseTerms ?? null) === null)) {
|
|
460
|
+
return { ok: false, reason: "not-approved", message: `${taskId} isn't a release candidate; only a release reruns under its own approval` };
|
|
461
|
+
}
|
|
459
462
|
// A prepared candidate (a release gate, the nightly check) already names the approved commit, and the builder loads
|
|
460
463
|
// exactly its tree on every attempt. The rerun keeps that scope and its approval as they are: nothing is proposed or
|
|
461
464
|
// sealed again, so a regate can never swap the approved commit, its release terms or the person's yes for others
|
|
@@ -481,7 +484,7 @@ export function regateTask(store, taskId, now, approval) {
|
|
|
481
484
|
return { ok: false, reason: why[queued.reason] ?? "not-rejected", message: `${taskId} could not be queued again: ${queued.reason}` };
|
|
482
485
|
}
|
|
483
486
|
}
|
|
484
|
-
store.addRunNote(last.id, "Toolroll", `The check will run again on the approved commit ${candidate.slice(0, 7)}, under the same approval, as a new attempt, ${approval.kind === "operator" ? `on ${approval.name}'s say-so` : "automatically once, because the failure was outside this change"}.`, now);
|
|
487
|
+
store.addRunNote(last.id, "Toolroll", `The check will run again on the approved commit ${candidate.slice(0, 7)}, under the same approval, as a new attempt, ${approval.kind === "operator" ? `on ${approval.name}'s say-so` : approval.kind === "release" ? "because Toolroll confirmed the failure was its own infrastructure, not the code" : "automatically once, because the failure was outside this change"}.`, now);
|
|
485
488
|
return { ok: true, run: last.id, head: candidate };
|
|
486
489
|
});
|
|
487
490
|
}
|
|
@@ -494,7 +497,7 @@ export function regateTask(store, taskId, now, approval) {
|
|
|
494
497
|
let sealed = false;
|
|
495
498
|
if (approval.kind === "operator")
|
|
496
499
|
sealed = approve(store, taskId, approval.name, now, proposed.digest, approval.token).ok;
|
|
497
|
-
else
|
|
500
|
+
else if (approval.kind === "mode")
|
|
498
501
|
sealed = store.sealScopeApproval(taskId, actor, now, {}, { kind: "mode", modeDigest: approval.modeDigest });
|
|
499
502
|
if (!sealed)
|
|
500
503
|
return { ok: false, reason: "not-approved", message: `${taskId}'s rerun scope could not be approved` };
|
package/dist/doctor.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export type DoctorReport = {
|
|
|
24
24
|
version: string;
|
|
25
25
|
problem: string | null;
|
|
26
26
|
};
|
|
27
|
+
/** The installed git against Toolroll's floor (git-floor.ts): null when it is new enough (or unreadable). */
|
|
28
|
+
git?: {
|
|
29
|
+
floor: string;
|
|
30
|
+
problem: string | null;
|
|
31
|
+
};
|
|
27
32
|
};
|
|
28
33
|
export type DoctorDeps = {
|
|
29
34
|
platform?: NodeJS.Platform;
|
|
@@ -35,6 +40,8 @@ export type DoctorDeps = {
|
|
|
35
40
|
procRoot?: string;
|
|
36
41
|
/** The doctor's own environment: the terminal a `toolroll up` would start from. */
|
|
37
42
|
env?: NodeJS.ProcessEnv;
|
|
43
|
+
/** The installed git's floor problem (a test's stand-in for git-floor.ts installedGitFloorProblem). */
|
|
44
|
+
gitFloorProblem?: string | null;
|
|
38
45
|
};
|
|
39
46
|
/** The names among `pairs` that hold a credential: a provider key, a credential-looking name, or a key-shaped value.
|
|
40
47
|
* Values are only tested here and never leave. */
|