@coreplane/switchboard 1.260.0 → 1.260.2
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/bin/switchboard.js +15 -11
- package/dist/assets/config/config.example.yaml +1 -0
- package/dist/assets/deploy/cloudflare/worker.ts +17 -56
- package/dist/assets/deploy/cloudflare/wrangler.template.jsonc +6 -0
- package/dist/assets/deploy/cloudflare-memory/backgroundTasks.ts +25 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +36 -16
- package/dist/assets/deploy/cloudflare-resident/worker.ts +39 -18
- package/dist/assets/deploy/profile.example.json +2 -1
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +2 -2
- package/dist/assets/src/core/budgets.ts +19 -6
- package/dist/assets/src/core/coordinator/driver.ts +47 -9
- package/dist/assets/src/core/pipelineStanding.ts +2 -0
- package/dist/assets/src/core/reviewedHead.ts +16 -2
- package/dist/assets/src/core/runEvents.ts +9 -1
- package/dist/assets/src/core/runLedger/types.ts +3 -3
- package/dist/assets/src/core/runRecord.ts +28 -8
- package/dist/assets/src/core/ship/coordinator.ts +248 -44
- package/dist/assets/src/core/ship/renewal.ts +2 -0
- package/dist/assets/src/deploy/liveGate.ts +8 -0
- package/dist/assets/src/deploy/profile.ts +8 -0
- package/dist/assets/src/deploy/restart.ts +49 -35
- package/dist/assets/web/dist/.vite/manifest.json +67 -67
- package/dist/assets/web/dist/assets/{DeliveryPage-DvMrWUg7.js → DeliveryPage-62qCSLvk.js} +1 -1
- package/dist/assets/web/dist/assets/{HomePage-CcGEJ4w0.js → HomePage-Dk3HRBc3.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-BzGVxDYs.js → PendingTurnRow-CeTGOXUy.js} +1 -1
- package/dist/assets/web/dist/assets/{PlanePage-Arj9cyd5.js → PlanePage-RD0M6O6W.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-CBPPe4Sj.js → ResidentDetailPage-Dl2KId95.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-CEpXgGo7.js → ResidentsIndexPage-DBy43I1R.js} +1 -1
- package/dist/assets/web/dist/assets/{RunFoldRow-BJq7tnLr.js → RunFoldRow-CwHDL-Mo.js} +1 -1
- package/dist/assets/web/dist/assets/{RunRoutePage-DWiny7LN.js → RunRoutePage-Du1HqLZs.js} +3 -3
- package/dist/assets/web/dist/assets/{RunsIndexPage-Cxufw5sV.js → RunsIndexPage-BZphy3Hr.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-SBcuDdVg.js → ScheduledPage-B7jh898t.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-DrD_vWdp.js → SettingsPage-3uLEsiiE.js} +1 -1
- package/dist/assets/web/dist/assets/{SilentTurn-Bjl7EPEN.js → SilentTurn-CKxSxnQC.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-BqteQTav.js → StatusDot-x7vcK0iE.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-KRqJXEPN.js → Tooltip-wVXCWhMp.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-Cv28FV4I.js → UnitRoutePage-V5BXK7FH.js} +1 -1
- package/dist/assets/web/dist/assets/budgets-KNNkT4PZ.js +1 -0
- package/dist/assets/web/dist/assets/{dist-CTASUSno.js → dist-C-XGnvM4.js} +1 -1
- package/dist/assets/web/dist/assets/{indexRow-B1YBy_IL.js → indexRow-Dxg7C8s9.js} +1 -1
- package/dist/assets/web/dist/assets/{main-eTAe9-hk.js → main-DAL--hyj.js} +2 -2
- package/dist/assets/web/dist/assets/sseReplay-BqgOggyQ.js +11 -0
- package/dist/cli.js +1405 -436
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/budgets-DQXVllsm.js +0 -1
- package/dist/assets/web/dist/assets/sseReplay-BcsNbn2j.js +0 -11
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
type PlanCursor,
|
|
52
52
|
type UnitStatus,
|
|
53
53
|
renderUnitReport,
|
|
54
|
+
reenterApprovedRebase,
|
|
54
55
|
settleUnit,
|
|
55
56
|
startUnit,
|
|
56
57
|
type ChildFacts,
|
|
@@ -114,6 +115,7 @@ export type CoordinatorStepRoute =
|
|
|
114
115
|
| "unit-wake"
|
|
115
116
|
| "checks"
|
|
116
117
|
| "merge"
|
|
118
|
+
| "rebase"
|
|
117
119
|
| "finish";
|
|
118
120
|
|
|
119
121
|
/** What a step stores: the bot's reply as the wire carried it — its status and
|
|
@@ -469,7 +471,7 @@ function prCheckReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
469
471
|
* ask's answer carries `retried` instead: whether the re-run was dispatched,
|
|
470
472
|
* so the machine never waits on a head an undispatched re-run left unchanged. */
|
|
471
473
|
function checksReturn(step: string, a: BotAnswer): StepReturn {
|
|
472
|
-
const { ok, checks, draft, retried, at } = a.body;
|
|
474
|
+
const { ok, checks, draft, retried, refired, at } = a.body;
|
|
473
475
|
if (ok !== true) throw new UnreadableAnswer("checks", a, "ok");
|
|
474
476
|
return {
|
|
475
477
|
type: "checks",
|
|
@@ -479,6 +481,7 @@ function checksReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
479
481
|
// unit for the ready event instead of merging or ending without a cause.
|
|
480
482
|
...(draft === true ? { draft: true } : {}),
|
|
481
483
|
...(typeof retried === "boolean" ? { retried } : {}),
|
|
484
|
+
...(typeof refired === "boolean" ? { refired } : {}),
|
|
482
485
|
at,
|
|
483
486
|
};
|
|
484
487
|
}
|
|
@@ -489,7 +492,10 @@ const isRoundChecks = (v: unknown): v is RoundChecks =>
|
|
|
489
492
|
Array.isArray(v.pending) &&
|
|
490
493
|
v.pending.every((n: unknown) => typeof n === "string") &&
|
|
491
494
|
Array.isArray(v.failed) &&
|
|
492
|
-
v.failed.every((f: unknown) => isRecord(f) && typeof f.name === "string" && typeof f.conclusion === "string")
|
|
495
|
+
v.failed.every((f: unknown) => isRecord(f) && typeof f.name === "string" && typeof f.conclusion === "string") &&
|
|
496
|
+
(v.required === undefined ||
|
|
497
|
+
(Array.isArray(v.required) && v.required.every((n: unknown) => typeof n === "string"))) &&
|
|
498
|
+
(v.expected === undefined || (Array.isArray(v.expected) && v.expected.every((n: unknown) => typeof n === "string")));
|
|
493
499
|
|
|
494
500
|
function mergeReturn(step: string, a: BotAnswer): StepReturn {
|
|
495
501
|
const { ok, outcome, by, sha, mergedAt, reason, at } = a.body;
|
|
@@ -501,13 +507,26 @@ function mergeReturn(step: string, a: BotAnswer): StepReturn {
|
|
|
501
507
|
return { type: "merge", step, outcome: "merged", sha, at };
|
|
502
508
|
if (
|
|
503
509
|
ok === true &&
|
|
504
|
-
(outcome === "pending" ||
|
|
510
|
+
(outcome === "pending" ||
|
|
511
|
+
outcome === "refused" ||
|
|
512
|
+
outcome === "conflict" ||
|
|
513
|
+
outcome === "enqueued" ||
|
|
514
|
+
outcome === "removed") &&
|
|
505
515
|
typeof reason === "string"
|
|
506
516
|
)
|
|
507
517
|
return { type: "merge", step, outcome, reason, at };
|
|
508
518
|
throw new UnreadableAnswer("merge", a, "outcome");
|
|
509
519
|
}
|
|
510
520
|
|
|
521
|
+
function rebaseReturn(step: string, a: BotAnswer): StepReturn {
|
|
522
|
+
const { ok, outcome, headSha, reason, at } = a.body;
|
|
523
|
+
if (ok === true && (outcome === "carried" || outcome === "changed") && typeof headSha === "string")
|
|
524
|
+
return { type: "rebase", step, outcome, headSha, at };
|
|
525
|
+
if (ok === true && (outcome === "conflict" || outcome === "refused") && typeof reason === "string")
|
|
526
|
+
return { type: "rebase", step, outcome, reason, at };
|
|
527
|
+
throw new UnreadableAnswer("rebase", a, "outcome");
|
|
528
|
+
}
|
|
529
|
+
|
|
511
530
|
// ---- the steps ----------------------------------------------------------------------------------------
|
|
512
531
|
|
|
513
532
|
/** One bot call inside a step: a reply that is not the bot's answer and a
|
|
@@ -670,8 +689,8 @@ async function perform(
|
|
|
670
689
|
}
|
|
671
690
|
case "checks":
|
|
672
691
|
// The round's checks step (record 0055): the bot reads the check runs at
|
|
673
|
-
// the reviewed head with the merge door's own reading — or
|
|
674
|
-
//
|
|
692
|
+
// the reviewed head with the merge door's own reading — or performs one
|
|
693
|
+
// of the step's bounded recovery effects first.
|
|
675
694
|
return checksReturn(
|
|
676
695
|
action.step,
|
|
677
696
|
answerOf(
|
|
@@ -682,6 +701,7 @@ async function perform(
|
|
|
682
701
|
prNumber: action.prNumber,
|
|
683
702
|
headSha: action.headSha,
|
|
684
703
|
...(action.retry !== undefined ? { retry: action.retry } : {}),
|
|
704
|
+
...(action.refire === true ? { refire: true } : {}),
|
|
685
705
|
}),
|
|
686
706
|
),
|
|
687
707
|
),
|
|
@@ -701,6 +721,16 @@ async function perform(
|
|
|
701
721
|
),
|
|
702
722
|
),
|
|
703
723
|
);
|
|
724
|
+
case "rebase":
|
|
725
|
+
return rebaseReturn(
|
|
726
|
+
action.step,
|
|
727
|
+
answerOf(
|
|
728
|
+
"rebase",
|
|
729
|
+
await step.do(action.step, STEP_CONFIG, () =>
|
|
730
|
+
call(bot, "rebase", { ...tag, prNumber: action.prNumber, headSha: action.headSha }),
|
|
731
|
+
),
|
|
732
|
+
),
|
|
733
|
+
);
|
|
704
734
|
}
|
|
705
735
|
}
|
|
706
736
|
|
|
@@ -833,7 +863,7 @@ async function runUnit(
|
|
|
833
863
|
let notes = 0;
|
|
834
864
|
let endedAt: number | undefined;
|
|
835
865
|
try {
|
|
836
|
-
for (;;) {
|
|
866
|
+
pipeline: for (;;) {
|
|
837
867
|
const action = nextAction(state);
|
|
838
868
|
if (action.type === "end") return { ...action.ending, ...(endedAt !== undefined ? { endedAt } : {}) };
|
|
839
869
|
// Keep the current round across its non-round phases (for example merge)
|
|
@@ -887,9 +917,9 @@ async function runUnit(
|
|
|
887
917
|
// The checks at the approved head (record 0055): the report's
|
|
888
918
|
// headline is a claim about them, never "merge-ready" over a red one.
|
|
889
919
|
...(check.pr.checks !== undefined ? { checks: check.pr.checks } : {}),
|
|
890
|
-
// The ready state beside them (agent-ship item 9): a
|
|
891
|
-
//
|
|
892
|
-
// approved-but-not-merge-ready
|
|
920
|
+
// The ready state beside them (agent-ship item 9): a conflict
|
|
921
|
+
// feeds the runner re-entry below, while an unsquashed fix-up
|
|
922
|
+
// commit remains an approved-but-not-merge-ready report.
|
|
893
923
|
...(check.pr.mergeableState !== undefined ? { mergeableState: check.pr.mergeableState } : {}),
|
|
894
924
|
...(check.pr.fixupCommits !== undefined ? { fixupCommits: check.pr.fixupCommits } : {}),
|
|
895
925
|
// The base's merge-queue rule (issue 2011): the merge:person
|
|
@@ -902,6 +932,14 @@ async function runUnit(
|
|
|
902
932
|
// the report simply omits the fact
|
|
903
933
|
}
|
|
904
934
|
}
|
|
935
|
+
// A person-merge pipeline used to publish this dirty state as its
|
|
936
|
+
// ending and hand the pull request to the sweep. It remains the
|
|
937
|
+
// owner instead: rung one runs now, and only a conflict buys a coding
|
|
938
|
+
// child under this run's remaining lease.
|
|
939
|
+
if (note.ending.kind === "merge_ready" && endFacts?.mergeableState === "dirty") {
|
|
940
|
+
state = reenterApprovedRebase(state);
|
|
941
|
+
continue pipeline;
|
|
942
|
+
}
|
|
905
943
|
// The last coding child's run is named so the bot can put its handoff
|
|
906
944
|
// — the deviations it recorded — on the unit's board issue beside the
|
|
907
945
|
// ending (agent-ship item 14).
|
|
@@ -94,6 +94,7 @@ export const ROUND_STAGE = {
|
|
|
94
94
|
request_changes: "fix",
|
|
95
95
|
no_verdict: "hold",
|
|
96
96
|
checks_failed: "fix",
|
|
97
|
+
checks_restarted: "approved",
|
|
97
98
|
transient: "hold",
|
|
98
99
|
enqueued: "approved",
|
|
99
100
|
dequeued: "fix",
|
|
@@ -174,6 +175,7 @@ export const ROUND_OUTCOME_WORDS = {
|
|
|
174
175
|
request_changes: "changes requested",
|
|
175
176
|
no_verdict: "no verdict",
|
|
176
177
|
checks_failed: "checks failed",
|
|
178
|
+
checks_restarted: "checks restarted",
|
|
177
179
|
transient: "retried",
|
|
178
180
|
enqueued: "queued to merge",
|
|
179
181
|
dequeued: "removed from the merge queue",
|
|
@@ -51,6 +51,20 @@ export function sameCommit(a: string, b: string): boolean {
|
|
|
51
51
|
return n >= 7 && a.slice(0, n) === b.slice(0, n);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** A mismatch sentence that cannot print two identical accepted-looking
|
|
55
|
+
* prefixes: it names the authoritative source, both values and their first
|
|
56
|
+
* differing hex position. */
|
|
57
|
+
function mismatchReason(source: "workspace-observed" | "model-reported", reviewed: string, expected: string): string {
|
|
58
|
+
const compared = Math.min(reviewed.length, expected.length);
|
|
59
|
+
let at = 0;
|
|
60
|
+
while (at < compared && reviewed[at] === expected[at]) at += 1;
|
|
61
|
+
const divergence =
|
|
62
|
+
at < compared
|
|
63
|
+
? `they first differ at hex ${at + 1} (${reviewed[at]} ≠ ${expected[at]})`
|
|
64
|
+
: `the ${reviewed.length}-hex reviewed value ends before the ${expected.length}-hex PR head`;
|
|
65
|
+
return `${source} reviewed head ${reviewed} is not the PR head ${expected}; ${divergence}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
54
68
|
/**
|
|
55
69
|
* Decide whether the reviewed head is the PR head. `expected` is the PR head
|
|
56
70
|
* from repo resolution; `observed` the dispatcher's own `git rev-parse HEAD`
|
|
@@ -63,7 +77,7 @@ export function checkReviewedHead(input: { expected?: string; observed?: string;
|
|
|
63
77
|
if (observed) {
|
|
64
78
|
return sameCommit(observed, expected)
|
|
65
79
|
? { ok: true, head: observed, source: "observed" }
|
|
66
|
-
: { ok: false, reason:
|
|
80
|
+
: { ok: false, reason: mismatchReason("workspace-observed", observed, expected) };
|
|
67
81
|
}
|
|
68
82
|
const reported = normalizeHead(input.reported);
|
|
69
83
|
if (!reported)
|
|
@@ -73,5 +87,5 @@ export function checkReviewedHead(input: { expected?: string; observed?: string;
|
|
|
73
87
|
};
|
|
74
88
|
return sameCommit(reported, expected)
|
|
75
89
|
? { ok: true, head: reported, source: "reported" }
|
|
76
|
-
: { ok: false, reason:
|
|
90
|
+
: { ok: false, reason: mismatchReason("model-reported", reported, expected) };
|
|
77
91
|
}
|
|
@@ -294,6 +294,10 @@ export type RunNoteKind =
|
|
|
294
294
|
* there was nothing to push), so a re-issue starts from the partial work
|
|
295
295
|
* (docs/reference/specs/agent-ship.md item 8). */
|
|
296
296
|
| "budget_salvage"
|
|
297
|
+
/** A coordinator's coding child ended failed, stopped or interrupted with
|
|
298
|
+
* work in its tree: before teardown the run loop made a WIP commit and
|
|
299
|
+
* pushed the unit branch, or recorded why no push was possible. */
|
|
300
|
+
| "work_salvage"
|
|
297
301
|
/** A pi run's compaction failed for good — the provider refused the summary
|
|
298
302
|
* (harness-pi.md item 7) — and the run loop treated it as a checkpoint
|
|
299
303
|
* signal: the tracked changes were committed and pushed to the run's own
|
|
@@ -363,6 +367,7 @@ export const RUN_NOTE_KINDS = [
|
|
|
363
367
|
"tool_unnamed",
|
|
364
368
|
"directory_reached",
|
|
365
369
|
"budget_salvage",
|
|
370
|
+
"work_salvage",
|
|
366
371
|
"compaction_salvage",
|
|
367
372
|
"stuck_loop",
|
|
368
373
|
"decline_cascade",
|
|
@@ -432,6 +437,9 @@ export type ShipRoundOutcome =
|
|
|
432
437
|
* 0055): the failures become check findings and the findings step runs as
|
|
433
438
|
* for any changes-requested round. */
|
|
434
439
|
| "checks_failed"
|
|
440
|
+
/** No required check appeared during the checks step's grace chunk, so the
|
|
441
|
+
* runner re-fired the pull_request event once by closing and reopening it. */
|
|
442
|
+
| "checks_restarted"
|
|
435
443
|
/** The round's coding child died on a provider transient with nothing
|
|
436
444
|
* pushed (issue 1932): the first such boundary marks the round's one
|
|
437
445
|
* re-run, a second the `transient` ending. */
|
|
@@ -1096,7 +1104,7 @@ export type RunEvent =
|
|
|
1096
1104
|
* `model` is the ref the run uses, resolved from a model the person
|
|
1097
1105
|
* named in plain words (the plain-words model unit) — applied at
|
|
1098
1106
|
* directive precedence. Additive: unknown → no model. */
|
|
1099
|
-
binds?: ReadonlyArray<{ line: string; reason: string; model?: string; confirmed?: true }>;
|
|
1107
|
+
binds?: ReadonlyArray<{ line: string; reason: string; model?: string; repo?: string; confirmed?: true }>;
|
|
1100
1108
|
question?: string;
|
|
1101
1109
|
/** A question's proposed line, redacted and cut like the receipt — what
|
|
1102
1110
|
* the next turn's "yes" binds (`bindFromAnswer`). */
|
|
@@ -128,9 +128,9 @@ export interface LiveRunMeta {
|
|
|
128
128
|
export type RunState = Record<string, unknown>;
|
|
129
129
|
|
|
130
130
|
/** A hosted ship parent's hosting fact on the row's state (record 0060): the
|
|
131
|
-
* runner instance the run hosts and
|
|
132
|
-
*
|
|
133
|
-
*
|
|
131
|
+
* runner instance the run hosts and its original wall-clock estimate. Reclaim
|
|
132
|
+
* may outlive that estimate while the Workflow or one of its child rows is
|
|
133
|
+
* live; only no durable owner beyond it closes the row. */
|
|
134
134
|
export interface HostingState {
|
|
135
135
|
instanceId: string;
|
|
136
136
|
/** Epoch ms: the hand-off time plus the instance's `caps.maxMinutes` plus
|
|
@@ -289,19 +289,23 @@ export interface RunLease {
|
|
|
289
289
|
loopEndsAt: number;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
/** A head a run pushed, as the record names it (item 2): the branch
|
|
292
|
+
/** A head a run pushed, as the record names it (item 2): the branch, sha,
|
|
293
|
+
* and whether the agent pushed it or the run's mechanical WIP salvage did.
|
|
294
|
+
* `by` is optional for records written before that distinction survived the
|
|
295
|
+
* event fold. */
|
|
293
296
|
export interface PushedHead {
|
|
294
297
|
ref: string;
|
|
295
298
|
sha: string;
|
|
299
|
+
by?: "push" | "salvage";
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
/** The heads a run's events say it pushed — one per branch, the last event's
|
|
299
303
|
* sha winning, in first-seen order — or nothing when it pushed none. */
|
|
300
304
|
export function pushedHeadsOf(events: readonly RunEvent[]): PushedHead[] | undefined {
|
|
301
|
-
const byRef = new Map<string, string>();
|
|
302
|
-
for (const e of events) if (e.type === "pushed_head") byRef.set(e.ref, e.sha);
|
|
305
|
+
const byRef = new Map<string, { sha: string; by: "push" | "salvage" }>();
|
|
306
|
+
for (const e of events) if (e.type === "pushed_head") byRef.set(e.ref, { sha: e.sha, by: e.by });
|
|
303
307
|
if (byRef.size === 0) return undefined;
|
|
304
|
-
return [...byRef].map(([ref,
|
|
308
|
+
return [...byRef].map(([ref, pushed]) => ({ ref, ...pushed }));
|
|
305
309
|
}
|
|
306
310
|
|
|
307
311
|
/** The lease a run's events say its harness started — the first `lease`
|
|
@@ -440,7 +444,7 @@ export interface RunOperatorDecision {
|
|
|
440
444
|
mode: "shadow" | "on";
|
|
441
445
|
outcome: "binds" | "question" | "refusal" | "non_decision";
|
|
442
446
|
reason: string;
|
|
443
|
-
binds?: { line: string; reason: string }[];
|
|
447
|
+
binds?: { line: string; reason: string; model?: string; repo?: string; confirmed?: true }[];
|
|
444
448
|
question?: string;
|
|
445
449
|
/** A question's proposed line — what the next turn's "yes" binds. */
|
|
446
450
|
proposal?: string;
|
|
@@ -468,7 +472,17 @@ export function operatorOfEvents(events: readonly RunEvent[]): RunOperatorDecisi
|
|
|
468
472
|
mode: e.mode,
|
|
469
473
|
outcome: e.outcome,
|
|
470
474
|
reason: e.reason,
|
|
471
|
-
...(e.binds
|
|
475
|
+
...(e.binds
|
|
476
|
+
? {
|
|
477
|
+
binds: e.binds.map((b) => ({
|
|
478
|
+
line: b.line,
|
|
479
|
+
reason: b.reason,
|
|
480
|
+
...(b.model !== undefined ? { model: b.model } : {}),
|
|
481
|
+
...(b.repo !== undefined ? { repo: b.repo } : {}),
|
|
482
|
+
...(b.confirmed ? { confirmed: true as const } : {}),
|
|
483
|
+
})),
|
|
484
|
+
}
|
|
485
|
+
: {}),
|
|
472
486
|
...(e.question !== undefined ? { question: e.question } : {}),
|
|
473
487
|
...(e.proposal !== undefined ? { proposal: e.proposal } : {}),
|
|
474
488
|
...(e.request !== undefined ? { request: e.request } : {}),
|
|
@@ -1019,8 +1033,14 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
1019
1033
|
Array.isArray(r.pushed) &&
|
|
1020
1034
|
r.pushed.every((h) => {
|
|
1021
1035
|
if (typeof h !== "object" || h === null) return false;
|
|
1022
|
-
const { ref, sha } = h as Record<string, unknown>;
|
|
1023
|
-
return
|
|
1036
|
+
const { ref, sha, by } = h as Record<string, unknown>;
|
|
1037
|
+
return (
|
|
1038
|
+
typeof ref === "string" &&
|
|
1039
|
+
ref.length > 0 &&
|
|
1040
|
+
typeof sha === "string" &&
|
|
1041
|
+
/^[0-9a-f]{7,40}$/.test(sha) &&
|
|
1042
|
+
(by === undefined || by === "push" || by === "salvage")
|
|
1043
|
+
);
|
|
1024
1044
|
})
|
|
1025
1045
|
)
|
|
1026
1046
|
)
|