toolroll 0.9.71 → 0.9.72
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 +79 -0
- package/dist/agent-fence.d.ts +5 -2
- package/dist/agent-fence.js +5 -2
- package/dist/approval-authority.d.ts +20 -0
- package/dist/approval-authority.js +67 -0
- package/dist/assignment.js +26 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/builder.js +48 -6
- package/dist/chat-decide.js +17 -2
- package/dist/check-access.js +2 -2
- package/dist/check-report.d.ts +109 -0
- package/dist/check-report.js +204 -0
- package/dist/claim.js +1 -1
- package/dist/commit-extract.d.ts +94 -0
- package/dist/commit-extract.js +436 -0
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +1 -0
- package/dist/contracts/flow.d.ts +16 -0
- package/dist/contracts/flow.js +3 -0
- package/dist/contracts/release-check.d.ts +335 -0
- package/dist/contracts/release-check.js +159 -0
- package/dist/contracts/release-terms.d.ts +60 -0
- package/dist/contracts/release-terms.js +70 -0
- package/dist/contracts/verification-receipt.d.ts +42 -8
- package/dist/contracts/verification-receipt.js +24 -7
- package/dist/contracts/verification-view.d.ts +1 -0
- package/dist/deploy-gate.d.ts +17 -0
- package/dist/deploy-gate.js +68 -0
- package/dist/dispatch.js +3 -10
- package/dist/dispose.js +4 -1
- package/dist/evidence-pack.d.ts +1 -1
- package/dist/evidence-pack.js +20 -2
- package/dist/evidence.d.ts +1 -0
- package/dist/evidence.js +2 -1
- package/dist/flow-engine.js +5 -1
- package/dist/flow-pull-request.d.ts +5 -2
- package/dist/flow-pull-request.js +14 -5
- package/dist/flow-schedule.js +25 -6
- package/dist/flows-ui.js +7 -1
- package/dist/invoke.d.ts +17 -6
- package/dist/invoke.js +48 -18
- package/dist/lead-status.js +5 -2
- package/dist/live-terminal.d.ts +10 -6
- package/dist/live-terminal.js +55 -14
- package/dist/nightly-check.d.ts +104 -0
- package/dist/nightly-check.js +362 -0
- package/dist/operate.js +39 -2
- package/dist/person-proof.d.ts +2 -0
- package/dist/person-proof.js +14 -0
- package/dist/plan-auto.js +4 -4
- package/dist/planner-source.d.ts +1 -1
- package/dist/project-delete-ui.js +1 -1
- package/dist/proof.js +2 -0
- package/dist/publish.js +156 -144
- package/dist/pull-request-flow.d.ts +2 -0
- package/dist/pull-request-flow.js +24 -2
- package/dist/release-adapters.d.ts +8 -0
- package/dist/release-adapters.js +212 -15
- package/dist/release-authority.d.ts +112 -0
- package/dist/release-authority.js +126 -0
- package/dist/release-carry.d.ts +233 -0
- package/dist/release-carry.js +645 -0
- package/dist/release-cli.d.ts +3 -0
- package/dist/release-cli.js +124 -5
- package/dist/release-coverage.js +3 -0
- package/dist/release-recheck.d.ts +146 -0
- package/dist/release-recheck.js +657 -0
- package/dist/release-view.d.ts +34 -0
- package/dist/release-view.js +27 -0
- package/dist/release.d.ts +131 -1
- package/dist/release.js +562 -244
- package/dist/remote-main.d.ts +2 -0
- package/dist/remote-main.js +1 -1
- package/dist/result-completion.d.ts +6 -0
- package/dist/result-completion.js +15 -1
- package/dist/result-follow-ups.js +29 -1
- package/dist/sandbox-runtime.d.ts +16 -5
- package/dist/sandbox-runtime.js +19 -6
- package/dist/scope.d.ts +115 -5
- package/dist/scope.js +219 -7
- package/dist/server/render-tasks.d.ts +8 -0
- package/dist/server/render-tasks.js +50 -9
- package/dist/server/route-table.js +1 -0
- package/dist/server/task-views.js +4 -0
- package/dist/server/tasks.js +12 -0
- package/dist/store.d.ts +227 -5
- package/dist/store.js +727 -37
- package/dist/task-outcome-cli.js +7 -3
- package/dist/toolroll-update.js +2 -2
- package/dist/verification-evidence.d.ts +25 -1
- package/dist/verification-evidence.js +57 -2
- package/dist/work-index.js +3 -6
- package/package.json +1 -1
package/dist/release-cli.js
CHANGED
|
@@ -15,7 +15,11 @@ import { checkedEnvelopeJson } from "./contracts/cli.js";
|
|
|
15
15
|
import { RELEASE_LIMITS, runRelease } from "./release.js";
|
|
16
16
|
import { processes, releaseAdapters } from "./release-adapters.js";
|
|
17
17
|
import { releaseGate } from "./release-gate.js";
|
|
18
|
-
import { databasePath } from "./store.js";
|
|
18
|
+
import { databasePath, openStore, revokeRelease } from "./store.js";
|
|
19
|
+
import { carryRefusalWords, proveCarry, setAskEveryReleaseCommit } from "./release-carry.js";
|
|
20
|
+
import { authenticateApprover, machineActing, personProof } from "./scope.js";
|
|
21
|
+
import { createNightlyCheckFlow } from "./nightly-check.js";
|
|
22
|
+
import { parseSchedule } from "./flow-schedule.js";
|
|
19
23
|
export const RELEASE_HELP = `toolroll release <branch> — release Toolroll itself, from a pushed branch with an open pull request
|
|
20
24
|
|
|
21
25
|
Requires an active approver's scope approval (the only human step) and a passing Full check on the exact commit.
|
|
@@ -31,7 +35,24 @@ toolroll release --release-gate <id> [--yes] — lift the update pause a deploym
|
|
|
31
35
|
database, service definition and running service are all still the previous build's. --yes lifts that one pause.
|
|
32
36
|
Any missing or contradicting evidence refuses and leaves the pause in place.
|
|
33
37
|
|
|
38
|
+
toolroll release carry <release> --to <commit> [--request <id>] — carry a person's release approval to a later commit
|
|
39
|
+
|
|
40
|
+
Only tests, docs and the changelog may change after the approval, within the limits, with an identical production
|
|
41
|
+
build. Anything else is refused, naming the commit, path and rule, and that commit needs a person's approval.
|
|
42
|
+
|
|
43
|
+
toolroll release revoke <release> — stop carrying later commits (before the merge starts)
|
|
44
|
+
|
|
45
|
+
toolroll release nightly --repo <path> --as <you> --token <password> [--schedule daily:03:00@<zone>] [--start]
|
|
46
|
+
— the full release check of main's head every night, approved once with your password; it never deploys,
|
|
47
|
+
merges or completes, and a failure tells you and holds composed releases until a full check passes
|
|
48
|
+
|
|
49
|
+
toolroll release ask-every-commit on|off --repo <path> [--as <you> --token <password>] — this project's releases ask
|
|
50
|
+
for every later commit (on), or carry test-only commits again (off: your password, never the lead's token)
|
|
51
|
+
|
|
34
52
|
Options
|
|
53
|
+
--no-carry every later commit of this release asks for approval again
|
|
54
|
+
--to <commit> with carry: the later commit
|
|
55
|
+
--request <id> with carry: the request's own id, so a retry is the same request
|
|
35
56
|
--repo <path> the gate checkout the release check runs in (default: this checkout)
|
|
36
57
|
--new release a new commit on the branch, setting the earlier unfinished release aside
|
|
37
58
|
--limit <step>=<min> change one step's time limit (${Object.entries(RELEASE_LIMITS).map(([step, minutes]) => `${step} ${minutes}`).join(", ")})
|
|
@@ -44,8 +65,8 @@ A runner's lease is returned with \`toolroll worker release <lease>\`.`;
|
|
|
44
65
|
/** A runner lease id (claim.ts mints randomUUID()). */
|
|
45
66
|
export const LEASE_SHAPED = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
46
67
|
export const LEASE_DEPRECATION = "`toolroll release <lease>` is now `toolroll worker release <lease>`; the old form goes in the next minor version.";
|
|
47
|
-
const VALUE_FLAGS = new Set(["--repo", "--limit", "--tap", "--release-gate"]);
|
|
48
|
-
const BARE_FLAGS = new Set(["--new", "--json", "--help", "--yes"]);
|
|
68
|
+
const VALUE_FLAGS = new Set(["--repo", "--limit", "--tap", "--release-gate", "--to", "--request", "--as", "--token", "--schedule"]);
|
|
69
|
+
const BARE_FLAGS = new Set(["--new", "--json", "--help", "--yes", "--no-carry", "--start"]);
|
|
49
70
|
export async function runReleaseCommand(args, write, deps = {}) {
|
|
50
71
|
const json = args.includes("--json");
|
|
51
72
|
const refuse = (reason, message, code) => {
|
|
@@ -54,7 +75,7 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
54
75
|
};
|
|
55
76
|
const positional = [];
|
|
56
77
|
const limits = {};
|
|
57
|
-
let repo, tap, gate;
|
|
78
|
+
let repo, tap, gate, to, request, as, token, scheduleFlag;
|
|
58
79
|
for (let i = 0; i < args.length; i += 1) {
|
|
59
80
|
const arg = args[i];
|
|
60
81
|
if (BARE_FLAGS.has(arg))
|
|
@@ -66,6 +87,16 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
66
87
|
i += 1;
|
|
67
88
|
if (arg === "--repo")
|
|
68
89
|
repo = value;
|
|
90
|
+
else if (arg === "--to")
|
|
91
|
+
to = value;
|
|
92
|
+
else if (arg === "--request")
|
|
93
|
+
request = value;
|
|
94
|
+
else if (arg === "--as")
|
|
95
|
+
as = value;
|
|
96
|
+
else if (arg === "--token")
|
|
97
|
+
token = value;
|
|
98
|
+
else if (arg === "--schedule")
|
|
99
|
+
scheduleFlag = value;
|
|
69
100
|
else if (arg === "--tap")
|
|
70
101
|
tap = value;
|
|
71
102
|
else if (arg === "--release-gate")
|
|
@@ -93,6 +124,11 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
93
124
|
}
|
|
94
125
|
if (args.includes("--yes"))
|
|
95
126
|
return refuse("usage", "--yes goes with --release-gate <id>; a release needs no confirmation beyond the owner's approval.", 2);
|
|
127
|
+
if (positional[0] === "nightly" && positional.length === 1)
|
|
128
|
+
return runNightlyCommand({ repo: repo === undefined ? undefined : resolve(deps.cwd ?? process.cwd(), repo), as, token, schedule: scheduleFlag, start: args.includes("--start") }, write, json, deps);
|
|
129
|
+
if (["carry", "revoke", "ask-every-commit"].includes(positional[0] ?? "") && positional.length === 2) {
|
|
130
|
+
return runAuthorityCommand(positional[0], positional[1], { to, request, as, token, repo: repo === undefined ? undefined : resolve(deps.cwd ?? process.cwd(), repo) }, write, json, deps);
|
|
131
|
+
}
|
|
96
132
|
if (positional.length !== 1)
|
|
97
133
|
return refuse("usage", `Which branch? \`toolroll release <branch>\`.\n${RELEASE_HELP}`, 2);
|
|
98
134
|
const branch = positional[0];
|
|
@@ -117,7 +153,7 @@ export async function runReleaseCommand(args, write, deps = {}) {
|
|
|
117
153
|
const adapters = deps.adapters === undefined
|
|
118
154
|
? releaseAdapters({ checkout, say, ...(tap === undefined ? {} : { tap }), ...(limits["deploy"] === undefined ? {} : { deployMinutes: limits["deploy"] }) })
|
|
119
155
|
: { ...deps.adapters, say };
|
|
120
|
-
const outcome = await runRelease({ branch, checkout, stateDir, limits, fresh: args.includes("--new") }, adapters);
|
|
156
|
+
const outcome = await runRelease({ branch, checkout, stateDir, limits, fresh: args.includes("--new"), noCarry: args.includes("--no-carry") }, adapters);
|
|
121
157
|
return report(outcome, write, json, lines);
|
|
122
158
|
}
|
|
123
159
|
function report(outcome, write, json, lines) {
|
|
@@ -179,3 +215,86 @@ function runGateRelease(id, yes, write, json, world) {
|
|
|
179
215
|
write(`Lift it: toolroll release --release-gate ${outcome.id} --yes`);
|
|
180
216
|
return 0;
|
|
181
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* The release's authority from the command line (release-once.md): ask for a carry (anyone may ask, the lead
|
|
220
|
+
* included; only the rules decide), stop carrying (only ever stricter), or change the project's carry setting
|
|
221
|
+
* (turning carrying back on is a person's own command, never the lead's or a remote token's).
|
|
222
|
+
*/
|
|
223
|
+
async function runAuthorityCommand(command, target, flags, write, json, deps) {
|
|
224
|
+
const name = `release ${command}`;
|
|
225
|
+
const answer = (ok, fields, words, code) => {
|
|
226
|
+
write(json ? checkedEnvelopeJson({ ok, command: name, ...fields }) : words);
|
|
227
|
+
return code;
|
|
228
|
+
};
|
|
229
|
+
const database = deps.database ?? databasePath(process.env, homedir());
|
|
230
|
+
if (!existsSync(database))
|
|
231
|
+
return answer(false, { reason: "no-plane", message: "This machine has no Toolroll database." }, "This machine has no Toolroll database.", 1);
|
|
232
|
+
const store = openStore(database);
|
|
233
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
234
|
+
try {
|
|
235
|
+
if (command === "ask-every-commit") {
|
|
236
|
+
if (target !== "on" && target !== "off")
|
|
237
|
+
return answer(false, { reason: "usage", message: "ask-every-commit takes on or off." }, "ask-every-commit takes on or off.", 2);
|
|
238
|
+
if (flags.repo === undefined)
|
|
239
|
+
return answer(false, { reason: "usage", message: "Name the project with --repo <path>." }, "Name the project with --repo <path>.", 2);
|
|
240
|
+
// Turning carrying back on relaxes a release's rules: a person's own ceremony, as an approval needs (a password
|
|
241
|
+
// typed now, a re-check, or their own session), never a lead or remote token. Turning it off is only stricter.
|
|
242
|
+
let actor = "cli";
|
|
243
|
+
if (target === "off") {
|
|
244
|
+
const refused = (message) => answer(false, { reason: "person-only", message }, message, 1);
|
|
245
|
+
if (machineActing() || process.env["TOOLROLL_LEAD_TOKEN"] !== undefined || process.env["TOOLROLL_REMOTE_TOKEN"] !== undefined)
|
|
246
|
+
return refused("Only a person turns carrying back on, never the lead or a remote token.");
|
|
247
|
+
if (flags.as === undefined || flags.token === undefined)
|
|
248
|
+
return refused("Turning carrying back on needs your password: add --as <you> --token <password>.");
|
|
249
|
+
const proved = authenticateApprover(store, flags.as, flags.token, flags.repo);
|
|
250
|
+
if (!proved.ok || !personProof(proved.method))
|
|
251
|
+
return refused("Only an approver of this project, proved as a person, turns carrying back on.");
|
|
252
|
+
actor = flags.as;
|
|
253
|
+
}
|
|
254
|
+
setAskEveryReleaseCommit(store, flags.repo, target === "on", actor, now);
|
|
255
|
+
const words = target === "on" ? "Every later release commit in this project asks for approval." : "Later test-only release commits ship under the release approval.";
|
|
256
|
+
return answer(true, { repo: flags.repo, ask: target === "on" }, words, 0);
|
|
257
|
+
}
|
|
258
|
+
const id = Number(target);
|
|
259
|
+
if (!Number.isSafeInteger(id) || id < 1)
|
|
260
|
+
return answer(false, { reason: "usage", message: `${target} isn't a release id.` }, `${target} isn't a release id.`, 2);
|
|
261
|
+
if (command === "revoke") {
|
|
262
|
+
const done = revokeRelease(store, id, "cli", now);
|
|
263
|
+
return done.ok ? answer(true, { release: id }, `Release ${id} no longer carries later commits.`, 0) : answer(false, { reason: "refused", message: done.why }, done.why, 1);
|
|
264
|
+
}
|
|
265
|
+
if (flags.to === undefined || !/^[0-9a-f]{40}$/.test(flags.to))
|
|
266
|
+
return answer(false, { reason: "usage", message: "carry takes --to <full commit id>." }, "carry takes --to <full commit id>.", 2);
|
|
267
|
+
const result = await proveCarry(store, { authority: id, to: flags.to, ...(flags.request === undefined ? {} : { requestId: flags.request }), now });
|
|
268
|
+
if (result.ok)
|
|
269
|
+
return answer(true, { release: id, task: result.taskId, retried: result.retried, carry: { files: result.carry.files, lines: result.carry.changedLines } }, `Carried release ${id} to ${flags.to.slice(0, 7)}: task ${result.taskId} (${result.carry.files} files, ${result.carry.changedLines} lines).`, 0);
|
|
270
|
+
return answer(false, { reason: result.reason, message: result.message, ...(result.path === undefined ? {} : { path: result.path }) }, carryRefusalWords(result, flags.to), 1);
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
store.close();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/** `toolroll release nightly`: the nightly full check on main, approved with a person's password (nightly-check.ts). */
|
|
277
|
+
async function runNightlyCommand(flags, write, json, deps) {
|
|
278
|
+
const answer = (ok, fields, words, code) => { write(json ? checkedEnvelopeJson({ ok, command: "release nightly", ...fields }) : words); return code; };
|
|
279
|
+
if (flags.repo === undefined)
|
|
280
|
+
return answer(false, { reason: "usage", message: "Name the project with --repo <path>." }, "Name the project with --repo <path>.", 2);
|
|
281
|
+
if (flags.as === undefined || flags.token === undefined)
|
|
282
|
+
return answer(false, { reason: "usage", message: "The nightly check is approved with your password: add --as <you> --token <password>." }, "The nightly check is approved with your password: add --as <you> --token <password>.", 2);
|
|
283
|
+
if (machineActing() || process.env["TOOLROLL_LEAD_TOKEN"] !== undefined || process.env["TOOLROLL_REMOTE_TOKEN"] !== undefined)
|
|
284
|
+
return answer(false, { reason: "person-only", message: "Only a person sets up the nightly check." }, "Only a person sets up the nightly check.", 1);
|
|
285
|
+
const zone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
286
|
+
const schedule = flags.schedule ?? `daily:03:00${zone ? `@${zone}` : ""}`;
|
|
287
|
+
if (parseSchedule(schedule) === null)
|
|
288
|
+
return answer(false, { reason: "usage", message: `${schedule} isn't a schedule (daily:03:00@Europe/Paris, say).` }, `${schedule} isn't a schedule (daily:03:00@Europe/Paris, say).`, 2);
|
|
289
|
+
const database = deps.database ?? databasePath(process.env, homedir());
|
|
290
|
+
const store = openStore(database);
|
|
291
|
+
try {
|
|
292
|
+
const made = createNightlyCheckFlow(store, { repo: flags.repo, schedule, as: flags.as, token: flags.token, start: flags.start }, (deps.now ?? (() => new Date()))());
|
|
293
|
+
if (!made.ok)
|
|
294
|
+
return answer(false, { reason: "refused", message: made.message }, made.message, 1);
|
|
295
|
+
return answer(true, { flow: made.flow, trigger: made.trigger, schedule }, `The nightly check of main runs ${schedule}${flags.start ? "" : " once you turn its schedule on"} (flow ${made.flow}).`, 0);
|
|
296
|
+
}
|
|
297
|
+
finally {
|
|
298
|
+
store.close();
|
|
299
|
+
}
|
|
300
|
+
}
|
package/dist/release-coverage.js
CHANGED
|
@@ -63,6 +63,9 @@ function passingReleases(store, root, repo) {
|
|
|
63
63
|
if (!gate.ok || gate.bytes === null)
|
|
64
64
|
return [];
|
|
65
65
|
const receipt = JSON.parse(gate.bytes);
|
|
66
|
+
// A release check whose coverage is partial or lost a required test covers nothing.
|
|
67
|
+
if (receipt.coverage !== undefined && receipt.coverage !== "full" && receipt.coverage !== "composed")
|
|
68
|
+
return [];
|
|
66
69
|
const head = store.getRun(run)?.headRevision ?? null;
|
|
67
70
|
if (receipt.run !== run || receipt.result?.ran !== true || receipt.result.exitCode !== 0)
|
|
68
71
|
return [];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type CheckContext, type CheckPart, type CheckReport, type ObligationSnapshot, type SealedInventory, type TargetedPlan } from "./contracts/release-check.js";
|
|
2
|
+
import { type CheckAttempt, type Store, type VerifyCommand } from "./store.js";
|
|
3
|
+
/** The sanity pass every targeted check runs beside its plan: a few fast tests of the core rules, when the tree has them. */
|
|
4
|
+
export declare const SANITY_UNITS: readonly string[];
|
|
5
|
+
/** A run's sealed check evidence, read back and verified (check-report.ts). */
|
|
6
|
+
export type SealedRun = {
|
|
7
|
+
run: number;
|
|
8
|
+
attempt: CheckAttempt;
|
|
9
|
+
report: CheckReport | null;
|
|
10
|
+
reportDigest: string | null;
|
|
11
|
+
inventory: SealedInventory | null;
|
|
12
|
+
context: CheckContext | null;
|
|
13
|
+
plan: TargetedPlan | null;
|
|
14
|
+
planDigest: string | null;
|
|
15
|
+
snapshot: ObligationSnapshot | null;
|
|
16
|
+
snapshotDigest: string | null;
|
|
17
|
+
};
|
|
18
|
+
export declare function sealedRun(store: Store, root: string, run: number): SealedRun | null;
|
|
19
|
+
/** R(A): what a later check must run again, from A's sealed evidence alone — or why only a full check will do. */
|
|
20
|
+
export type Obligations = {
|
|
21
|
+
full: string | null;
|
|
22
|
+
units: string[];
|
|
23
|
+
parts: CheckPart[];
|
|
24
|
+
groups: {
|
|
25
|
+
script: "app" | "flows";
|
|
26
|
+
kind: "scripted" | "real";
|
|
27
|
+
groups: string[];
|
|
28
|
+
}[];
|
|
29
|
+
versions: string[];
|
|
30
|
+
/** The identities that have to be reported, and pass. */
|
|
31
|
+
required: string[];
|
|
32
|
+
/** Identities skipped at A: they may stay skipped. */
|
|
33
|
+
skipped: string[];
|
|
34
|
+
};
|
|
35
|
+
/** The disposition of every outcome at A (the table in the plan). */
|
|
36
|
+
export declare function requiredSet(a: {
|
|
37
|
+
report: CheckReport | null;
|
|
38
|
+
inventory: SealedInventory | null;
|
|
39
|
+
}): Obligations;
|
|
40
|
+
/** The identities that failed in one run's sealed report (for an obligation snapshot). */
|
|
41
|
+
export declare function failedIdentities(report: CheckReport | null): string[];
|
|
42
|
+
/** The earlier targeted runs of this generation after A, with their sealed report digests and what failed in them. */
|
|
43
|
+
export declare function obligationSnapshot(store: Store, root: string, authority: number, base: number, before: number): ObligationSnapshot;
|
|
44
|
+
/** The plan a targeted run runs: R(A), the snapshot's failures, every test changed since A, and the sanity pass. */
|
|
45
|
+
export declare function planFrom(base: number, obligations: Obligations, snapshot: ObligationSnapshot, changed: readonly string[], units: readonly string[], journeysRan: readonly CheckPart[]): TargetedPlan;
|
|
46
|
+
export type RecheckDecision = {
|
|
47
|
+
mode: "targeted";
|
|
48
|
+
authority: number;
|
|
49
|
+
plan: TargetedPlan;
|
|
50
|
+
snapshot: ObligationSnapshot;
|
|
51
|
+
watch: string[];
|
|
52
|
+
} | {
|
|
53
|
+
mode: "full";
|
|
54
|
+
authority: number | null;
|
|
55
|
+
why: string;
|
|
56
|
+
watch: string[];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Whether the check about to run for this release task may be targeted (planTargetedRecheck): only when A and this run
|
|
60
|
+
* are gate tasks of the same current generation, A is the generation's newest full check with a valid report that
|
|
61
|
+
* accounts for every item and reused nothing from CI, everything since A is test-only (the carry proved it), the
|
|
62
|
+
* execution context matches A's, and no main-check failure is open. Otherwise a full check, told which identities it
|
|
63
|
+
* must still report (watch).
|
|
64
|
+
*/
|
|
65
|
+
export declare function planTargetedRecheck(store: Store, root: string, input: {
|
|
66
|
+
runId: number;
|
|
67
|
+
taskId: string;
|
|
68
|
+
repo: string;
|
|
69
|
+
head: string;
|
|
70
|
+
command: VerifyCommand;
|
|
71
|
+
access: string;
|
|
72
|
+
now: Date;
|
|
73
|
+
}): RecheckDecision;
|
|
74
|
+
/** Coverage of a check: full, composed from A, partial (never passes), or identity-removed (asks again). */
|
|
75
|
+
export type Coverage = "full" | "composed" | "partial" | "identity-removed";
|
|
76
|
+
/**
|
|
77
|
+
* Whether a targeted run B, with A's sealed evidence, its plan and snapshot, covers every item of B's inventory
|
|
78
|
+
* (coverage conservation), and every required identity passed — re-derived from sealed values only.
|
|
79
|
+
*/
|
|
80
|
+
export declare function coverageOf(input: {
|
|
81
|
+
a: {
|
|
82
|
+
report: CheckReport;
|
|
83
|
+
inventory: SealedInventory;
|
|
84
|
+
};
|
|
85
|
+
b: {
|
|
86
|
+
report: CheckReport | null;
|
|
87
|
+
inventory: SealedInventory | null;
|
|
88
|
+
exitCode: number | null;
|
|
89
|
+
};
|
|
90
|
+
plan: TargetedPlan;
|
|
91
|
+
snapshot: ObligationSnapshot;
|
|
92
|
+
}): Coverage;
|
|
93
|
+
/** A full run's own check: a required identity of the generation that is gone or newly skipped. */
|
|
94
|
+
export declare function fullRunRemovedIdentity(report: CheckReport | null, watch: readonly string[], skippedAtBase: readonly string[]): boolean;
|
|
95
|
+
/** What a composed receipt names (receipt version 3): each link is proved again by provedComposition. */
|
|
96
|
+
export type ComposedLinks = {
|
|
97
|
+
base: number;
|
|
98
|
+
baseReceipt: string;
|
|
99
|
+
baseReport: string;
|
|
100
|
+
authority: number;
|
|
101
|
+
carries: number[];
|
|
102
|
+
artifacts: string;
|
|
103
|
+
plan: string;
|
|
104
|
+
snapshot: string;
|
|
105
|
+
report: string;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* After the check: its coverage, and for a composed one the links its receipt names. An identity-removed result stops
|
|
109
|
+
* the release carrying (a person approves it again). `gateOf` reads a run's verified receipt digest.
|
|
110
|
+
*/
|
|
111
|
+
export declare function settleCoverage(store: Store, root: string, input: {
|
|
112
|
+
runId: number;
|
|
113
|
+
taskId: string;
|
|
114
|
+
exitCode: number | null;
|
|
115
|
+
now: Date;
|
|
116
|
+
}, gateOf: (run: number) => string | null): {
|
|
117
|
+
coverage: Coverage;
|
|
118
|
+
composed: ComposedLinks | null;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Re-prove every link a composed receipt names, from sealed artifacts and the store: A's receipt and report, this run's
|
|
122
|
+
* plan (re-derived from A's sealed evidence and the sealed snapshot), its snapshot and report, the release's artifact
|
|
123
|
+
* digest on every carry of this task, the generation and the grant. Null when every link holds; else why not.
|
|
124
|
+
*/
|
|
125
|
+
export declare function provedComposition(store: Store, root: string, runId: number, links: ComposedLinks, gateOf: (run: number) => string | null, grantOf: (run: number) => string | null): string | null;
|
|
126
|
+
/** The identities a later check of this release still has to run again and pass (the anchor page's obligations). */
|
|
127
|
+
export declare function releaseObligationsOf(store: Store, root: string, authority: number): string[];
|
|
128
|
+
/**
|
|
129
|
+
* The identities a release generation still owes, independent of whether a targeted check is allowed (review round
|
|
130
|
+
* 1): every identity that failed in any of its checks before `before`, until a later check reported it PASSED by name.
|
|
131
|
+
* A failed typecheck or build doesn't wipe them, and a full fallback keeps them: each must be reported again, and a
|
|
132
|
+
* check that drops or newly skips one asks a person again.
|
|
133
|
+
*/
|
|
134
|
+
export declare function owedIdentities(store: Store, root: string, authority: number, before: number): string[];
|
|
135
|
+
/**
|
|
136
|
+
* The generation's protected identities (review round 2): every identity that failed in any of its checks before
|
|
137
|
+
* `before`. Never shrinks — a later pass doesn't forgive a later removal: removing, renaming or newly skipping any of
|
|
138
|
+
* them, in any later run of the generation, asks a person again. Separate from what is still owed (owedIdentities).
|
|
139
|
+
*/
|
|
140
|
+
export declare function protectedIdentities(store: Store, root: string, authority: number, before: number): string[];
|
|
141
|
+
/**
|
|
142
|
+
* Whether a run's report removed one of the protected identities: a unit test or journey gone or newly skipped from a
|
|
143
|
+
* file or group the run did report, a unit file gone from its inventory, a group or upgrade version missing from a lane
|
|
144
|
+
* that finished. A lane that didn't run or finish is no pass, so it is judged by the next run that does.
|
|
145
|
+
*/
|
|
146
|
+
export declare function removedProtected(report: CheckReport | null, inventory: SealedInventory | null, kept: readonly string[]): boolean;
|