@coreplane/switchboard 1.256.0 → 1.257.0
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/dist/assets/config/config.example.yaml +14 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +340 -7
- package/dist/assets/deploy/cloudflare-resident/drain.ts +49 -4
- package/dist/assets/deploy/cloudflare-resident/worker.ts +52 -4
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +5 -3
- package/dist/assets/project.json +17 -9
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +24 -1
- package/dist/assets/src/core/authz/grants.ts +12 -0
- package/dist/assets/src/core/authz/policy.ts +15 -0
- package/dist/assets/src/core/budgets.ts +51 -17
- package/dist/assets/src/core/coordinator/contract.ts +45 -5
- package/dist/assets/src/core/coordinator/driver.ts +6 -3
- package/dist/assets/src/core/delivery.ts +8 -5
- package/dist/assets/src/core/plane/decide.ts +468 -12
- package/dist/assets/src/core/plane/findings.ts +120 -0
- package/dist/assets/src/core/runEvents.ts +41 -14
- package/dist/assets/src/core/runFriction.ts +16 -0
- package/dist/assets/src/core/runLedger/types.ts +16 -3
- package/dist/assets/src/core/runRecord.ts +12 -0
- package/dist/assets/src/core/ship/contract.ts +14 -2
- package/dist/assets/src/core/ship/coordinator.ts +98 -29
- package/dist/assets/src/core/ship/renewal.ts +19 -17
- package/dist/assets/src/core/trace/attrs.ts +1 -1
- package/dist/assets/src/core/untrusted.ts +35 -0
- package/dist/assets/src/execution/residentCredentials.ts +7 -4
- package/dist/assets/web/dist/.vite/manifest.json +80 -55
- package/dist/assets/web/dist/assets/HomePage-DJqJQzrL.js +1 -0
- package/dist/assets/web/dist/assets/{PendingTurnRow-DGINv9XT.js → PendingTurnRow-fhKqKUVv.js} +1 -1
- package/dist/assets/web/dist/assets/PlanePage-Bood0hNO.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-jYhEeeyu.js → ResidentDetailPage-DOe1HtUC.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-DvFmlV4U.js → ResidentsIndexPage-TyCGjXM4.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-C_4t-sit.js +1 -0
- package/dist/assets/web/dist/assets/{RunRoutePage-CvZ-TOT3.js → RunRoutePage-BNepPMRX.js} +4 -4
- package/dist/assets/web/dist/assets/RunsIndexPage-jZnMw1mM.js +1 -0
- package/dist/assets/web/dist/assets/{ScheduledPage-Viim-bus.js → ScheduledPage-BkNoudDP.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-CYUy8McC.js → SettingsPage-COvWGXl-.js} +1 -1
- package/dist/assets/web/dist/assets/SilentTurn-DNAANKgN.js +2 -0
- package/dist/assets/web/dist/assets/SlackMark-CtiHrNjr.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-Dv6UMaPy.js → StatusDot-BgmkSV0S.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-Bd-5Rypv.js → Tooltip-DEUPuCPW.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DNhdzxvY.js +1 -0
- package/dist/assets/web/dist/assets/{dist-Nl3uaxrP.js → dist-CF3jz9LM.js} +1 -1
- package/dist/assets/web/dist/assets/durationTone-CVpX_yIk.js +1 -0
- package/dist/assets/web/dist/assets/main-7SAujY_s.js +28 -0
- package/dist/assets/web/dist/assets/main-DGC6WFqS.css +1 -0
- package/dist/assets/web/dist/assets/{sseReplay-DE6wv1Ua.js → sseReplay-BRJEIh83.js} +1 -1
- package/dist/cli.js +2672 -1102
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/HomePage-BEiStFwW.js +0 -3
- package/dist/assets/web/dist/assets/PlanePage-JEj-lqgz.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-DvWzR1JQ.js +0 -1
- package/dist/assets/web/dist/assets/RunsIndexPage-JqVSDOok.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-B81wEnKN.js +0 -1
- package/dist/assets/web/dist/assets/durationTone-U_rOqo6r.js +0 -1
- package/dist/assets/web/dist/assets/main-C4QfwybO.css +0 -1
- package/dist/assets/web/dist/assets/main-DRxWlffc.js +0 -28
|
@@ -350,6 +350,7 @@ import {
|
|
|
350
350
|
liveDrain,
|
|
351
351
|
parseDrainRequest,
|
|
352
352
|
reportImageCurrent,
|
|
353
|
+
staleHolds,
|
|
353
354
|
type DrainRecord,
|
|
354
355
|
} from "./drain";
|
|
355
356
|
import {
|
|
@@ -1604,9 +1605,15 @@ export class ResidentRegistryDO extends DurableObject<Env> {
|
|
|
1604
1605
|
* the new image (issue 1931): hold the drain for each — the fleet must not
|
|
1605
1606
|
* reopen onto them until they report. No live drain, nothing to hold. */
|
|
1606
1607
|
async holdDrainFor(resources: string[]): Promise<void> {
|
|
1607
|
-
const
|
|
1608
|
+
const now = systemClock();
|
|
1609
|
+
const record = liveDrain(await this.ctx.storage.get(DRAIN_KEY), now);
|
|
1608
1610
|
if (record === null || resources.length === 0) return;
|
|
1609
|
-
|
|
1611
|
+
const held = holdDrain(record, resources, now);
|
|
1612
|
+
await this.ctx.storage.put(DRAIN_KEY, held);
|
|
1613
|
+
// The hold's liveness alarm (issue 2044): the cycle bound is the earlier
|
|
1614
|
+
// end, so the alarm fires there — reopening the fleet with the stale
|
|
1615
|
+
// containers named — instead of at `until`, the last resort.
|
|
1616
|
+
if (held.holdsUntil !== undefined) await this.ctx.storage.setAlarm(Date.parse(held.holdsUntil));
|
|
1610
1617
|
}
|
|
1611
1618
|
|
|
1612
1619
|
/** One resident's word that its running container is on the deploy's image:
|
|
@@ -1635,11 +1642,25 @@ export class ResidentRegistryDO extends DurableObject<Env> {
|
|
|
1635
1642
|
const stored = await this.ctx.storage.get(DRAIN_KEY);
|
|
1636
1643
|
if (stored === undefined) return;
|
|
1637
1644
|
if (liveDrain(stored, now) === null) {
|
|
1645
|
+
// The hold's liveness (issue 2044): a reopen past the cycle bound names
|
|
1646
|
+
// the containers whose post-deploy cycle never landed — a warning, never
|
|
1647
|
+
// a silence; each restarts on its own next quiet attach or refresh.
|
|
1648
|
+
const stale = staleHolds(stored, now);
|
|
1649
|
+
if (stale !== null)
|
|
1650
|
+
console.log(
|
|
1651
|
+
`[drain] fleet reopened with ${stale.join(", ")} still on the pre-deploy image — the post-deploy cycle did not land within its bound; a stale container restarts on its next quiet attach or refresh`,
|
|
1652
|
+
);
|
|
1638
1653
|
await this.ctx.storage.delete(DRAIN_KEY);
|
|
1639
1654
|
await this.pushDrainPost("below");
|
|
1640
1655
|
} else {
|
|
1641
|
-
// Replaced with a later end under an already-armed alarm: re-arm at
|
|
1642
|
-
|
|
1656
|
+
// Replaced with a later end under an already-armed alarm: re-arm at the
|
|
1657
|
+
// record's own earlier end — the hold's cycle bound when one stands.
|
|
1658
|
+
const record = stored as DrainRecord;
|
|
1659
|
+
const ends = [
|
|
1660
|
+
Date.parse(record.until),
|
|
1661
|
+
...(record.holdsUntil !== undefined ? [Date.parse(record.holdsUntil)] : []),
|
|
1662
|
+
];
|
|
1663
|
+
await this.ctx.storage.setAlarm(Math.min(...ends.filter(Number.isFinite)));
|
|
1643
1664
|
}
|
|
1644
1665
|
}
|
|
1645
1666
|
|
|
@@ -3261,6 +3282,14 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
3261
3282
|
await this.writeDiskMarkers({ ready: sha, depsKey: lockfileHash, builtSha: sha });
|
|
3262
3283
|
this.deleteSchedules(PROVISIONING_CALLBACK);
|
|
3263
3284
|
await this.setResidentState("warm");
|
|
3285
|
+
// A fresh provision runs on the image the deployed Worker pins, so it IS
|
|
3286
|
+
// the cycle a held drain waits for (issue 2044): report it — a rebuild
|
|
3287
|
+
// mid-drain reopens the fleet instead of leaving the hold standing on a
|
|
3288
|
+
// report `reconcileImage` would never send for a container it never saw
|
|
3289
|
+
// as stale. The marker is cleared with the report; a failure keeps both
|
|
3290
|
+
// for the next reconcile, and the hold's cycle bound is the backstop.
|
|
3291
|
+
await this.ctx.storage.put(IMAGE_REPORT_PENDING_KEY, { resource });
|
|
3292
|
+
await this.reportPendingImageCurrent("provision");
|
|
3264
3293
|
// The first refresh instance is the cron's: the row now reads `warm`
|
|
3265
3294
|
// with no instance recorded, so the next firing creates it (item 9).
|
|
3266
3295
|
} catch (err) {
|
|
@@ -7597,6 +7626,19 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
7597
7626
|
}
|
|
7598
7627
|
|
|
7599
7628
|
private async runOpTraced(op: "test" | "build", refArg: string | null, t0: number): Promise<OpRunOk | ThreadErr> {
|
|
7629
|
+
// The fleet drain (item 69; issue 2044): a typed op is a new piece of work
|
|
7630
|
+
// like a new run's attach, and during the incident one waited silently at
|
|
7631
|
+
// the drain — so it is answered with the drain's own record at once, a
|
|
7632
|
+
// deterministic refusal the command surfaces as its reason, never a wait.
|
|
7633
|
+
const drain = await this.fleetDrain();
|
|
7634
|
+
if (drain !== null) {
|
|
7635
|
+
return {
|
|
7636
|
+
error: `op-refused: the resident fleet is drained for ${drain.reason} (asked by ${drain.by}, ends by ${drain.until}) — re-run the command when the fleet reopens`,
|
|
7637
|
+
status: 503,
|
|
7638
|
+
reason: "draining",
|
|
7639
|
+
cause: "system",
|
|
7640
|
+
};
|
|
7641
|
+
}
|
|
7600
7642
|
try {
|
|
7601
7643
|
await this.ensureHydrated();
|
|
7602
7644
|
} catch (err) {
|
|
@@ -8070,6 +8112,7 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
8070
8112
|
AUTO_REBUILDS_KEY,
|
|
8071
8113
|
INFRA_STREAK_KEY,
|
|
8072
8114
|
MEMORY_KEY,
|
|
8115
|
+
IMAGE_REPORT_PENDING_KEY,
|
|
8073
8116
|
]);
|
|
8074
8117
|
const facts = map.get(FACTS_KEY) as RepoFacts | undefined;
|
|
8075
8118
|
const snap = map.get(SNAPSHOT_KEY) as SnapshotRecord | undefined;
|
|
@@ -8147,6 +8190,11 @@ export class ResidentDO extends Sandbox<Env> {
|
|
|
8147
8190
|
inFlight: this.inFlightCount() + registeredRuns,
|
|
8148
8191
|
// The runs alone (no refresh cycle): what the deploy preflight refuses on.
|
|
8149
8192
|
runsInFlight: this.runsInFlightCount() + registeredRuns,
|
|
8193
|
+
// Issue 2044: whether this container's new-image report is still owed —
|
|
8194
|
+
// `pending` names a resident a held drain waits on, so `repo list` and
|
|
8195
|
+
// the deploy's readers never print a bare "warm" for a repository whose
|
|
8196
|
+
// running container the last deploy could not verify.
|
|
8197
|
+
imageReport: map.get(IMAGE_REPORT_PENDING_KEY) !== undefined ? "pending" : "current",
|
|
8150
8198
|
// Item 22: who holds what, as the rows say — the mirror mutex and the
|
|
8151
8199
|
// cycle/hydration leases, each judged against this incarnation.
|
|
8152
8200
|
incarnation: this.incarnation,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.257.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "switchboard",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.257.0",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"workspaces": [
|
|
12
12
|
"web",
|
|
@@ -20445,7 +20445,7 @@
|
|
|
20445
20445
|
},
|
|
20446
20446
|
"packages/switchboard": {
|
|
20447
20447
|
"name": "@coreplane/switchboard",
|
|
20448
|
-
"version": "1.
|
|
20448
|
+
"version": "1.257.0",
|
|
20449
20449
|
"license": "Apache-2.0",
|
|
20450
20450
|
"dependencies": {
|
|
20451
20451
|
"@earendil-works/pi-ai": "0.85.1",
|
package/dist/assets/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "switchboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.257.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Mention it in Slack and an agent reviews the PR, ships the fix, or answers the question — on the model you choose, with its tools running where you decide.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"cli": "tsx src/cli.ts",
|
|
40
40
|
"verify": "npm run verify:root && npm run verify --workspaces --if-present && npm run check:site",
|
|
41
41
|
"verify:root": "npm run check:consistency && npm run typecheck && npm run lint && npm run format:check && npm test && npm run check:dist",
|
|
42
|
-
"check:consistency": "npm run check:lockfile && npm run check:sandbox-pair && npm run skills:check && npm run check:deps-drift && npm run licenses:check && npm run docs:check && npm run pr-title:check && npm run specs:check && npm run decisions:check && npm run hygiene:check && npm run check:project-facts && npm run check:registry-drift && npm run agents:check && npm run clock:check && npm run screenshots:check",
|
|
42
|
+
"check:consistency": "npm run check:lockfile && npm run check:sandbox-pair && npm run skills:check && npm run check:deps-drift && npm run licenses:check && npm run docs:check && npm run pr-title:check && npm run specs:check && npm run decisions:check && npm run hygiene:check && npm run vocabulary:check && npm run check:project-facts && npm run check:registry-drift && npm run agents:check && npm run clock:check && npm run screenshots:check",
|
|
43
43
|
"ci:gate": "node scripts/ci-gate.mjs",
|
|
44
|
-
"fix": "npm run docs:gen && npm run pr-title:gen && npm run agents:gen && npm run clock:gen && npm run hygiene:gen && npm run deploy:gen && npm run skills:sync && npm run lint:fix && npm run format",
|
|
44
|
+
"fix": "npm run docs:gen && npm run pr-title:gen && npm run agents:gen && npm run clock:gen && npm run hygiene:gen && npm run vocabulary:gen && npm run deploy:gen && npm run skills:sync && npm run lint:fix && npm run format",
|
|
45
45
|
"deploy:gen": "npm run --silent cli -- deploy init",
|
|
46
46
|
"deploy:check": "npm run --silent cli -- deploy init --check",
|
|
47
47
|
"check:lockfile": "node scripts/check-lockfile.mjs",
|
|
@@ -73,6 +73,8 @@
|
|
|
73
73
|
"decisions:check": "tsx scripts/decisions-check.ts",
|
|
74
74
|
"hygiene:check": "node scripts/public-hygiene.mjs",
|
|
75
75
|
"hygiene:gen": "node scripts/public-hygiene.mjs --write",
|
|
76
|
+
"vocabulary:check": "node scripts/vocabulary-check.mjs",
|
|
77
|
+
"vocabulary:gen": "node scripts/vocabulary-check.mjs --write",
|
|
76
78
|
"docs:changed": "node scripts/docs-changed.mjs",
|
|
77
79
|
"deploy:targets": "node scripts/deploy-targets.mjs",
|
|
78
80
|
"docs:dev": "npm run dev -w docs --",
|
package/dist/assets/project.json
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"commands": {
|
|
33
33
|
"$comment": "What each root npm script does and when to run it — the source of the Commands table in AGENTS.md (npm run agents:gen). A script without an entry fails agents:check, so a new script cannot ship undocumented.",
|
|
34
34
|
"verify": {
|
|
35
|
-
"does": "The whole gate: every
|
|
35
|
+
"does": "The whole gate: every check, every workspace — exactly what CI runs.",
|
|
36
36
|
"when": "Local sessions, before requesting review. ~4 min. A pipeline agent leaves it to CI."
|
|
37
37
|
},
|
|
38
38
|
"verify:root": {
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"when": "When only the bot changed."
|
|
41
41
|
},
|
|
42
42
|
"check:consistency": {
|
|
43
|
-
"does": "The sub-second checks that generated and declared things equal the code
|
|
43
|
+
"does": "The sub-second checks that generated and declared things equal the code, this table included.",
|
|
44
44
|
"when": "After touching a generated or declared artifact; one CI leg."
|
|
45
45
|
},
|
|
46
46
|
"test": {
|
|
47
|
-
"does": "The whole vitest suite
|
|
47
|
+
"does": "The whole vitest suite from one entry, after `deploy:gen`.",
|
|
48
48
|
"when": "Local sessions, before pushing; a pipeline agent runs the changed set — the full suite is CI's."
|
|
49
49
|
},
|
|
50
50
|
"typecheck": {
|
|
@@ -160,21 +160,29 @@
|
|
|
160
160
|
"when": "After renaming a test or editing a spec; `-- --fix` makes truncated titles explicit."
|
|
161
161
|
},
|
|
162
162
|
"specs:coverage": {
|
|
163
|
-
"does": "Maps a change's paths to the specs whose
|
|
163
|
+
"does": "Maps a change's paths to the specs whose headers cover them, then lists paths no spec covers.",
|
|
164
164
|
"when": "`-- --changed origin/main...HEAD [--test-guard]` before review; `-- --require` fails on an uncovered path; `-- --json` for machines."
|
|
165
165
|
},
|
|
166
166
|
"decisions:check": {
|
|
167
|
-
"does": "Every record under `docs/decisions/` and `docs/plans/` has a valid `status`, a superseded one names its successor, an accepted body only gains
|
|
168
|
-
"when": "Part of `check:consistency`; a failing record is superseded or amended
|
|
167
|
+
"does": "Every record under `docs/decisions/` and `docs/plans/` has a valid `status`, a superseded one names its successor, an accepted body only gains `## Amended`.",
|
|
168
|
+
"when": "Part of `check:consistency`; a failing record is superseded or amended, never edited."
|
|
169
169
|
},
|
|
170
170
|
"hygiene:check": {
|
|
171
171
|
"does": "The public tree's imprint (company, people, trackers, plan ids, ids, dates) equals the recorded list, which only shrinks.",
|
|
172
|
-
"when": "Part of `check:consistency`. New hit: rewrite the line or allow it
|
|
172
|
+
"when": "Part of `check:consistency`. New hit: rewrite the line or allow it by name."
|
|
173
173
|
},
|
|
174
174
|
"hygiene:gen": {
|
|
175
175
|
"does": "Records the tree's remaining imprint after a scrub; refuses growth unless `-- --force`.",
|
|
176
176
|
"when": "Part of `fix`; new imprint fails it like `hygiene:check`."
|
|
177
177
|
},
|
|
178
|
+
"vocabulary:check": {
|
|
179
|
+
"does": "No internal word prints on a user surface; the baseline only shrinks.",
|
|
180
|
+
"when": "Part of `check:consistency`; a hit is rewritten in the user's nouns."
|
|
181
|
+
},
|
|
182
|
+
"vocabulary:gen": {
|
|
183
|
+
"does": "Records the remaining internal words; refuses growth unless `-- --force`.",
|
|
184
|
+
"when": "Part of `fix`."
|
|
185
|
+
},
|
|
178
186
|
"agents:gen": {
|
|
179
187
|
"does": "Writes the Commands table in AGENTS.md from `package.json` and this file.",
|
|
180
188
|
"when": "After adding or changing a script; part of `fix`."
|
|
@@ -220,8 +228,8 @@
|
|
|
220
228
|
"when": "After a `web/` change."
|
|
221
229
|
},
|
|
222
230
|
"screenshots:gen": {
|
|
223
|
-
"does": "Renders the dashboard screenshots whose inputs changed, both themes, recording
|
|
224
|
-
"when": "After a `web/` or fixture change
|
|
231
|
+
"does": "Renders the dashboard screenshots whose inputs changed, both themes, recording input hashes in the manifest (`--force`: all).",
|
|
232
|
+
"when": "After a `web/` or fixture change; needs `npx playwright-core install chromium`, so not in `fix`."
|
|
225
233
|
},
|
|
226
234
|
"screenshots:check": {
|
|
227
235
|
"does": "Each surface's inputs still hash to what its screenshots were rendered from — no browser.",
|
package/dist/assets/source.json
CHANGED
|
@@ -279,6 +279,23 @@ export const FAST_GATES_BEFORE_PUSH =
|
|
|
279
279
|
"CI's gate — `npm run verify` runs there on your push, never here: push a head early and let CI judge it, " +
|
|
280
280
|
"fixing forward with further commits and pushes.";
|
|
281
281
|
|
|
282
|
+
// Every coding prompt carries this verbatim, right after the fast gates
|
|
283
|
+
// (agent-coding item 13; record 0071 mechanism one, issue 1747): the rebase
|
|
284
|
+
// before every push, always and never configurable. Approved pull requests
|
|
285
|
+
// went stale behind sibling merges and each needed a hand-posted rebase; a
|
|
286
|
+
// rebase against a base the run just fetched, with the change's context still
|
|
287
|
+
// in its window, is the cheapest rebase the system will ever run, so it is
|
|
288
|
+
// unconditional. One constant, one text: the three coding variants carry the
|
|
289
|
+
// same bytes — a ship fix round's child runs these same prompts — and the ship
|
|
290
|
+
// contract's first instruction points here instead of re-stating it
|
|
291
|
+
// (src/core/ship/contract.ts).
|
|
292
|
+
export const REBASE_BEFORE_PUSH =
|
|
293
|
+
"REBASE BEFORE EVERY PUSH — always, not configurable. Immediately before each push: fetch your base branch, " +
|
|
294
|
+
"rebase your branch onto it, resolve any conflict with the context you already have (the repository's " +
|
|
295
|
+
"AGENTS.md says how a generated file is regenerated — regenerate it, never hand-merge it), re-run THE FAST " +
|
|
296
|
+
"GATES on the rebased tree, and only then push. Every head that reaches review is then current with its " +
|
|
297
|
+
"base when it lands, and no unit ends merge-ready behind a sibling that merged first.";
|
|
298
|
+
|
|
282
299
|
const CODING_SYSTEM = `You are Switchboard's coding agent, operating from a Slack request.
|
|
283
300
|
|
|
284
301
|
You work inside a dedicated workspace directory with bash, read_file, and write_file tools. ${SANDBOX_TOOLCHAIN}
|
|
@@ -303,6 +320,8 @@ ${CHECKS_BY_COST}
|
|
|
303
320
|
|
|
304
321
|
${FAST_GATES_BEFORE_PUSH}
|
|
305
322
|
|
|
323
|
+
${REBASE_BEFORE_PUSH}
|
|
324
|
+
|
|
306
325
|
${NEVER_MERGE}
|
|
307
326
|
|
|
308
327
|
${UNIT_CONTRACT}
|
|
@@ -352,6 +371,8 @@ ${CHECKS_BY_COST}
|
|
|
352
371
|
|
|
353
372
|
${FAST_GATES_BEFORE_PUSH}
|
|
354
373
|
|
|
374
|
+
${REBASE_BEFORE_PUSH}
|
|
375
|
+
|
|
355
376
|
${NEVER_MERGE}
|
|
356
377
|
|
|
357
378
|
${UNIT_CONTRACT}
|
|
@@ -397,6 +418,8 @@ ${CHECKS_BY_COST}
|
|
|
397
418
|
|
|
398
419
|
${FAST_GATES_BEFORE_PUSH}
|
|
399
420
|
|
|
421
|
+
${REBASE_BEFORE_PUSH}
|
|
422
|
+
|
|
400
423
|
${NEVER_MERGE}
|
|
401
424
|
|
|
402
425
|
${UNIT_CONTRACT}
|
|
@@ -589,7 +612,7 @@ Your tools work without a workspace: the GitHub tools — \`github_repos\` (the
|
|
|
589
612
|
|
|
590
613
|
${statusCardRule('"Read the issue and its thread", "Post the comment"')} A one-step answer needs no checklist; post one when the request has steps the person would wait on.
|
|
591
614
|
|
|
592
|
-
You cannot run commands, clone repositories, edit code, or review pull requests, and you cannot search the web — and you cannot hand off, route, forward or start another run: never say you will hand off, route, forward or start anything, because you cannot start another agent's run. Other Switchboard agents can do those things, and a plain message reaches them by itself
|
|
615
|
+
You cannot run commands, clone repositories, edit code, or review pull requests, and you cannot search the web — and you cannot hand off, route, forward or start another run: never say you will hand off, route, forward or start anything, because you cannot start another agent's run. Other Switchboard agents can do those things, and a plain message reaches them by itself: Switchboard's door reads every message — a reply in this thread included — and starts the right agent from the person's own words. For a code change or a pull request, say what you found and that the change is not yours to make, and that asking for it in plain words — right here in the thread or anywhere in the channel, like "in acme/api: fix the failing login test" — starts the agent that makes the change, opens the PR and loops review; the same for a PR review ("review <PR URL>") and a web-research question ("compare X and Y on the web"). NEVER tell the person to post a new top-level message, re-post, re-ask elsewhere or retype their request — their words have already been said and the door reads thread replies; and never hand back a command or an \`agent:…\` line to type: describe the ask in their words. Delete an issue only when the user explicitly asked to delete it (closing is an update).`;
|
|
593
616
|
|
|
594
617
|
// The explore agent (docs/reference/specs/agent-explore.md): a long, read-only
|
|
595
618
|
// investigation — "run our CI locally and validate the claims", "how long does
|
|
@@ -13,6 +13,18 @@ import { NO_GRANTS, type Grants, type GrantSet } from "./types.js";
|
|
|
13
13
|
/** Every grant, on every axis. What admins and the local CLI hold. */
|
|
14
14
|
export const ALL_GRANTS: Grants = Object.freeze({ actions: "all", channels: "all", repos: "all" });
|
|
15
15
|
|
|
16
|
+
/** The plane's system actor (record 0064; record 0057 as amended): the run
|
|
17
|
+
* ledger object steers write-preset runs itself — the checkpoint steer and the
|
|
18
|
+
* held turn's reissue land as `run_inbox` rows with sender `plane` — under
|
|
19
|
+
* this one standing grant. Recorded here so the table stays the whole story:
|
|
20
|
+
* the write happens on the object, which holds no other authority. */
|
|
21
|
+
export const PLANE_ACTOR_ID = "plane";
|
|
22
|
+
export const PLANE_GRANTS: Grants = Object.freeze({
|
|
23
|
+
actions: new Set(["steer:write"]),
|
|
24
|
+
channels: "all",
|
|
25
|
+
repos: "all",
|
|
26
|
+
});
|
|
27
|
+
|
|
16
28
|
/** The namespaces a native `grants` key may use (invariant 4). `cli:` is not
|
|
17
29
|
* configurable (the local CLI always holds everything) and `agent:` actors
|
|
18
30
|
* derive their grants from their principal, so neither is listed. */
|
|
@@ -63,6 +63,12 @@ export const POLICY: readonly Rule[] = [
|
|
|
63
63
|
// NAMED run (`authorizeSteerOwner`, authorization item 16a) — the requester,
|
|
64
64
|
// a linked id, or `runs:write` — which no command-door row can see.
|
|
65
65
|
{ action: "steer:write", resource: "command", when: [grant("steer:write")] },
|
|
66
|
+
// The plane's checkpoint steer (record 0064): the system actor `plane`
|
|
67
|
+
// (`PLANE_ACTOR_ID`, a `service` actor holding `PLANE_GRANTS`' one standing
|
|
68
|
+
// `steer:write`) writes the steer into a write-preset run's inbox on the
|
|
69
|
+
// ledger object itself, sender `plane`; this row records that authority in
|
|
70
|
+
// the one table even though the object, not the bot, executes the write.
|
|
71
|
+
{ action: "steer:write", resource: "run", actorKinds: ["service"], when: [grant("steer:write")] },
|
|
66
72
|
|
|
67
73
|
// ── conversations ────────────────────────────────────────────────────────
|
|
68
74
|
// Who may point the bot at another thread (record 0037). Asked for a
|
|
@@ -108,6 +114,11 @@ export const POLICY: readonly Rule[] = [
|
|
|
108
114
|
// read is never a chat baseline, like the costs reads.
|
|
109
115
|
{ action: "providers:read", resource: "command", when: [grant("providers:read")] },
|
|
110
116
|
|
|
117
|
+
// ── pull requests ──────────────────────────────────────────────────
|
|
118
|
+
// `pulls rebase` force-pushes the pipeline's branches and can start a paid
|
|
119
|
+
// fix round (record 0071): a grant, never a chat baseline.
|
|
120
|
+
{ action: "pulls:write", resource: "command", when: [grant("pulls:write")] },
|
|
121
|
+
|
|
111
122
|
// ── repos ────────────────────────────────────────────────────────────────
|
|
112
123
|
{ action: "repo:read", resource: "command", when: [grant("repo:read")] },
|
|
113
124
|
{ action: "repo:write", resource: "repo", when: [grant("repo:write")] },
|
|
@@ -150,6 +161,10 @@ export const POLICY: readonly Rule[] = [
|
|
|
150
161
|
// channel-config right — whoever may set the channel may set a thread in it;
|
|
151
162
|
// never a baseline, so membership alone admits nobody.
|
|
152
163
|
{ action: "config:write", resource: "config-scope", resourceKind: "thread", when: [grant("config:write")] },
|
|
164
|
+
// The ORG scope (`config set org --pulls.…`, record 0071) — and a repository
|
|
165
|
+
// scope, the org setting's per-repository slice — affects every requester:
|
|
166
|
+
// the same `config:write` grant, never a baseline.
|
|
167
|
+
{ action: "config:write", resource: "config-scope", resourceKind: "org", when: [grant("config:write")] },
|
|
153
168
|
// A user edits only their own scope.
|
|
154
169
|
{ action: "config:write", resource: "config-scope", resourceKind: "user", when: [IS_SELF] },
|
|
155
170
|
// The author binding's one trusted write (record 0062, authorization.md item
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
// Workflow-driven coordinator and the deploy Workers can bundle it — the
|
|
17
17
|
// dependency runs registry → budgets, never the reverse.
|
|
18
18
|
|
|
19
|
+
export const SECOND_MS = 1_000;
|
|
20
|
+
|
|
21
|
+
export const secondsToMs = (seconds: number): number => seconds * SECOND_MS;
|
|
22
|
+
|
|
19
23
|
export const MINUTE_MS = 60_000;
|
|
20
24
|
|
|
21
25
|
/** Minutes → milliseconds, for a duration a request names in minutes (a drain's
|
|
@@ -109,6 +113,14 @@ export const DRAIN = {
|
|
|
109
113
|
marginMinutes: 5,
|
|
110
114
|
maxMinutes: 90,
|
|
111
115
|
defaultMinutes: 60,
|
|
116
|
+
/** How long a held drain waits for a container's post-deploy cycle before
|
|
117
|
+
* reopening the fleet anyway with the stale container named (issue 2044):
|
|
118
|
+
* the platform replaces container processes three to ten minutes after a
|
|
119
|
+
* deploy (docs/reference/specs/resident-repos.md item 65), so a cycle that
|
|
120
|
+
* has not landed within this bound is not coming on its own — the gate
|
|
121
|
+
* asked for the cycle, so it owns the outcome and never holds the fleet
|
|
122
|
+
* closed to the drain's whole `until` on a report nothing will send. */
|
|
123
|
+
cycleBoundMinutes: 10,
|
|
112
124
|
} as const;
|
|
113
125
|
|
|
114
126
|
/** How long an intake receipt row is kept on the run history object
|
|
@@ -143,7 +155,7 @@ export type Preset = LoopPreset | "ship";
|
|
|
143
155
|
* that pushes at minute 29 and then runs the repo's whole gate (seven to ten
|
|
144
156
|
* minutes on a resident) was cut at 45 one run in twelve, so the ask is
|
|
145
157
|
* double the 90th percentile (31.5) with the gate inside it. Ship's holds
|
|
146
|
-
* its own loop at the default rounds with a
|
|
158
|
+
* its own loop at the default rounds with a findings round at that ask, not at
|
|
147
159
|
* its floor (`fit` proves the floor case; the ask leaves room above it). */
|
|
148
160
|
export const ASKS: Readonly<Record<Preset, number>> = {
|
|
149
161
|
general: 5,
|
|
@@ -155,17 +167,18 @@ export const ASKS: Readonly<Record<Preset, number>> = {
|
|
|
155
167
|
conductor: 120,
|
|
156
168
|
};
|
|
157
169
|
|
|
158
|
-
/** The rounds a ship loop is made of. `
|
|
170
|
+
/** The rounds a ship loop is made of. `findings` is a coding child handed the
|
|
159
171
|
* review's findings; `merge` is the runner's wait on the guards. */
|
|
160
|
-
export type RoundKind = "coding" | "review" | "
|
|
172
|
+
export type RoundKind = "coding" | "review" | "findings" | "merge";
|
|
161
173
|
|
|
162
174
|
/** The least lease in which a round does useful work, in minutes. A carve that
|
|
163
175
|
* falls under the floor is refused rather than dispatched: a two-minute
|
|
164
|
-
* review or
|
|
165
|
-
* Review's is the ledger's 90th percentile of completed reviews (5.1
|
|
166
|
-
* 181); coding's is the least a
|
|
167
|
-
* gate alone takes seven to ten minutes on a resident, and a
|
|
168
|
-
* run it finishes nothing its contract asks; the merge
|
|
176
|
+
* review or findings round costs an attach and a model turn and finishes
|
|
177
|
+
* nothing. Review's is the ledger's 90th percentile of completed reviews (5.1
|
|
178
|
+
* min over 181); coding's is the least a findings round can run the repo's
|
|
179
|
+
* gate in — the gate alone takes seven to ten minutes on a resident, and a
|
|
180
|
+
* round that cannot run it finishes nothing its contract asks; the merge
|
|
181
|
+
* wait's is a guess
|
|
169
182
|
* until the ledger says. */
|
|
170
183
|
export const PRESET_FLOORS: Readonly<Record<LoopPreset, number>> = {
|
|
171
184
|
general: 2,
|
|
@@ -177,7 +190,7 @@ export const PRESET_FLOORS: Readonly<Record<LoopPreset, number>> = {
|
|
|
177
190
|
};
|
|
178
191
|
export const FLOORS: Readonly<Record<RoundKind, number>> = {
|
|
179
192
|
coding: PRESET_FLOORS.coding,
|
|
180
|
-
|
|
193
|
+
findings: PRESET_FLOORS.coding,
|
|
181
194
|
review: PRESET_FLOORS.review,
|
|
182
195
|
merge: 10,
|
|
183
196
|
};
|
|
@@ -186,6 +199,12 @@ export const FLOORS: Readonly<Record<RoundKind, number>> = {
|
|
|
186
199
|
* when the remainder allows it. */
|
|
187
200
|
export const MERGE_WAIT_ASK_MINUTES = 60;
|
|
188
201
|
|
|
202
|
+
/** The sweep's bounds (record 0071, mechanism two): the short lease of the one
|
|
203
|
+
* model round a conflicting pull request may buy, and the spend that round is
|
|
204
|
+
* capped at per pull request — a round refused at either bound ends the pull
|
|
205
|
+
* request's line with the conflict named, never a retry loop. */
|
|
206
|
+
export const PULL_SWEEP = { leaseMinutes: 15, spendCapUsd: 5 } as const;
|
|
207
|
+
|
|
189
208
|
/** The provider retry ladder (issue 1932): the backoff before each retry of a
|
|
190
209
|
* transient model-call failure — a gateway 5xx, a stream cut before
|
|
191
210
|
* `message_stop`, a gateway timeout. Three attempts with growing waits,
|
|
@@ -208,7 +227,15 @@ export const SHIP_WAIT = { marginMinutes: 5, chunkMinutes: 5, mergeChunkMinutes:
|
|
|
208
227
|
* it, a reservation's window, and the default re-ask cadence — how often a
|
|
209
228
|
* silent resident something waits on is probed (`plane.reaskMinutes`
|
|
210
229
|
* overrides it per deployment). */
|
|
211
|
-
export const PLANE = {
|
|
230
|
+
export const PLANE = {
|
|
231
|
+
recentMinutes: 60,
|
|
232
|
+
reservationMinutes: 2,
|
|
233
|
+
reaskMinutes: 2,
|
|
234
|
+
/** A coding round with no pushed head past this is steered once (record 0064, "The backpressure contract"). */
|
|
235
|
+
noPushMinutes: 15,
|
|
236
|
+
/** The line an in-flight call with NO declared bound is judged against for the same steer. */
|
|
237
|
+
noBoundMinutes: 20,
|
|
238
|
+
} as const;
|
|
212
239
|
|
|
213
240
|
/** The named amounts a lease holds back, in minutes. Each stands for a step
|
|
214
241
|
* every run or round pays: `provision` is attach and restore before the
|
|
@@ -328,6 +355,12 @@ export function provisionalBearerExpiresAt(now: number, leaseMinutes: number): n
|
|
|
328
355
|
return now + (ALLOWANCES.provision + leaseMinutes + ALLOWANCES.bearerGrace) * MINUTE_MS;
|
|
329
356
|
}
|
|
330
357
|
|
|
358
|
+
/** The e2b credential-write exec's own timeout: the store-file write is one
|
|
359
|
+
* `printf` plus a `git config` reset, so a minute is generous headroom while
|
|
360
|
+
* still failing fast when the micro-VM is wedged
|
|
361
|
+
* (docs/reference/specs/execution.md item 5). */
|
|
362
|
+
export const SANDBOX_CREDENTIAL_WRITE_TIMEOUT_MS = MINUTE_MS;
|
|
363
|
+
|
|
331
364
|
/** The per-command bash budget's rows: the default when a call names none, the
|
|
332
365
|
* ceiling a call may raise it to, and the floor under which a number is a
|
|
333
366
|
* typo (docs/reference/specs/execution.md item 11). */
|
|
@@ -361,19 +394,20 @@ export interface RoundPosition {
|
|
|
361
394
|
}
|
|
362
395
|
|
|
363
396
|
/** The rounds of a loop in order: the coding round, then a review and, after
|
|
364
|
-
* every review but the last, a
|
|
397
|
+
* every review but the last, a findings round, then the merge wait. */
|
|
365
398
|
export function loopRounds(loop: Loop): RoundKind[] {
|
|
366
399
|
const rounds: RoundKind[] = ["coding"];
|
|
367
400
|
for (let i = 0; i < loop.maxRounds; i++) {
|
|
368
401
|
rounds.push("review");
|
|
369
|
-
if (i < loop.maxRounds - 1) rounds.push("
|
|
402
|
+
if (i < loop.maxRounds - 1) rounds.push("findings");
|
|
370
403
|
}
|
|
371
404
|
rounds.push("merge");
|
|
372
405
|
return rounds;
|
|
373
406
|
}
|
|
374
407
|
|
|
375
408
|
/** Where a round sits in `loopRounds`: the coding round first; review round
|
|
376
|
-
* `n` (counted from 1) and the
|
|
409
|
+
* `n` (counted from 1) and the findings round that follows it at `2n − 1`
|
|
410
|
+
* and `2n`; the
|
|
377
411
|
* merge wait last. The ship coordinator numbers its rounds this way, so the
|
|
378
412
|
* reserve it carves with is the one the fit assumed. */
|
|
379
413
|
export function loopPosition(loop: Loop, kind: RoundKind, n = 0): number {
|
|
@@ -382,7 +416,7 @@ export function loopPosition(loop: Loop, kind: RoundKind, n = 0): number {
|
|
|
382
416
|
return 0;
|
|
383
417
|
case "review":
|
|
384
418
|
return 2 * n - 1;
|
|
385
|
-
case "
|
|
419
|
+
case "findings":
|
|
386
420
|
return 2 * n;
|
|
387
421
|
case "merge":
|
|
388
422
|
return 2 * loop.maxRounds;
|
|
@@ -403,7 +437,7 @@ export function carveChildOfParent(
|
|
|
403
437
|
}
|
|
404
438
|
|
|
405
439
|
/** The minutes a round holds back for what must follow it in the loop: the
|
|
406
|
-
* floor plus provisioning of every later review and
|
|
440
|
+
* floor plus provisioning of every later review and findings round, and the merge
|
|
407
441
|
* wait's floor. A merge holds nothing back; a round with no loop (a
|
|
408
442
|
* conductor's child) holds nothing back either. */
|
|
409
443
|
export function reserveMinutes(
|
|
@@ -419,12 +453,12 @@ export function reserveMinutes(
|
|
|
419
453
|
return reserve;
|
|
420
454
|
}
|
|
421
455
|
|
|
422
|
-
/** What a round asks for: the preset's ask for a coding or
|
|
456
|
+
/** What a round asks for: the preset's ask for a coding or findings round, the
|
|
423
457
|
* review's for a review, the merge wait's own for the merge. */
|
|
424
458
|
export function roundAskMinutes(kind: RoundKind): number {
|
|
425
459
|
switch (kind) {
|
|
426
460
|
case "coding":
|
|
427
|
-
case "
|
|
461
|
+
case "findings":
|
|
428
462
|
return ASKS.coding;
|
|
429
463
|
case "review":
|
|
430
464
|
return ASKS.review;
|
|
@@ -233,6 +233,26 @@ export interface ChecksSettledPayload {
|
|
|
233
233
|
settledAt: number;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/** The event the merge watch sends a waiting unit when its pull request
|
|
237
|
+
* merged, whoever merged (record 0071, mechanism three): the unit ends
|
|
238
|
+
* `merged` by other. The type names the unit within the instance, the
|
|
239
|
+
* nudge's alphabet and clip rule. */
|
|
240
|
+
export const PULL_MERGED_EVENT_PREFIX = "pr-merged-";
|
|
241
|
+
export function pullMergedEventType(key: { instanceId: string; unit: string }): string {
|
|
242
|
+
const suffix = `-${key.unit}`;
|
|
243
|
+
const room = 100 - PULL_MERGED_EVENT_PREFIX.length - suffix.length;
|
|
244
|
+
return `${PULL_MERGED_EVENT_PREFIX}${key.instanceId.slice(0, room)}${suffix}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** What the merged event carries — the pull request and the merge's facts;
|
|
248
|
+
* the waiting unit confirms through its own pr-check before it acts. */
|
|
249
|
+
export interface PullMergedPayload {
|
|
250
|
+
repo: string;
|
|
251
|
+
number: number;
|
|
252
|
+
sha: string;
|
|
253
|
+
mergedAt: string;
|
|
254
|
+
}
|
|
255
|
+
|
|
236
256
|
/** What the event carries — ids, a status and a clock; the parent confirms
|
|
237
257
|
* through `read-record` before it acts, so nothing more rides here. */
|
|
238
258
|
export interface RunFinishedPayload {
|
|
@@ -379,17 +399,17 @@ export interface UnitIdle {
|
|
|
379
399
|
wakes: number;
|
|
380
400
|
}
|
|
381
401
|
|
|
382
|
-
/** One unit of the plan an instance runs (a task string is a generated plan of
|
|
383
|
-
* one unit, `U1`): its branch, the units it waits on, and — as the runner
|
|
384
|
-
* reaches it — its thread, its pull request, the round boundaries the card
|
|
385
|
-
* drew and how it ended. One row a person can read for "what happened to this
|
|
386
|
-
* unit". */
|
|
387
402
|
/** What the severity gate caught on a round: the level in force and the gated findings as `id (severity)`. */
|
|
388
403
|
export interface RoundGate {
|
|
389
404
|
level: AddressSeverity;
|
|
390
405
|
findings: string[];
|
|
391
406
|
}
|
|
392
407
|
|
|
408
|
+
/** One unit of the plan an instance runs (a task string is a generated plan of
|
|
409
|
+
* one unit, `U1`): its branch, the units it waits on, and — as the runner
|
|
410
|
+
* reaches it — its thread, its pull request, the round boundaries the card
|
|
411
|
+
* drew and how it ended. One row a person can read for "what happened to this
|
|
412
|
+
* unit". */
|
|
393
413
|
export interface CoordinatorUnit {
|
|
394
414
|
instanceId: string;
|
|
395
415
|
/** `U<n>` as the plan spells it. */
|
|
@@ -606,6 +626,26 @@ export async function sendChecksSettled(
|
|
|
606
626
|
}
|
|
607
627
|
}
|
|
608
628
|
|
|
629
|
+
/** The one send per merged fact the watch observed (record 0071): best effort
|
|
630
|
+
* like `sendRunFinished` — a refusal is answered, never thrown; the waiting
|
|
631
|
+
* unit's bounded wait re-asks its own pr-check on its next chunk. */
|
|
632
|
+
export async function sendPullMerged(
|
|
633
|
+
workflow: WorkflowSender | undefined,
|
|
634
|
+
key: { instanceId: string; unit: string },
|
|
635
|
+
payload: PullMergedPayload,
|
|
636
|
+
): Promise<RunFinishedSend> {
|
|
637
|
+
const instance = key.instanceId;
|
|
638
|
+
if (!workflow) return { kind: "no-binding", instance };
|
|
639
|
+
const type = pullMergedEventType(key);
|
|
640
|
+
try {
|
|
641
|
+
const handle = await workflow.get(instance);
|
|
642
|
+
await handle.sendEvent({ type, payload });
|
|
643
|
+
return { kind: "sent", instance, type };
|
|
644
|
+
} catch (err) {
|
|
645
|
+
return { kind: "failed", instance, type, reason: err instanceof Error ? err.message : String(err) };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
609
649
|
/** One deploy-roll signal to the child's parent (run-history item 47a): best
|
|
610
650
|
* effort like `sendRunFinished` — a refusal is answered, never thrown; a lost
|
|
611
651
|
* send costs the wait a chunk, never the round. */
|
|
@@ -61,7 +61,7 @@ import {
|
|
|
61
61
|
type UnitEnding,
|
|
62
62
|
type UnitPipelineState,
|
|
63
63
|
stepPrefixOf,
|
|
64
|
-
type
|
|
64
|
+
type LeaseSegmentProgress,
|
|
65
65
|
} from "../ship/coordinator.js";
|
|
66
66
|
import {
|
|
67
67
|
checksSettledEventType,
|
|
@@ -451,12 +451,15 @@ function prCheckReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
451
451
|
* ask's answer carries `retried` instead: whether the re-run was dispatched,
|
|
452
452
|
* so the machine never waits on a head an undispatched re-run left unchanged. */
|
|
453
453
|
function checksReturn(step: string, a: BotAnswer): StepReturn {
|
|
454
|
-
const { ok, checks, retried, at } = a.body;
|
|
454
|
+
const { ok, checks, draft, retried, at } = a.body;
|
|
455
455
|
if (ok !== true) throw new UnreadableAnswer("checks", a, "ok");
|
|
456
456
|
return {
|
|
457
457
|
type: "checks",
|
|
458
458
|
step,
|
|
459
459
|
...(isRoundChecks(checks) ? { checks } : {}),
|
|
460
|
+
// The pull request is a draft (issue 2063): the machine's table holds the
|
|
461
|
+
// unit for the ready event instead of merging or ending without a cause.
|
|
462
|
+
...(draft === true ? { draft: true } : {}),
|
|
460
463
|
...(typeof retried === "boolean" ? { retried } : {}),
|
|
461
464
|
at,
|
|
462
465
|
};
|
|
@@ -691,7 +694,7 @@ async function runUnit(
|
|
|
691
694
|
instanceId: string,
|
|
692
695
|
node: PlanUnitNode,
|
|
693
696
|
plan: PlanFacts,
|
|
694
|
-
session?:
|
|
697
|
+
session?: LeaseSegmentProgress,
|
|
695
698
|
): Promise<UnitEnding> {
|
|
696
699
|
const unit = node.id;
|
|
697
700
|
// A renewal's segment names its steps under the segment (`U10/s2/…`), so
|