@mutmutco/cli 4.6.21 → 4.6.23
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/main.cjs +784 -51
- package/dist/runbook-sweep.cjs +4 -2
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -710,6 +710,10 @@ function createGitHubClient(options = {}) {
|
|
|
710
710
|
}
|
|
711
711
|
return read();
|
|
712
712
|
},
|
|
713
|
+
async restWithStatus(method, path2, init) {
|
|
714
|
+
const res = await request(method, joinUrl(baseUrl, path2), init);
|
|
715
|
+
return { status: res.status, body: await parseJson(res) };
|
|
716
|
+
},
|
|
713
717
|
async restPaginate(path2, init) {
|
|
714
718
|
const items = [];
|
|
715
719
|
let url = withPerPage(joinUrl(baseUrl, path2));
|
|
@@ -1237,6 +1241,10 @@ var init_house_map = __esm({
|
|
|
1237
1241
|
// allowlisted GitHub REST GET reads for automation (pull files, compare, contents)
|
|
1238
1242
|
run: "devops",
|
|
1239
1243
|
// #9479: one named workflow run — the bounded `run cancel <id>` and `run rerun <id>` writes
|
|
1244
|
+
vars: "devops",
|
|
1245
|
+
// #9780: repository Actions variables — typed get/list/create/update/delete receipts
|
|
1246
|
+
repo: "devops",
|
|
1247
|
+
// #9779: canonical repository typed reads (archive status)
|
|
1240
1248
|
ci: "devops",
|
|
1241
1249
|
// the CI/gate audit
|
|
1242
1250
|
jev: "devops",
|
|
@@ -1616,10 +1624,10 @@ var init_cli_shared = __esm({
|
|
|
1616
1624
|
);
|
|
1617
1625
|
const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args) ?? await ghFilingAppEnv(file, args);
|
|
1618
1626
|
const reportsCredential = !options.env && /(^|[\\/])gh(\.exe)?$/i.test(file) && isAppFilingWrite(args);
|
|
1619
|
-
const tag = async (
|
|
1620
|
-
if (!reportsCredential) return
|
|
1627
|
+
const tag = async (call3, credential) => {
|
|
1628
|
+
if (!reportsCredential) return call3;
|
|
1621
1629
|
try {
|
|
1622
|
-
return { ...await
|
|
1630
|
+
return { ...await call3, credential };
|
|
1623
1631
|
} catch (e) {
|
|
1624
1632
|
if (e && typeof e === "object") e.credential = credential;
|
|
1625
1633
|
throw e;
|
|
@@ -15415,6 +15423,15 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
|
|
|
15415
15423
|
["run cancel", { metadata: "read", actions: "write" }],
|
|
15416
15424
|
// #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
|
|
15417
15425
|
["run rerun", { metadata: "read", actions: "write" }],
|
|
15426
|
+
// #9780: repository Actions variables — get/list mint only Variables:read; create/update/delete
|
|
15427
|
+
// Variables:write. Repository scope only; no secret store, no rename, no workflow dispatch.
|
|
15428
|
+
["vars get", { metadata: "read", variables: "read" }],
|
|
15429
|
+
["vars list", { metadata: "read", variables: "read" }],
|
|
15430
|
+
["vars create", { metadata: "read", variables: "write" }],
|
|
15431
|
+
["vars update", { metadata: "read", variables: "write" }],
|
|
15432
|
+
["vars delete", { metadata: "read", variables: "write" }],
|
|
15433
|
+
// #9779: the archive-status read is a metadata:read-only repository read — nothing broader.
|
|
15434
|
+
["repo archive-status", { metadata: "read" }],
|
|
15418
15435
|
["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }],
|
|
15419
15436
|
// #9614: closing an issue is an issue-state write — it rides the org App (mmi-github-app), so the close
|
|
15420
15437
|
// is attributed to the App instead of the personal account. Nothing broader than issues.
|
|
@@ -17934,7 +17951,45 @@ function jobReportsPullRequestCheck(job) {
|
|
|
17934
17951
|
});
|
|
17935
17952
|
if (!directIf) return true;
|
|
17936
17953
|
const expression = directIf.replace(/^\s*if:\s*/, "").trim().replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "").trim();
|
|
17937
|
-
|
|
17954
|
+
if (/pull_request/i.test(expression)) return true;
|
|
17955
|
+
let rest = expression;
|
|
17956
|
+
let valid = true;
|
|
17957
|
+
const take = (pattern) => {
|
|
17958
|
+
const match = rest.match(pattern);
|
|
17959
|
+
if (!match) return void 0;
|
|
17960
|
+
rest = rest.slice(match[0].length).trimStart();
|
|
17961
|
+
return match[0];
|
|
17962
|
+
};
|
|
17963
|
+
const atom = () => {
|
|
17964
|
+
if (take(/^\(/)) {
|
|
17965
|
+
const possible2 = disjunction();
|
|
17966
|
+
if (!take(/^\)/)) valid = false;
|
|
17967
|
+
return possible2;
|
|
17968
|
+
}
|
|
17969
|
+
if (take(/^(?:github\.event_name\s*==\s*'[^']*'|github\.event_name\s*==\s*"[^"]*"|'[^']*'\s*==\s*github\.event_name|"[^"]*"\s*==\s*github\.event_name)/)) return false;
|
|
17970
|
+
if (take(/^always\(\s*\)/)) return true;
|
|
17971
|
+
if (take(/^needs\.[A-Za-z0-9_-]+\.outputs\.[A-Za-z0-9_-]+\s*(?:==|!=)\s*(?:'[^']*'|"[^"]*")/)) return true;
|
|
17972
|
+
valid = false;
|
|
17973
|
+
return true;
|
|
17974
|
+
};
|
|
17975
|
+
const conjunction = () => {
|
|
17976
|
+
let possible2 = atom();
|
|
17977
|
+
while (take(/^&&/)) {
|
|
17978
|
+
const next = atom();
|
|
17979
|
+
possible2 = possible2 && next;
|
|
17980
|
+
}
|
|
17981
|
+
return possible2;
|
|
17982
|
+
};
|
|
17983
|
+
const disjunction = () => {
|
|
17984
|
+
let possible2 = conjunction();
|
|
17985
|
+
while (take(/^\|\|/)) {
|
|
17986
|
+
const next = conjunction();
|
|
17987
|
+
possible2 = possible2 || next;
|
|
17988
|
+
}
|
|
17989
|
+
return possible2;
|
|
17990
|
+
};
|
|
17991
|
+
const possible = disjunction();
|
|
17992
|
+
return !valid || rest.length > 0 || possible;
|
|
17938
17993
|
}
|
|
17939
17994
|
function workflowTriggersPullRequest(yaml) {
|
|
17940
17995
|
const onBlock = extractOnBlock(yaml);
|
|
@@ -25428,7 +25483,9 @@ function checkRunbookClaims(root, docs, deps) {
|
|
|
25428
25483
|
if (token === "--") break;
|
|
25429
25484
|
const flag = token.split("=")[0];
|
|
25430
25485
|
if (/^--[\w-]+$/.test(flag) && flag !== "--help" && !placeholder(flag) && !deps.commandOptions?.get(resolved)?.has(flag)) {
|
|
25431
|
-
|
|
25486
|
+
const known = deps.commandOptions?.get(resolved);
|
|
25487
|
+
const knownFlags = known?.size ? ` (known flags: ${[...known].sort().join(" ")})` : "";
|
|
25488
|
+
report("unknown-flag", `mmi-cli ${resolved}: ${flag}${knownFlags} (source: ${deps.commandSource ?? "unspecified"})`);
|
|
25432
25489
|
}
|
|
25433
25490
|
}
|
|
25434
25491
|
} else if (cmd === "npm") {
|
|
@@ -25667,7 +25724,7 @@ function runDocRefs(root, deps = {}) {
|
|
|
25667
25724
|
findings.push(...checkCommands(docs, commandPaths).findings);
|
|
25668
25725
|
}
|
|
25669
25726
|
const runbooks = Object.fromEntries(Object.entries(docs).filter(([doc, body]) => isRunbook(doc, body)));
|
|
25670
|
-
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions }));
|
|
25727
|
+
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions, commandSource: deps.commandSource }));
|
|
25671
25728
|
const warnings = [...refResult.warnings];
|
|
25672
25729
|
if (deps.runbooksOnly && !Object.keys(runbooks).length) {
|
|
25673
25730
|
warnings.push({ kind: "runbook-missing", doc: "docs/Guides/operator.md", line: 0, detail: "no operator runbook or <!-- runbook --> document exists" });
|
|
@@ -28089,10 +28146,10 @@ var rollout_plan_default = {
|
|
|
28089
28146
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
28090
28147
|
},
|
|
28091
28148
|
baseline: {
|
|
28092
|
-
version: "4.6.
|
|
28093
|
-
tag: "v4.6.
|
|
28094
|
-
commit: "
|
|
28095
|
-
npm: "@mutmutco/cli@4.6.
|
|
28149
|
+
version: "4.6.23",
|
|
28150
|
+
tag: "v4.6.23",
|
|
28151
|
+
commit: "27b8f434196b",
|
|
28152
|
+
npm: "@mutmutco/cli@4.6.23"
|
|
28096
28153
|
},
|
|
28097
28154
|
exitCriterion: "fleet-n-of-n",
|
|
28098
28155
|
hubOnlyShortcut: "forbidden",
|
|
@@ -28109,14 +28166,14 @@ var rollout_plan_default = {
|
|
|
28109
28166
|
repo: "mutmutco/mmi-hub",
|
|
28110
28167
|
role: "canary",
|
|
28111
28168
|
schedule: "train",
|
|
28112
|
-
v3Target: "v4.6.
|
|
28169
|
+
v3Target: "v4.6.23"
|
|
28113
28170
|
}
|
|
28114
28171
|
],
|
|
28115
28172
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
28116
28173
|
rollback: {
|
|
28117
28174
|
independent: true,
|
|
28118
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
28119
|
-
v3Target: "v4.6.
|
|
28175
|
+
mechanism: "npm dist-tag latest -> 4.6.23 and redeploy the Hub Lambda from tag v4.6.23 (27b8f434196b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
28176
|
+
v3Target: "v4.6.23 (@mutmutco/cli@4.6.23, tag commit 27b8f434196b \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
28120
28177
|
}
|
|
28121
28178
|
},
|
|
28122
28179
|
{
|
|
@@ -41185,7 +41242,7 @@ var PRIMARY_GROUPS = [
|
|
|
41185
41242
|
["Setup and support", ["bootstrap", "secrets", "docs", "tests", "spawn", "dist"]],
|
|
41186
41243
|
["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
|
|
41187
41244
|
];
|
|
41188
|
-
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["oauth", "org", "runtime", "plugin", "packages", "github", "archive-prune", "intercom", "jev"]);
|
|
41245
|
+
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["oauth", "org", "runtime", "plugin", "packages", "github", "archive-prune", "intercom", "jev", "vars", "repo"]);
|
|
41189
41246
|
var CAMPAIGN_ORCHESTRATOR_TOP_LEVEL = /* @__PURE__ */ new Set(["pickup", "read", "run"]);
|
|
41190
41247
|
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
|
|
41191
41248
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
@@ -41234,6 +41291,10 @@ var COMMAND_OWNERSHIP = {
|
|
|
41234
41291
|
read: { module_owner: "cli/src/app-read-broker.ts", consumer: "agent-workflow" },
|
|
41235
41292
|
run: { module_owner: "cli/src/merge-group-orphans.ts", consumer: "agent-workflow" },
|
|
41236
41293
|
// #9479
|
|
41294
|
+
vars: { module_owner: "cli/src/devops-vars.ts", consumer: "agent-workflow" },
|
|
41295
|
+
// #9780 repository Actions variables
|
|
41296
|
+
repo: { module_owner: "cli/src/repo-archive-status.ts", consumer: "agent-workflow" },
|
|
41297
|
+
// #9779 archive-status read
|
|
41237
41298
|
ci: { module_owner: "cli/src/ci-audit.ts", consumer: "release-operator" },
|
|
41238
41299
|
rcand: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
41239
41300
|
release: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
@@ -44436,6 +44497,573 @@ function registerRunRerun(run, writeToken, projects) {
|
|
|
44436
44497
|
});
|
|
44437
44498
|
}
|
|
44438
44499
|
|
|
44500
|
+
// src/devops-vars.ts
|
|
44501
|
+
var import_node_crypto13 = require("node:crypto");
|
|
44502
|
+
init_github_client();
|
|
44503
|
+
init_cli_shared();
|
|
44504
|
+
var VARS_RECEIPT_SCHEMA = "mmi-cli.devops-vars/1";
|
|
44505
|
+
function parseVarsName(raw) {
|
|
44506
|
+
const name = raw.trim();
|
|
44507
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || name.length > 256) {
|
|
44508
|
+
throw new Error(`<name> must match [A-Za-z_][A-Za-z0-9_]* (\u2264256 chars) \u2014 received '${raw.slice(0, 64)}'`);
|
|
44509
|
+
}
|
|
44510
|
+
return name;
|
|
44511
|
+
}
|
|
44512
|
+
function parseVarsValue(raw) {
|
|
44513
|
+
if (typeof raw !== "string") throw new Error(`--value must be a string \u2014 received ${raw === null ? "null" : typeof raw}; values are preserved exactly, never coerced`);
|
|
44514
|
+
if (raw.length > 49152) throw new Error(`--value exceeds GitHub's 48KB variable size limit (${raw.length} chars)`);
|
|
44515
|
+
return raw;
|
|
44516
|
+
}
|
|
44517
|
+
function resolveCanonicalRepo(raw, projects) {
|
|
44518
|
+
if (!Array.isArray(projects)) return { kind: "unavailable" };
|
|
44519
|
+
const input = raw.trim();
|
|
44520
|
+
if (!/^[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)?$/.test(input) || input.split("/").some((part) => part === "." || part === "..")) {
|
|
44521
|
+
return { kind: "unregistered" };
|
|
44522
|
+
}
|
|
44523
|
+
const key = registryRepoKey(qualifyRegistryRepo(input));
|
|
44524
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
44525
|
+
const aliasRepos = [];
|
|
44526
|
+
const lower = input.toLowerCase();
|
|
44527
|
+
for (const project2 of projects) {
|
|
44528
|
+
if (!project2 || typeof project2 !== "object" || Array.isArray(project2)) continue;
|
|
44529
|
+
if (!input.includes("/")) {
|
|
44530
|
+
const alias = typeof project2.slug === "string" ? project2.slug : typeof project2.name === "string" ? project2.name : void 0;
|
|
44531
|
+
if (typeof alias === "string" && alias.trim() !== "" && alias.toLowerCase() === lower && Array.isArray(project2.repos)) {
|
|
44532
|
+
for (const r of project2.repos) if (typeof r === "string" && r.trim() !== "") aliasRepos.push(qualifyRegistryRepo(r.trim()));
|
|
44533
|
+
}
|
|
44534
|
+
}
|
|
44535
|
+
if (!Array.isArray(project2.repos)) continue;
|
|
44536
|
+
for (const r of project2.repos) {
|
|
44537
|
+
if (typeof r !== "string" || r.trim() === "") continue;
|
|
44538
|
+
const qualified = qualifyRegistryRepo(r.trim());
|
|
44539
|
+
if (registryRepoKey(qualified) === key) byKey.set(key, byKey.get(key) ?? qualified);
|
|
44540
|
+
}
|
|
44541
|
+
}
|
|
44542
|
+
const distinct = /* @__PURE__ */ new Map();
|
|
44543
|
+
if (byKey.has(key)) distinct.set(key, byKey.get(key));
|
|
44544
|
+
for (const r of aliasRepos) {
|
|
44545
|
+
const rk = registryRepoKey(r);
|
|
44546
|
+
if (!distinct.has(rk)) distinct.set(rk, r);
|
|
44547
|
+
}
|
|
44548
|
+
const hits = [...distinct.entries()];
|
|
44549
|
+
if (hits.length === 1) {
|
|
44550
|
+
const [hitKey, repo] = hits[0];
|
|
44551
|
+
return { kind: "resolved", repo, via: hitKey === key ? "repo-name" : "project-alias" };
|
|
44552
|
+
}
|
|
44553
|
+
if (hits.length > 1) return { kind: "conflict", candidates: hits.map(([, repo]) => repo) };
|
|
44554
|
+
return { kind: "unregistered" };
|
|
44555
|
+
}
|
|
44556
|
+
function classifyUpstreamError(e) {
|
|
44557
|
+
if (e instanceof GitHubApiError) {
|
|
44558
|
+
if (e.status === 404) return { status: 404, reason: "bare-404" };
|
|
44559
|
+
if (e.status === 403 || e.status === 401) return { status: e.status, reason: "denied" };
|
|
44560
|
+
return { status: e.status, reason: "transport-error" };
|
|
44561
|
+
}
|
|
44562
|
+
const name = e?.name ?? "";
|
|
44563
|
+
if (name === "TimeoutError" || name === "AbortError" || /timeout|abort/i.test(String(e?.message ?? ""))) {
|
|
44564
|
+
return { status: 0, reason: "timeout" };
|
|
44565
|
+
}
|
|
44566
|
+
return { status: 0, reason: "transport-error" };
|
|
44567
|
+
}
|
|
44568
|
+
async function call(deps, log, method, path2, body) {
|
|
44569
|
+
try {
|
|
44570
|
+
const res = await deps.transport.restWithStatus(method, path2, body !== void 0 ? { body } : void 0);
|
|
44571
|
+
log.push({ method, path: path2, status: res.status });
|
|
44572
|
+
return res;
|
|
44573
|
+
} catch (e) {
|
|
44574
|
+
log.push({ method, path: path2, status: classifyUpstreamError(e).status });
|
|
44575
|
+
throw e;
|
|
44576
|
+
}
|
|
44577
|
+
}
|
|
44578
|
+
function beginOp() {
|
|
44579
|
+
return { startedAt: /* @__PURE__ */ new Date(), log: [] };
|
|
44580
|
+
}
|
|
44581
|
+
function receiptBase(deps, operation, repo, ctx, now) {
|
|
44582
|
+
return {
|
|
44583
|
+
schema: VARS_RECEIPT_SCHEMA,
|
|
44584
|
+
operation,
|
|
44585
|
+
repo,
|
|
44586
|
+
window: { startedAt: ctx.startedAt.toISOString(), endedAt: now().toISOString(), atomicSnapshot: false },
|
|
44587
|
+
actor: deps.actor ?? { credential: "app_installation" },
|
|
44588
|
+
correlationId: (deps.correlationId ?? import_node_crypto13.randomUUID)(),
|
|
44589
|
+
upstream: ctx.log
|
|
44590
|
+
};
|
|
44591
|
+
}
|
|
44592
|
+
var VARS_PER_PAGE = 100;
|
|
44593
|
+
var VARS_MAX_PAGES = 100;
|
|
44594
|
+
function parseVariableEntry(raw) {
|
|
44595
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("variable entry is not an object");
|
|
44596
|
+
const e = raw;
|
|
44597
|
+
if (typeof e.name !== "string" || e.name === "") throw new Error("variable entry has no name");
|
|
44598
|
+
if (e.value !== void 0 && typeof e.value !== "string") throw new Error(`variable '${e.name}' value is ${typeof e.value}, not a string \u2014 refusing to coerce`);
|
|
44599
|
+
if (e.created_at !== void 0 && typeof e.created_at !== "string") throw new Error(`variable '${e.name}' created_at is not a string`);
|
|
44600
|
+
if (e.updated_at !== void 0 && typeof e.updated_at !== "string") throw new Error(`variable '${e.name}' updated_at is not a string`);
|
|
44601
|
+
return {
|
|
44602
|
+
name: e.name,
|
|
44603
|
+
...e.value !== void 0 ? { value: e.value } : {},
|
|
44604
|
+
...e.created_at !== void 0 ? { createdAt: e.created_at } : {},
|
|
44605
|
+
...e.updated_at !== void 0 ? { updatedAt: e.updated_at } : {}
|
|
44606
|
+
};
|
|
44607
|
+
}
|
|
44608
|
+
async function enumerateRepositoryVariables(deps, repo, log = []) {
|
|
44609
|
+
const entries = [];
|
|
44610
|
+
let total = -1;
|
|
44611
|
+
for (let page = 1; page <= VARS_MAX_PAGES; page++) {
|
|
44612
|
+
const { body } = await call(deps, log, "GET", `repos/${repo}/actions/variables?per_page=${VARS_PER_PAGE}&page=${page}`);
|
|
44613
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
44614
|
+
return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page} body is not a JSON object` };
|
|
44615
|
+
}
|
|
44616
|
+
const envelope = body;
|
|
44617
|
+
if (typeof envelope.total_count !== "number" || !Number.isInteger(envelope.total_count) || envelope.total_count < 0 || !Array.isArray(envelope.variables)) {
|
|
44618
|
+
return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page} envelope has no integer total_count / variables array` };
|
|
44619
|
+
}
|
|
44620
|
+
if (total >= 0 && envelope.total_count !== total) {
|
|
44621
|
+
return { complete: false, reason: "incomplete-pagination", entries, pages: page, detail: `total_count moved between pages (${total} \u2192 ${envelope.total_count}); the enumeration is not a snapshot` };
|
|
44622
|
+
}
|
|
44623
|
+
total = envelope.total_count;
|
|
44624
|
+
try {
|
|
44625
|
+
for (const raw of envelope.variables) entries.push(parseVariableEntry(raw));
|
|
44626
|
+
} catch (e) {
|
|
44627
|
+
return { complete: false, reason: "malformed-page", entries, pages: page, detail: `page ${page}: ${e.message}` };
|
|
44628
|
+
}
|
|
44629
|
+
const expectedPages = Math.max(1, Math.ceil(total / VARS_PER_PAGE));
|
|
44630
|
+
if (expectedPages > VARS_MAX_PAGES) {
|
|
44631
|
+
return { complete: false, reason: "page-bound-exceeded", entries, pages: page, detail: `total_count ${total} needs ${expectedPages} pages; the bounded read stops at ${VARS_MAX_PAGES}` };
|
|
44632
|
+
}
|
|
44633
|
+
if (page >= expectedPages) break;
|
|
44634
|
+
}
|
|
44635
|
+
if (entries.length !== total) {
|
|
44636
|
+
return { complete: false, reason: "incomplete-pagination", entries, pages: Math.max(1, Math.ceil(total / VARS_PER_PAGE)), detail: `accumulated ${entries.length} entries but total_count is ${total}` };
|
|
44637
|
+
}
|
|
44638
|
+
return { complete: true, entries, totalVariables: total, pages: Math.max(1, Math.ceil(total / VARS_PER_PAGE)) };
|
|
44639
|
+
}
|
|
44640
|
+
function findEntry(entries, name) {
|
|
44641
|
+
const lower = name.toLowerCase();
|
|
44642
|
+
return entries.find((e) => e.name.toLowerCase() === lower);
|
|
44643
|
+
}
|
|
44644
|
+
async function readVariable(deps, repo, name, log) {
|
|
44645
|
+
let body;
|
|
44646
|
+
try {
|
|
44647
|
+
({ body } = await call(deps, log, "GET", `repos/${repo}/actions/variables/${name}`));
|
|
44648
|
+
} catch (e) {
|
|
44649
|
+
const { status, reason } = classifyUpstreamError(e);
|
|
44650
|
+
if (reason === "denied") return { presence: "unknown", unknownReason: "denied", detail: `HTTP ${status}: the App installation cannot read repository variables`, capability: { permission: "variables:read", outcome: "denied" } };
|
|
44651
|
+
if (reason !== "bare-404") return { presence: "unknown", unknownReason: reason, detail: String(e.message).slice(0, 200) };
|
|
44652
|
+
try {
|
|
44653
|
+
const en = await enumerateRepositoryVariables(deps, repo, log);
|
|
44654
|
+
if (!en.complete) return { presence: "unknown", unknownReason: en.reason, detail: `bare 404 on GET, and the absence proof failed: ${en.detail}` };
|
|
44655
|
+
const hit = findEntry(en.entries, name);
|
|
44656
|
+
if (hit) return { presence: "present", value: hit.value, observed: { ...hit.createdAt ? { createdAt: hit.createdAt } : {}, ...hit.updatedAt ? { updatedAt: hit.updatedAt } : {}, source: "enumeration" } };
|
|
44657
|
+
return { presence: "absent", absenceProof: { provenBy: "complete-enumeration", pages: en.pages, perPage: VARS_PER_PAGE, totalVariables: en.totalVariables, nameExcluded: true } };
|
|
44658
|
+
} catch (proofError) {
|
|
44659
|
+
const proof = classifyUpstreamError(proofError);
|
|
44660
|
+
return { presence: "unknown", unknownReason: proof.reason === "bare-404" ? "transport-error" : proof.reason, detail: `bare 404 on GET, and the absence proof failed: ${String(proofError.message).slice(0, 160)}` };
|
|
44661
|
+
}
|
|
44662
|
+
}
|
|
44663
|
+
try {
|
|
44664
|
+
const entry = parseVariableEntry(body);
|
|
44665
|
+
return { presence: "present", value: entry.value, observed: { ...entry.createdAt ? { createdAt: entry.createdAt } : {}, ...entry.updatedAt ? { updatedAt: entry.updatedAt } : {}, source: "get" } };
|
|
44666
|
+
} catch (e) {
|
|
44667
|
+
return { presence: "unknown", unknownReason: "parser-refused", detail: `HTTP 200 but the body could not be parsed as a variable: ${e.message}` };
|
|
44668
|
+
}
|
|
44669
|
+
}
|
|
44670
|
+
async function getVariableReceipt(deps, repo, name) {
|
|
44671
|
+
const ctx = beginOp();
|
|
44672
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44673
|
+
const read = await readVariable(deps, repo, name, ctx.log);
|
|
44674
|
+
return {
|
|
44675
|
+
...receiptBase(deps, "get", repo, ctx, now),
|
|
44676
|
+
name,
|
|
44677
|
+
presence: read.presence,
|
|
44678
|
+
...read.value !== void 0 ? { value: read.value } : {},
|
|
44679
|
+
...read.observed ? { observed: read.observed } : {},
|
|
44680
|
+
...read.absenceProof ? { absenceProof: read.absenceProof } : {},
|
|
44681
|
+
...read.unknownReason ? { unknownReason: read.unknownReason } : {},
|
|
44682
|
+
...read.capability ? { capability: read.capability } : {}
|
|
44683
|
+
};
|
|
44684
|
+
}
|
|
44685
|
+
async function listVariablesReceipt(deps, repo) {
|
|
44686
|
+
const ctx = beginOp();
|
|
44687
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44688
|
+
try {
|
|
44689
|
+
const en = await enumerateRepositoryVariables(deps, repo, ctx.log);
|
|
44690
|
+
if (en.complete) {
|
|
44691
|
+
return {
|
|
44692
|
+
...receiptBase(deps, "list", repo, ctx, now),
|
|
44693
|
+
pagination: { pages: en.pages, perPage: VARS_PER_PAGE, complete: true, totalVariables: en.totalVariables },
|
|
44694
|
+
variables: en.entries.map(({ name, createdAt, updatedAt }) => ({ name, ...createdAt ? { createdAt } : {}, ...updatedAt ? { updatedAt } : {} }))
|
|
44695
|
+
};
|
|
44696
|
+
}
|
|
44697
|
+
return {
|
|
44698
|
+
...receiptBase(deps, "list", repo, ctx, now),
|
|
44699
|
+
pagination: { pages: en.pages, perPage: VARS_PER_PAGE, complete: false, reason: en.reason, detail: en.detail },
|
|
44700
|
+
variables: en.entries.map(({ name, createdAt, updatedAt }) => ({ name, ...createdAt ? { createdAt } : {}, ...updatedAt ? { updatedAt } : {} })),
|
|
44701
|
+
unknownReason: en.reason
|
|
44702
|
+
};
|
|
44703
|
+
} catch (e) {
|
|
44704
|
+
const { status, reason } = classifyUpstreamError(e);
|
|
44705
|
+
return {
|
|
44706
|
+
...receiptBase(deps, "list", repo, ctx, now),
|
|
44707
|
+
pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason },
|
|
44708
|
+
variables: [],
|
|
44709
|
+
unknownReason: reason,
|
|
44710
|
+
...reason === "denied" ? { capability: { permission: "variables:read", outcome: "denied" }, pagination: { pages: 0, perPage: VARS_PER_PAGE, complete: false, reason, detail: `HTTP ${status}: the App installation cannot list repository variables` } } : {}
|
|
44711
|
+
};
|
|
44712
|
+
}
|
|
44713
|
+
}
|
|
44714
|
+
async function readBaseline(deps, repo, name, log) {
|
|
44715
|
+
const read = await readVariable(deps, repo, name, log);
|
|
44716
|
+
if (read.presence === "present") return { presence: "present", value: read.value };
|
|
44717
|
+
if (read.presence === "absent") return { presence: "absent" };
|
|
44718
|
+
return { presence: "unknown", unknownReason: read.unknownReason, ...read.capability ? { capability: read.capability } : {} };
|
|
44719
|
+
}
|
|
44720
|
+
function rejectedWrite(base, baseline, reason, capability) {
|
|
44721
|
+
return {
|
|
44722
|
+
...base,
|
|
44723
|
+
outcome: "rejected",
|
|
44724
|
+
casLimitation: "no-atomic-compare-and-swap",
|
|
44725
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44726
|
+
reason,
|
|
44727
|
+
...capability ? { capability } : {}
|
|
44728
|
+
};
|
|
44729
|
+
}
|
|
44730
|
+
function unknownWrite(base, baseline, reason, readback) {
|
|
44731
|
+
return {
|
|
44732
|
+
...base,
|
|
44733
|
+
outcome: "outcome-unknown",
|
|
44734
|
+
casLimitation: "no-atomic-compare-and-swap",
|
|
44735
|
+
baseline: baseline.presence === "present" ? { presence: "present", value: baseline.value, source: "get" } : baseline,
|
|
44736
|
+
...readback ? { readback } : {},
|
|
44737
|
+
reason
|
|
44738
|
+
};
|
|
44739
|
+
}
|
|
44740
|
+
async function createVariableReceipt(deps, repo, name, value) {
|
|
44741
|
+
const ctx = beginOp();
|
|
44742
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44743
|
+
let en;
|
|
44744
|
+
try {
|
|
44745
|
+
en = await enumerateRepositoryVariables(deps, repo, ctx.log);
|
|
44746
|
+
} catch (e) {
|
|
44747
|
+
const { status, reason } = classifyUpstreamError(e);
|
|
44748
|
+
return {
|
|
44749
|
+
...receiptBase(deps, "create", repo, ctx, now),
|
|
44750
|
+
operation: "create",
|
|
44751
|
+
name,
|
|
44752
|
+
outcome: "not-dispatched",
|
|
44753
|
+
casLimitation: "no-atomic-compare-and-swap",
|
|
44754
|
+
baseline: { presence: "unknown", source: "complete-enumeration" },
|
|
44755
|
+
reason: `no observed absent baseline: ${reason === "denied" ? `HTTP ${status}: the App installation cannot list repository variables` : String(e.message).slice(0, 160)}`,
|
|
44756
|
+
...reason === "denied" ? { capability: { permission: "variables:read", outcome: "denied" } } : {}
|
|
44757
|
+
};
|
|
44758
|
+
}
|
|
44759
|
+
const base = { ...receiptBase(deps, "create", repo, ctx, now), operation: "create", name, casLimitation: "no-atomic-compare-and-swap" };
|
|
44760
|
+
if (!en.complete) {
|
|
44761
|
+
return { ...base, outcome: "not-dispatched", baseline: { presence: "unknown", source: "complete-enumeration" }, reason: `no observed absent baseline: ${en.reason} (${en.detail})` };
|
|
44762
|
+
}
|
|
44763
|
+
const existing = findEntry(en.entries, name);
|
|
44764
|
+
if (existing) {
|
|
44765
|
+
return { ...base, outcome: "not-dispatched", baseline: { presence: "present", ...existing.value !== void 0 ? { value: existing.value } : {}, source: "complete-enumeration" }, reason: "variable already present \u2014 create only dispatches against an observed absent baseline (use update)" };
|
|
44766
|
+
}
|
|
44767
|
+
try {
|
|
44768
|
+
await call(deps, ctx.log, "POST", `repos/${repo}/actions/variables`, { name, value });
|
|
44769
|
+
} catch (e) {
|
|
44770
|
+
const { status, reason } = classifyUpstreamError(e);
|
|
44771
|
+
const absentBaseline = { presence: "absent", source: "complete-enumeration" };
|
|
44772
|
+
if (reason === "denied") return { ...base, outcome: "rejected", baseline: absentBaseline, reason: `HTTP ${status}: the App installation lacks repository Variables write`, capability: { permission: "variables:write", outcome: "denied" } };
|
|
44773
|
+
if (status === 404) return { ...base, outcome: "rejected", baseline: absentBaseline, reason: "HTTP 404: repository missing or not visible to the installation" };
|
|
44774
|
+
if (status === 409 || status === 422) return { ...base, outcome: "rejected", baseline: absentBaseline, reason: `HTTP ${status}: ${String(e.message).slice(0, 160)}` };
|
|
44775
|
+
return unknownWrite({ ...base, schema: VARS_RECEIPT_SCHEMA, operation: "create", repo }, { presence: "absent", source: "complete-enumeration" }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the create MAY have applied \u2014 reconcile read-only, never replay`);
|
|
44776
|
+
}
|
|
44777
|
+
const rb = await readVariable(deps, repo, name, ctx.log);
|
|
44778
|
+
if (rb.presence === "present") {
|
|
44779
|
+
if (rb.value === value) {
|
|
44780
|
+
return { ...base, outcome: "verified", baseline: { presence: "absent", source: "complete-enumeration" }, readback: { presence: "present", value: rb.value } };
|
|
44781
|
+
}
|
|
44782
|
+
return unknownWrite(base, { presence: "absent", source: "complete-enumeration" }, "read-back shows a different value than dispatched \u2014 a concurrent outside write is possible; reconcile read-only", { presence: "present", value: rb.value });
|
|
44783
|
+
}
|
|
44784
|
+
if (rb.presence === "absent") {
|
|
44785
|
+
return unknownWrite(base, { presence: "absent", source: "complete-enumeration" }, "accepted but the read-back proves absence \u2014 reconcile read-only before any retry", { presence: "absent" });
|
|
44786
|
+
}
|
|
44787
|
+
return {
|
|
44788
|
+
...base,
|
|
44789
|
+
outcome: "accepted-but-unverified",
|
|
44790
|
+
baseline: { presence: "absent", source: "complete-enumeration" },
|
|
44791
|
+
readback: { presence: "unknown" },
|
|
44792
|
+
reason: `create accepted but the read-back could not verify: ${rb.unknownReason ?? "unknown"}`
|
|
44793
|
+
};
|
|
44794
|
+
}
|
|
44795
|
+
async function updateVariableReceipt(deps, repo, name, value) {
|
|
44796
|
+
const ctx = beginOp();
|
|
44797
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44798
|
+
const base = { ...receiptBase(deps, "update", repo, ctx, now), operation: "update", name, casLimitation: "no-atomic-compare-and-swap" };
|
|
44799
|
+
const baseline = await readBaseline(deps, repo, name, ctx.log);
|
|
44800
|
+
if (baseline.presence !== "present") {
|
|
44801
|
+
return {
|
|
44802
|
+
...base,
|
|
44803
|
+
outcome: "not-dispatched",
|
|
44804
|
+
baseline: { presence: baseline.presence, ...baseline.value !== void 0 ? { value: baseline.value } : {}, source: "get" },
|
|
44805
|
+
reason: baseline.presence === "absent" ? "no present baseline \u2014 the enumeration proved absence (use create)" : `no retained present baseline (${baseline.unknownReason ?? "unknown"}); a bare 404 is never a present baseline`,
|
|
44806
|
+
...baseline.capability ? { capability: baseline.capability } : {}
|
|
44807
|
+
};
|
|
44808
|
+
}
|
|
44809
|
+
const live = await readVariable(deps, repo, name, ctx.log);
|
|
44810
|
+
if (live.presence !== "present" || live.value !== baseline.value) {
|
|
44811
|
+
return {
|
|
44812
|
+
...base,
|
|
44813
|
+
outcome: "not-dispatched",
|
|
44814
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44815
|
+
...live.presence === "present" ? { readback: { presence: "present", value: live.value } } : {},
|
|
44816
|
+
reason: live.presence === "present" ? "concurrent change: the live value no longer matches the retained baseline \u2014 stopped for reconciliation" : `baseline unconfirmed before dispatch (${live.unknownReason ?? "unknown"}) \u2014 stopped`
|
|
44817
|
+
};
|
|
44818
|
+
}
|
|
44819
|
+
try {
|
|
44820
|
+
await call(deps, ctx.log, "PATCH", `repos/${repo}/actions/variables/${name}`, { value });
|
|
44821
|
+
} catch (e) {
|
|
44822
|
+
const { status, reason } = classifyUpstreamError(e);
|
|
44823
|
+
if (reason === "denied") return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: the App installation lacks repository Variables write`, { permission: "variables:write", outcome: "denied" });
|
|
44824
|
+
if (status === 404) return rejectedWrite(base, { presence: "present", value: baseline.value }, "HTTP 404 at write: the variable vanished after the baseline check (concurrent deletion suspected) \u2014 reconcile read-only, never create");
|
|
44825
|
+
if (status === 409 || status === 422) return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: ${String(e.message).slice(0, 160)}`);
|
|
44826
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the update MAY have applied \u2014 reconcile read-only, never replay`);
|
|
44827
|
+
}
|
|
44828
|
+
const rb = await readVariable(deps, repo, name, ctx.log);
|
|
44829
|
+
if (rb.presence === "present") {
|
|
44830
|
+
if (rb.value === value) return { ...base, outcome: "verified", baseline: { presence: "present", value: baseline.value, source: "get" }, readback: { presence: "present", value: rb.value } };
|
|
44831
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, "read-back shows a different value than dispatched \u2014 a concurrent outside write is possible; reconcile read-only", { presence: "present", value: rb.value });
|
|
44832
|
+
}
|
|
44833
|
+
if (rb.presence === "absent") {
|
|
44834
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, "accepted but the read-back proves absence \u2014 reconcile read-only before any retry", { presence: "absent" });
|
|
44835
|
+
}
|
|
44836
|
+
return {
|
|
44837
|
+
...base,
|
|
44838
|
+
outcome: "accepted-but-unverified",
|
|
44839
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44840
|
+
readback: { presence: "unknown" },
|
|
44841
|
+
reason: `update accepted but the read-back could not verify: ${rb.unknownReason ?? "unknown"}`
|
|
44842
|
+
};
|
|
44843
|
+
}
|
|
44844
|
+
async function deleteVariableReceipt(deps, repo, name) {
|
|
44845
|
+
const ctx = beginOp();
|
|
44846
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
44847
|
+
const base = { ...receiptBase(deps, "delete", repo, ctx, now), operation: "delete", name, casLimitation: "no-atomic-compare-and-swap" };
|
|
44848
|
+
const baseline = await readBaseline(deps, repo, name, ctx.log);
|
|
44849
|
+
if (baseline.presence !== "present") {
|
|
44850
|
+
return {
|
|
44851
|
+
...base,
|
|
44852
|
+
outcome: "not-dispatched",
|
|
44853
|
+
baseline: { presence: baseline.presence, ...baseline.value !== void 0 ? { value: baseline.value } : {}, source: "get" },
|
|
44854
|
+
reason: baseline.presence === "absent" ? "no present baseline \u2014 the enumeration already proves absence; nothing to delete" : `no retained present baseline (${baseline.unknownReason ?? "unknown"}); a bare 404 never authorizes a delete`,
|
|
44855
|
+
...baseline.capability ? { capability: baseline.capability } : {}
|
|
44856
|
+
};
|
|
44857
|
+
}
|
|
44858
|
+
const live = await readVariable(deps, repo, name, ctx.log);
|
|
44859
|
+
if (live.presence !== "present" || live.value !== baseline.value) {
|
|
44860
|
+
return {
|
|
44861
|
+
...base,
|
|
44862
|
+
outcome: "not-dispatched",
|
|
44863
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44864
|
+
...live.presence === "present" ? { readback: { presence: "present", value: live.value } } : {},
|
|
44865
|
+
reason: live.presence === "present" ? "concurrent change: the live value no longer matches the retained baseline \u2014 stopped for reconciliation" : `baseline unconfirmed before dispatch (${live.unknownReason ?? "unknown"}) \u2014 stopped`
|
|
44866
|
+
};
|
|
44867
|
+
}
|
|
44868
|
+
try {
|
|
44869
|
+
await call(deps, ctx.log, "DELETE", `repos/${repo}/actions/variables/${name}`);
|
|
44870
|
+
} catch (e) {
|
|
44871
|
+
const { status, reason } = classifyUpstreamError(e);
|
|
44872
|
+
if (reason === "denied") return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: the App installation lacks repository Variables write`, { permission: "variables:write", outcome: "denied" });
|
|
44873
|
+
if (status === 404) return rejectedWrite(base, { presence: "present", value: baseline.value }, "HTTP 404 at write: the variable vanished after the baseline check (concurrent deletion suspected) \u2014 reconcile read-only");
|
|
44874
|
+
if (status === 409 || status === 422) return rejectedWrite(base, { presence: "present", value: baseline.value }, `HTTP ${status}: ${String(e.message).slice(0, 160)}`);
|
|
44875
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, `${reason === "timeout" ? "dispatch timed out" : `HTTP ${status || "no response"}`}; the delete MAY have applied \u2014 reconcile read-only, never replay`);
|
|
44876
|
+
}
|
|
44877
|
+
try {
|
|
44878
|
+
const en = await enumerateRepositoryVariables(deps, repo);
|
|
44879
|
+
if (en.complete && !findEntry(en.entries, name)) {
|
|
44880
|
+
return { ...base, outcome: "verified", baseline: { presence: "present", value: baseline.value, source: "get" }, readback: { presence: "absent" }, reason: `absence proven by complete enumeration (${en.pages} page(s), ${en.totalVariables} variable(s), name excluded)` };
|
|
44881
|
+
}
|
|
44882
|
+
if (en.complete) {
|
|
44883
|
+
const still = findEntry(en.entries, name);
|
|
44884
|
+
return unknownWrite(base, { presence: "present", value: baseline.value }, "delete accepted but the variable is still enumerated \u2014 reconcile read-only, never replay", { presence: "present", ...still.value !== void 0 ? { value: still.value } : {} });
|
|
44885
|
+
}
|
|
44886
|
+
return {
|
|
44887
|
+
...base,
|
|
44888
|
+
outcome: "accepted-but-unverified",
|
|
44889
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44890
|
+
reason: `delete accepted but absence is unproven: ${en.reason} (${en.detail})`
|
|
44891
|
+
};
|
|
44892
|
+
} catch (e) {
|
|
44893
|
+
return {
|
|
44894
|
+
...base,
|
|
44895
|
+
outcome: "accepted-but-unverified",
|
|
44896
|
+
baseline: { presence: "present", value: baseline.value, source: "get" },
|
|
44897
|
+
reason: `delete accepted but absence is unproven (${classifyUpstreamError(e).reason}): ${String(e.message).slice(0, 160)}`
|
|
44898
|
+
};
|
|
44899
|
+
}
|
|
44900
|
+
}
|
|
44901
|
+
var VARS_HELP_AFTER = [
|
|
44902
|
+
"",
|
|
44903
|
+
"Presence semantics (#9780): present | absent | unknown. A bare 404 is UNKNOWN, never absence \u2014",
|
|
44904
|
+
"absence is claimed only from a complete bounded enumeration excluding the exact name, with",
|
|
44905
|
+
"pagination evidence. Truncated pagination, malformed pages, denial, parser refusal or timeout is",
|
|
44906
|
+
"unknown. Values are preserved exactly (including `off`) and never coerced.",
|
|
44907
|
+
"Mutations verify by read-back (delete: complete enumeration) \u2014 an HTTP success alone is never",
|
|
44908
|
+
"verified; an ambiguous result is reported unknown and reconciled READ-ONLY, never replayed.",
|
|
44909
|
+
"The pre-write check is NOT atomic compare-and-swap: a concurrent outside write between the check",
|
|
44910
|
+
"and the dispatch is not prevented. Repository scope only \u2014 no organization/environment scope, no",
|
|
44911
|
+
"secret store, no rename, no workflow dispatch, no credential fallback."
|
|
44912
|
+
].join("\n");
|
|
44913
|
+
function resolveRepoOrFail(raw, projects, verb) {
|
|
44914
|
+
const resolution = resolveCanonicalRepo(raw, projects);
|
|
44915
|
+
if (resolution.kind === "resolved") return resolution.repo;
|
|
44916
|
+
if (resolution.kind === "conflict") return fail(`vars ${verb}: '${raw}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo; ambiguous aliases are refused (#9780)`);
|
|
44917
|
+
if (resolution.kind === "unavailable") return fail(`vars ${verb}: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered`);
|
|
44918
|
+
return fail(`vars ${verb}: '${raw}' is not a registered repository in the Hub registry; App variables access is limited to registered repos \u2014 pass the exact owner/repo`);
|
|
44919
|
+
}
|
|
44920
|
+
function registerDevopsVarsCommands(program3, deps) {
|
|
44921
|
+
const vars = program3.command("vars").description("repository Actions variables \u2014 typed get/list/create/update/delete on the org App (Variables read/write), Hub-registered repos only (#9780)");
|
|
44922
|
+
const runRead = async (commandPath4, verb, repoArg, name, o) => {
|
|
44923
|
+
const projects = await deps.projects();
|
|
44924
|
+
const repo = resolveRepoOrFail(repoArg, projects, verb);
|
|
44925
|
+
const ops = { transport: await deps.appClient(commandPath4), ...deps.actor ? { actor: deps.actor() } : {} };
|
|
44926
|
+
const receipt = verb === "get" && name !== void 0 ? await getVariableReceipt(ops, repo, name) : await listVariablesReceipt(ops, repo);
|
|
44927
|
+
if (o.json) console.log(JSON.stringify(receipt, null, 2));
|
|
44928
|
+
else printReadText(receipt);
|
|
44929
|
+
const failed2 = receipt.operation === "get" ? receipt.presence === "unknown" : !receipt.pagination.complete;
|
|
44930
|
+
if (failed2) process.exitCode = 1;
|
|
44931
|
+
};
|
|
44932
|
+
const runMutation = async (commandPath4, verb, nameArg, value, repoArg, o) => {
|
|
44933
|
+
let name;
|
|
44934
|
+
let exactValue;
|
|
44935
|
+
try {
|
|
44936
|
+
name = parseVarsName(nameArg);
|
|
44937
|
+
if (verb !== "delete") exactValue = parseVarsValue(value);
|
|
44938
|
+
} catch (e) {
|
|
44939
|
+
return fail(`vars ${verb}: ${e.message}`);
|
|
44940
|
+
}
|
|
44941
|
+
const projects = await deps.projects();
|
|
44942
|
+
const repo = resolveRepoOrFail(repoArg, projects, verb);
|
|
44943
|
+
const ops = { transport: await deps.appClient(commandPath4), ...deps.actor ? { actor: deps.actor() } : {} };
|
|
44944
|
+
const receipt = verb === "create" ? await createVariableReceipt(ops, repo, name, exactValue) : verb === "update" ? await updateVariableReceipt(ops, repo, name, exactValue) : await deleteVariableReceipt(ops, repo, name);
|
|
44945
|
+
if (o.json) console.log(JSON.stringify(receipt, null, 2));
|
|
44946
|
+
else printMutationText(receipt);
|
|
44947
|
+
if (receipt.outcome !== "verified") process.exitCode = 1;
|
|
44948
|
+
};
|
|
44949
|
+
vars.command("get <name>").description("exact variable baseline or typed observed absence/unknown \u2014 a bare 404 is unknown; absence needs a complete enumeration (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runRead("vars get", "get", o.repo ?? "", name, o));
|
|
44950
|
+
vars.command("list").description("exhaustive bounded enumeration: names + provider timestamps only (never values), with pagination/completeness evidence (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (o) => runRead("vars list", "list", o.repo ?? "", void 0, o));
|
|
44951
|
+
vars.command("create <name>").description("create ONLY against an observed absent baseline (complete enumeration); value preserved exactly; verified by read-back (#9780)").requiredOption("--value <value>", "exact string value (never coerced; e.g. the literal `off`)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars create", "create", name, o.value, o.repo ?? "", o));
|
|
44952
|
+
vars.command("update <name>").description("replace ONLY against a retained present baseline, re-checked just before dispatch; concurrent changes stop the write (#9780)").requiredOption("--value <value>", "exact string value (never coerced)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars update", "update", name, o.value, o.repo ?? "", o));
|
|
44953
|
+
vars.command("delete <name>").description("delete ONLY against a retained present baseline; absence proven afterwards by a complete enumeration (#9780)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").addHelpText("after", VARS_HELP_AFTER).action(async (name, o) => runMutation("vars delete", "delete", name, void 0, o.repo ?? "", o));
|
|
44954
|
+
}
|
|
44955
|
+
function printReadText(receipt) {
|
|
44956
|
+
if (receipt.operation === "get") {
|
|
44957
|
+
if (receipt.presence === "present") {
|
|
44958
|
+
console.log(`vars get: ${receipt.repo} ${receipt.name} = ${JSON.stringify(receipt.value ?? "")} (presence: present, source: ${receipt.observed?.source ?? "get"})`);
|
|
44959
|
+
} else if (receipt.presence === "absent") {
|
|
44960
|
+
const p = receipt.absenceProof;
|
|
44961
|
+
console.log(`vars get: ${receipt.repo} ${receipt.name} absent \u2014 proven by complete enumeration (${p.pages} page(s), ${p.totalVariables} variable(s), name excluded)`);
|
|
44962
|
+
} else {
|
|
44963
|
+
console.log(`vars get: ${receipt.repo} ${receipt.name} presence UNKNOWN (${receipt.unknownReason}${receipt.capability ? ", capability denied" : ""}) \u2014 absence is never claimed from this read`);
|
|
44964
|
+
}
|
|
44965
|
+
return;
|
|
44966
|
+
}
|
|
44967
|
+
if (receipt.pagination.complete) {
|
|
44968
|
+
console.log(`vars list: ${receipt.repo} \u2014 ${receipt.pagination.totalVariables} variable(s), complete (${receipt.pagination.pages} page(s)): ${receipt.variables.map((v) => v.name).join(", ") || "(none)"}`);
|
|
44969
|
+
} else {
|
|
44970
|
+
console.log(`vars list: ${receipt.repo} \u2014 INCOMPLETE enumeration (${receipt.pagination.reason ?? "unknown"}); the names below are NOT the full set: ${receipt.variables.map((v) => v.name).join(", ") || "(none)"}`);
|
|
44971
|
+
}
|
|
44972
|
+
}
|
|
44973
|
+
function printMutationText(receipt) {
|
|
44974
|
+
const baseline = receipt.baseline.presence === "present" ? `baseline ${JSON.stringify(receipt.baseline.value ?? "")}` : `baseline ${receipt.baseline.presence}`;
|
|
44975
|
+
const readback = receipt.readback ? `, read-back ${receipt.readback.presence}${receipt.readback.value !== void 0 ? ` ${JSON.stringify(receipt.readback.value)}` : ""}` : "";
|
|
44976
|
+
console.log(`vars ${receipt.operation}: ${receipt.repo} ${receipt.name} \u2014 ${receipt.outcome} (${baseline}${readback})${receipt.reason ? ` \u2014 ${receipt.reason}` : ""} [no atomic compare-and-swap; writes are never replayed]`);
|
|
44977
|
+
}
|
|
44978
|
+
|
|
44979
|
+
// src/repo-archive-status.ts
|
|
44980
|
+
var import_node_crypto14 = require("node:crypto");
|
|
44981
|
+
init_github_client();
|
|
44982
|
+
init_cli_shared();
|
|
44983
|
+
var ARCHIVE_STATUS_RECEIPT_SCHEMA = "mmi-cli.repo-archive-status/1";
|
|
44984
|
+
var ARCHIVE_SOURCE_TIME_NOTE = "GitHub exposes no archived_at; observedAt is this read's window end and the provider timestamps are the repository's own updated_at/pushed_at from the same response that carried the archived flag";
|
|
44985
|
+
async function call2(deps, log, method, path2) {
|
|
44986
|
+
try {
|
|
44987
|
+
const res = await deps.transport.restWithStatus(method, path2);
|
|
44988
|
+
log.push({ method, path: path2, status: res.status });
|
|
44989
|
+
return res;
|
|
44990
|
+
} catch (e) {
|
|
44991
|
+
const status = e instanceof GitHubApiError ? e.status : 0;
|
|
44992
|
+
log.push({ method, path: path2, status });
|
|
44993
|
+
throw e;
|
|
44994
|
+
}
|
|
44995
|
+
}
|
|
44996
|
+
function classify2(e) {
|
|
44997
|
+
if (e instanceof GitHubApiError) {
|
|
44998
|
+
if (e.status === 404) return { status: 404, reason: "missing-or-invisible" };
|
|
44999
|
+
if (e.status === 403 || e.status === 401) return { status: e.status, reason: "denied" };
|
|
45000
|
+
return { status: e.status, reason: "transport-error" };
|
|
45001
|
+
}
|
|
45002
|
+
const name = e?.name ?? "";
|
|
45003
|
+
if (name === "TimeoutError" || name === "AbortError" || /timeout|abort/i.test(String(e?.message ?? ""))) return { status: 0, reason: "timeout" };
|
|
45004
|
+
return { status: 0, reason: "transport-error" };
|
|
45005
|
+
}
|
|
45006
|
+
async function readArchiveStatusReceipt(deps, repo) {
|
|
45007
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
45008
|
+
const startedAt = now();
|
|
45009
|
+
const log = [];
|
|
45010
|
+
const base = {
|
|
45011
|
+
schema: ARCHIVE_STATUS_RECEIPT_SCHEMA,
|
|
45012
|
+
repo,
|
|
45013
|
+
sourceTime: { observedAt: "", note: ARCHIVE_SOURCE_TIME_NOTE },
|
|
45014
|
+
window: { startedAt: startedAt.toISOString(), endedAt: "", atomicSnapshot: false },
|
|
45015
|
+
actor: deps.actor ?? { credential: "app_installation" },
|
|
45016
|
+
correlationId: (deps.correlationId ?? import_node_crypto14.randomUUID)(),
|
|
45017
|
+
upstream: log
|
|
45018
|
+
};
|
|
45019
|
+
try {
|
|
45020
|
+
const { body } = await call2(deps, log, "GET", `repos/${repo}`);
|
|
45021
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
45022
|
+
return { ...base, archived: "unknown", unknownReason: "parser-refused", sourceTime: { ...base.sourceTime, observedAt: now().toISOString() }, window: { ...base.window, endedAt: now().toISOString() } };
|
|
45023
|
+
}
|
|
45024
|
+
const payload = body;
|
|
45025
|
+
if (typeof payload.archived !== "boolean") {
|
|
45026
|
+
return { ...base, archived: "unknown", unknownReason: "parser-refused", sourceTime: { ...base.sourceTime, observedAt: now().toISOString() }, window: { ...base.window, endedAt: now().toISOString() } };
|
|
45027
|
+
}
|
|
45028
|
+
if (payload.updated_at !== void 0 && typeof payload.updated_at !== "string") payload.updated_at = void 0;
|
|
45029
|
+
if (payload.pushed_at !== void 0 && typeof payload.pushed_at !== "string") payload.pushed_at = void 0;
|
|
45030
|
+
return {
|
|
45031
|
+
...base,
|
|
45032
|
+
archived: payload.archived,
|
|
45033
|
+
sourceTime: {
|
|
45034
|
+
observedAt: now().toISOString(),
|
|
45035
|
+
...typeof payload.updated_at === "string" ? { providerUpdatedAt: payload.updated_at } : {},
|
|
45036
|
+
...typeof payload.pushed_at === "string" ? { providerPushedAt: payload.pushed_at } : {},
|
|
45037
|
+
note: ARCHIVE_SOURCE_TIME_NOTE
|
|
45038
|
+
},
|
|
45039
|
+
window: { ...base.window, endedAt: now().toISOString() }
|
|
45040
|
+
};
|
|
45041
|
+
} catch (e) {
|
|
45042
|
+
const { status, reason } = classify2(e);
|
|
45043
|
+
return {
|
|
45044
|
+
...base,
|
|
45045
|
+
archived: "unknown",
|
|
45046
|
+
unknownReason: reason,
|
|
45047
|
+
...reason === "denied" ? { capability: { permission: "metadata:read", outcome: "denied" } } : {},
|
|
45048
|
+
...reason === "missing-or-invisible" ? { sourceTime: { ...base.sourceTime, observedAt: now().toISOString(), note: `${ARCHIVE_SOURCE_TIME_NOTE} \u2014 HTTP 404: the repository is missing or not visible to the installation, which is NOT archived:false` } } : { sourceTime: { ...base.sourceTime, observedAt: now().toISOString() } },
|
|
45049
|
+
window: { ...base.window, endedAt: now().toISOString() }
|
|
45050
|
+
};
|
|
45051
|
+
}
|
|
45052
|
+
}
|
|
45053
|
+
function registerRepoArchiveStatusCommand(program3, deps) {
|
|
45054
|
+
const repoRoot2 = program3.command("repo").description("canonical repository typed reads \u2014 archive status (#9779)");
|
|
45055
|
+
repoRoot2.command("archive-status").description("typed archived-state read for one canonical repository: archived + source time, App metadata:read; a 404 is missing-or-invisible, never archived:false (#9779)").requiredOption("--repo <owner/repo>", "registered repository (owner/repo, repo name, or one unambiguous project alias)").option("--json", "machine-readable receipt").action(async (o) => {
|
|
45056
|
+
const projects = await deps.projects();
|
|
45057
|
+
const resolution = resolveCanonicalRepo(o.repo ?? "", projects);
|
|
45058
|
+
const repo = resolution.kind === "resolved" ? resolution.repo : resolution.kind === "conflict" ? fail(`repo archive-status: '${o.repo ?? ""}' resolves to MULTIPLE registered repositories (${resolution.candidates.join(", ")}) \u2014 pass the exact owner/repo (#9779)`) : resolution.kind === "unavailable" ? fail("repo archive-status: the Hub project registry is unreadable; refusing to address a repo this command cannot prove registered") : fail(`repo archive-status: '${o.repo ?? ""}' is not a registered repository in the Hub registry \u2014 pass the exact owner/repo`);
|
|
45059
|
+
const receipt = await readArchiveStatusReceipt({ transport: await deps.appClient("repo archive-status"), ...deps.actor ? { actor: deps.actor() } : {} }, repo);
|
|
45060
|
+
if (o.json) console.log(JSON.stringify(receipt, null, 2));
|
|
45061
|
+
else if (receipt.archived === "unknown") console.log(`repo archive-status: ${receipt.repo} \u2014 archived state UNKNOWN (${receipt.unknownReason}) \u2014 never reads as not-archived`);
|
|
45062
|
+
else console.log(`repo archive-status: ${receipt.repo} \u2014 archived: ${receipt.archived} (observed ${receipt.sourceTime.observedAt}${receipt.sourceTime.providerUpdatedAt ? `, provider updated_at ${receipt.sourceTime.providerUpdatedAt}` : ""})`);
|
|
45063
|
+
if (receipt.archived === "unknown") process.exitCode = 1;
|
|
45064
|
+
});
|
|
45065
|
+
}
|
|
45066
|
+
|
|
44439
45067
|
// src/board-commands.ts
|
|
44440
45068
|
init_cli_shared();
|
|
44441
45069
|
|
|
@@ -45384,7 +46012,7 @@ var import_node_os20 = require("node:os");
|
|
|
45384
46012
|
var import_node_path34 = require("node:path");
|
|
45385
46013
|
|
|
45386
46014
|
// src/box-control.ts
|
|
45387
|
-
var
|
|
46015
|
+
var import_node_crypto15 = require("node:crypto");
|
|
45388
46016
|
var MANIFEST_PATH = "infra/box-control/manifest.json";
|
|
45389
46017
|
var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
|
|
45390
46018
|
var sshdPost = (dest) => `if sshd -t; then systemctl reload ssh; else rm -f ${dest}; echo 'sshd -t rejected ${dest}: removed, not reloaded' >&2; exit 1; fi`;
|
|
@@ -45488,7 +46116,7 @@ function laneCoverageAction(files, box, op) {
|
|
|
45488
46116
|
return { notice: `LANE_COVERAGE: ${mismatch}` };
|
|
45489
46117
|
}
|
|
45490
46118
|
function md5(content) {
|
|
45491
|
-
return (0,
|
|
46119
|
+
return (0, import_node_crypto15.createHash)("md5").update(content).digest("hex");
|
|
45492
46120
|
}
|
|
45493
46121
|
function planRemoteScript(dests) {
|
|
45494
46122
|
const lines2 = ["set -u"];
|
|
@@ -47646,7 +48274,7 @@ function renderDeployPortDoctor(report) {
|
|
|
47646
48274
|
}
|
|
47647
48275
|
|
|
47648
48276
|
// src/bootstrap-drift.ts
|
|
47649
|
-
var
|
|
48277
|
+
var import_node_crypto16 = require("node:crypto");
|
|
47650
48278
|
function byteComparableSeeds(manifest, cls) {
|
|
47651
48279
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
47652
48280
|
}
|
|
@@ -47666,7 +48294,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
47666
48294
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
47667
48295
|
}
|
|
47668
48296
|
function seedContentHash(content) {
|
|
47669
|
-
return (0,
|
|
48297
|
+
return (0, import_node_crypto16.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
47670
48298
|
}
|
|
47671
48299
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
47672
48300
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -48916,7 +49544,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
48916
49544
|
}
|
|
48917
49545
|
|
|
48918
49546
|
// src/bootstrap-seed-delivery.ts
|
|
48919
|
-
var
|
|
49547
|
+
var import_node_crypto17 = require("node:crypto");
|
|
48920
49548
|
init_github_client();
|
|
48921
49549
|
var SHA = /^[a-f0-9]{40}$/;
|
|
48922
49550
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -48949,7 +49577,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
48949
49577
|
}
|
|
48950
49578
|
const baseSha = base?.object?.sha;
|
|
48951
49579
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
48952
|
-
const identity = (0,
|
|
49580
|
+
const identity = (0, import_node_crypto17.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
48953
49581
|
const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
|
|
48954
49582
|
const scoped = forcePr && delivery.mode === "direct" ? { mode: "pr", ref: `bootstrap-seed-${identity}-${slug}`, branch: `bootstrap-seed-${identity}-${slug}`, reason: "registry gate render requires review" } : delivery;
|
|
48955
49583
|
return { ...scoped, baseSha, baseBranch };
|
|
@@ -50834,7 +51462,7 @@ init_github_client();
|
|
|
50834
51462
|
init_cli_shared();
|
|
50835
51463
|
|
|
50836
51464
|
// src/suite-status.ts
|
|
50837
|
-
var
|
|
51465
|
+
var import_node_crypto18 = require("node:crypto");
|
|
50838
51466
|
var SUITE_REPO = "mutmutco/MM-Work";
|
|
50839
51467
|
var REPOS = {
|
|
50840
51468
|
"mmi-scout": "mutmutco/MMI-Scout",
|
|
@@ -50862,7 +51490,7 @@ function parseSuiteMapping(body) {
|
|
|
50862
51490
|
return void 0;
|
|
50863
51491
|
}
|
|
50864
51492
|
}
|
|
50865
|
-
var sha256 = (b) => (0,
|
|
51493
|
+
var sha256 = (b) => (0, import_node_crypto18.createHash)("sha256").update(b).digest("hex");
|
|
50866
51494
|
async function proveBundle(url, tag, fetchUrl) {
|
|
50867
51495
|
const get = async (u) => {
|
|
50868
51496
|
try {
|
|
@@ -51119,7 +51747,7 @@ async function waitSuiteDev(deps, opts) {
|
|
|
51119
51747
|
// src/suite-train.ts
|
|
51120
51748
|
var import_node_fs39 = require("node:fs");
|
|
51121
51749
|
var import_node_path36 = require("node:path");
|
|
51122
|
-
var
|
|
51750
|
+
var import_node_crypto19 = require("node:crypto");
|
|
51123
51751
|
|
|
51124
51752
|
// src/kit-version.ts
|
|
51125
51753
|
var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
|
|
@@ -51221,7 +51849,7 @@ function readSuiteCut(path2) {
|
|
|
51221
51849
|
}
|
|
51222
51850
|
function writeSuiteCut(path2, record2) {
|
|
51223
51851
|
(0, import_node_fs39.mkdirSync)((0, import_node_path36.dirname)(path2), { recursive: true });
|
|
51224
|
-
const temp = `${path2}.${(0,
|
|
51852
|
+
const temp = `${path2}.${(0, import_node_crypto19.randomBytes)(8).toString("hex")}.tmp`;
|
|
51225
51853
|
try {
|
|
51226
51854
|
(0, import_node_fs39.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
|
|
51227
51855
|
(0, import_node_fs39.renameSync)(temp, path2);
|
|
@@ -53702,16 +54330,16 @@ async function ghWithAppOn401(label, args, options) {
|
|
|
53702
54330
|
}
|
|
53703
54331
|
function withAppOn401(client, label = "post-merge") {
|
|
53704
54332
|
let app;
|
|
53705
|
-
const run = async (
|
|
54333
|
+
const run = async (call3) => {
|
|
53706
54334
|
try {
|
|
53707
|
-
return await
|
|
54335
|
+
return await call3(app ?? client);
|
|
53708
54336
|
} catch (e) {
|
|
53709
54337
|
if (!isUnauthorized(e)) throw e;
|
|
53710
54338
|
if (app) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53711
54339
|
const token = await appTokenFor(label, e);
|
|
53712
54340
|
app = createGitHubClient({ token: async () => token });
|
|
53713
54341
|
try {
|
|
53714
|
-
return await
|
|
54342
|
+
return await call3(app);
|
|
53715
54343
|
} catch (e2) {
|
|
53716
54344
|
if (isUnauthorized(e2)) throw new PostMergeCredentialError(label, "still HTTP 401 with the App installation token (expired or revoked App credential)");
|
|
53717
54345
|
throw e2;
|
|
@@ -54322,7 +54950,7 @@ async function deleteMergedRemoteBranch(options) {
|
|
|
54322
54950
|
init_github_client();
|
|
54323
54951
|
|
|
54324
54952
|
// src/release-custody.ts
|
|
54325
|
-
var
|
|
54953
|
+
var import_node_crypto20 = require("node:crypto");
|
|
54326
54954
|
var import_node_fs41 = require("node:fs");
|
|
54327
54955
|
var import_node_path38 = require("node:path");
|
|
54328
54956
|
init_github_client();
|
|
@@ -54545,7 +55173,7 @@ var ReleaseCustodyError = class extends Error {
|
|
|
54545
55173
|
this.name = "ReleaseCustodyError";
|
|
54546
55174
|
}
|
|
54547
55175
|
};
|
|
54548
|
-
var custodyDigest = (value) => (0,
|
|
55176
|
+
var custodyDigest = (value) => (0, import_node_crypto20.createHash)("sha256").update(value).digest("hex");
|
|
54549
55177
|
function parseCustody(raw, repo) {
|
|
54550
55178
|
const s = JSON.parse(raw);
|
|
54551
55179
|
if (s.schema !== "mmi-release-custody/v1" || s.repo !== repo || !s.precutId || !Number.isSafeInteger(s.revision) || s.revision < 1 || !s.controller || !Number.isSafeInteger(s.controller.runId) || s.controller.runId <= 0 || !Number.isSafeInteger(s.controller.runAttempt) || s.controller.runAttempt <= 0 || !s.controller.token || !(s.ledger === null || typeof s.ledger === "string") || !s.archives || !s.effects) {
|
|
@@ -54633,7 +55261,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
54633
55261
|
throw new ReleaseCustodyError(`previous controller run ${found.state.controller.runId} attempt ${found.state.controller.runAttempt} is not proven terminal`);
|
|
54634
55262
|
}
|
|
54635
55263
|
let state = {
|
|
54636
|
-
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0,
|
|
55264
|
+
...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto20.randomUUID)(), effects: {}, ...await initialize() },
|
|
54637
55265
|
revision: (found.state?.revision ?? 0) + 1,
|
|
54638
55266
|
controller
|
|
54639
55267
|
};
|
|
@@ -54694,7 +55322,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
|
|
|
54694
55322
|
proof: { kind: "ledger-lifecycle", controller: state.controller }
|
|
54695
55323
|
};
|
|
54696
55324
|
}
|
|
54697
|
-
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0,
|
|
55325
|
+
return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto20.randomUUID)() : state.precutId };
|
|
54698
55326
|
});
|
|
54699
55327
|
}
|
|
54700
55328
|
async preserveLocal(raw) {
|
|
@@ -54920,7 +55548,7 @@ function withHubReleaseCustody(deps) {
|
|
|
54920
55548
|
if (!authority.ok || !authority.train || authority.authenticatedDelegation !== true) {
|
|
54921
55549
|
throw new ReleaseCustodyError("repository custody requires the server-verified owner-triggered train session");
|
|
54922
55550
|
}
|
|
54923
|
-
const controller = { ...runIdentity, token: (0,
|
|
55551
|
+
const controller = { ...runIdentity, token: (0, import_node_crypto20.randomUUID)() };
|
|
54924
55552
|
const raw = localRaw(path2);
|
|
54925
55553
|
const requiredContexts = () => discoverRequiredCheckContexts(
|
|
54926
55554
|
deps,
|
|
@@ -55632,7 +56260,7 @@ init_github_quota();
|
|
|
55632
56260
|
|
|
55633
56261
|
// src/issue-commands.ts
|
|
55634
56262
|
var import_node_fs44 = require("node:fs");
|
|
55635
|
-
var
|
|
56263
|
+
var import_node_crypto21 = require("node:crypto");
|
|
55636
56264
|
init_cli_shared();
|
|
55637
56265
|
init_clean_exit();
|
|
55638
56266
|
init_error_codes();
|
|
@@ -56109,7 +56737,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
56109
56737
|
const identity = `${spec.type}
|
|
56110
56738
|
${spec.title.trim()}
|
|
56111
56739
|
${spec.body ?? ""}`;
|
|
56112
|
-
const hash = (0,
|
|
56740
|
+
const hash = (0, import_node_crypto21.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
56113
56741
|
return `${batchKey}:${hash}`;
|
|
56114
56742
|
}
|
|
56115
56743
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -56903,9 +57531,15 @@ function orgScopedHint(path2) {
|
|
|
56903
57531
|
if (!match) return "";
|
|
56904
57532
|
const org = match[1];
|
|
56905
57533
|
const runners = /^orgs\/[^/?#]+\/actions\/runners(?:\/|$)/.test(path2.split("?")[0]);
|
|
56906
|
-
const surface = runners ? `For which lane is busy in a repo, use the App-token reads \`mmi-cli devops read run list --repo <owner>/<repo> --status in_progress --json databaseId,workflowName\` followed by \`mmi-cli devops
|
|
57534
|
+
const surface = runners ? `For which lane is busy in a repo, use the App-token reads \`mmi-cli devops read run list --repo <owner>/<repo> --status in_progress --json databaseId,workflowName\` followed by \`MMI_ACTOR=app mmi-cli devops api get "repos/<owner>/<repo>/actions/runs/<id>/jobs?per_page=100"\` (runner_name/status per job); fleet-wide runner online/offline state has no App-token read and a human token holder answers it with \`gh api orgs/${org}/actions/runners\` (docs/Guides/gh-runner-runbook.md).` : "It needs a human token holder with an org-scoped token, or a dedicated mmi-cli verb when one exists for this resource.";
|
|
56907
57535
|
return ` This route is ORG-SCOPED (\`orgs/${org}/...\`) and is deliberately outside the allowlist \u2014 no App-token read exists for org-level resources, so retrying path variants cannot work and no \`repos/{owner}/{repo}/...\` respelling of it will be accepted. ${surface} A refusal writes nothing to stdout: check the exit code and never merge stderr into the JSON pipe (2>&1).`;
|
|
56908
57536
|
}
|
|
57537
|
+
function repoRunnersReadHint(path2) {
|
|
57538
|
+
const match = new RegExp(`^repos/(${NAME})/(${NAME})/actions/runners(?:/\\d+)?$`).exec(path2.split("?")[0]);
|
|
57539
|
+
if (!match) return "";
|
|
57540
|
+
const slug = `${match[1]}/${match[2]}`;
|
|
57541
|
+
return ` This route is a REPO-SCOPED runner read (\`repos/${slug}/actions/runners...\`) and is deliberately outside the allowlist \u2014 retrying path variants cannot work, and no new grant or allowlist growth is implied. The App-token reads prove only observed activity, never fleet inventory or idle capacity: \`MMI_ACTOR=app mmi-cli devops read run list --repo ${slug} --status in_progress --json databaseId,workflowName\` followed by \`MMI_ACTOR=app mmi-cli devops api get "repos/${slug}/actions/runs/<id>/jobs?per_page=100"\` (runner_name/status per job). Fleet inventory is a human admin read: \`gh api orgs/<org>/actions/runners\` (docs/Guides/gh-runner-runbook.md) \u2014 whether this repo also carries repo-level runners is unproved either way. A refusal writes nothing to stdout: check the exit code and never merge stderr into the JSON pipe (2>&1).`;
|
|
57542
|
+
}
|
|
56909
57543
|
function tagsReadHint(path2) {
|
|
56910
57544
|
const match = new RegExp(`^repos/(${NAME})/(${NAME})/(?:tags|git/(?:refs|matching-refs|tags|commits))(?:[/?#]|$)`).exec(path2);
|
|
56911
57545
|
if (!match) return "";
|
|
@@ -56949,7 +57583,7 @@ function parseApiGetPath(rawPath) {
|
|
|
56949
57583
|
const path2 = normalizeApiGetPath(rawPath);
|
|
56950
57584
|
const refuse = () => {
|
|
56951
57585
|
const hint = /^[A-Za-z]:[/\\]/.test(rawPath.trim()) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
|
|
56952
|
-
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${workflowRunsReadHint(path2)}${hint}`;
|
|
57586
|
+
const routeHints = `${typedReadHint(path2)}${runsReadHint(path2)}${orgScopedHint(path2)}${repoRunnersReadHint(path2)}${tagsReadHint(path2)}${branchRulesReadHint(path2)}${commitsReadHint(path2)}${checkRunsReadHint(path2)}${workflowRunsReadHint(path2)}${hint}`;
|
|
56953
57587
|
return new Error(`api get: refusing '${rawPath}' \u2014${routeHints}${API_GET_RECOVERY} Only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.`);
|
|
56954
57588
|
};
|
|
56955
57589
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
@@ -57141,6 +57775,8 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
57141
57775
|
// src/run-log-read.ts
|
|
57142
57776
|
init_cli_shared();
|
|
57143
57777
|
var FAILED2 = /* @__PURE__ */ new Set(["failure", "cancelled", "timed_out"]);
|
|
57778
|
+
var RUN_LOG_RANGE_LINE_CAP = 2e3;
|
|
57779
|
+
var RUN_LOG_RANGE_CHAR_CAP = 32 * 1024;
|
|
57144
57780
|
function logRepo(raw) {
|
|
57145
57781
|
const repo = parseBrokerRepo(raw, "--repo");
|
|
57146
57782
|
if (repo.split("/").some((s) => s === "." || s === "..")) throw new Error("read run log: --repo refuses dot segments");
|
|
@@ -57159,6 +57795,78 @@ function sliceRunLog(text, tail) {
|
|
|
57159
57795
|
});
|
|
57160
57796
|
return { errorLines: lines2.filter((_, i) => context.has(i)), tail: lines2.slice(-tail) };
|
|
57161
57797
|
}
|
|
57798
|
+
function parseLineRange(raw) {
|
|
57799
|
+
const match = /^(\d+):(\d+)$/.exec(raw.trim());
|
|
57800
|
+
if (!match) throw new Error(`read run log: --lines must be <start:end> line numbers, e.g. --lines 1:2000 (received '${raw}')`);
|
|
57801
|
+
const start = Number(match[1]);
|
|
57802
|
+
const end = Number(match[2]);
|
|
57803
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start) {
|
|
57804
|
+
throw new Error(`read run log: --lines must be a 1-based start:end range with start <= end and safe-integer bounds (received '${raw}')`);
|
|
57805
|
+
}
|
|
57806
|
+
return { start, end };
|
|
57807
|
+
}
|
|
57808
|
+
async function readRunLogRange(id, o, runner, env = brokerChildEnv()) {
|
|
57809
|
+
const { start, end: requestedEnd } = parseLineRange(o.lines);
|
|
57810
|
+
if (o.tail !== void 0) throw new Error("read run log: --lines and --tail are mutually exclusive (the range already bounds the returned lines)");
|
|
57811
|
+
if (o.failed) throw new Error("read run log: --lines and --failed are mutually exclusive (--lines reads exactly one named job)");
|
|
57812
|
+
if (o.attempt === void 0) throw new Error("read run log: --lines requires an explicit --attempt <n> (1-1000) \u2014 Actions job ids are minted per attempt, so the id must name one attempt");
|
|
57813
|
+
if (o.job === void 0 || !/^\d+$/.test(o.job.trim())) throw new Error("read run log: --lines requires a numeric --job <id> \u2014 job names are ambiguous across attempts; take the id from the attempt-pinned jobs read");
|
|
57814
|
+
const runId = parseBrokerId(id, "run id");
|
|
57815
|
+
const repo = logRepo(o.repo);
|
|
57816
|
+
const attempt = parseBrokerInt(o.attempt, "--attempt", 1, 1e3);
|
|
57817
|
+
const jobId = Number(parseBrokerId(o.job, "job id"));
|
|
57818
|
+
if (!Number.isSafeInteger(jobId) || jobId < 1) throw new Error(`read run log: --lines requires a numeric --job id (received '${o.job}')`);
|
|
57819
|
+
const pages = JSON.parse(await readAppApi(`repos/${repo}/actions/runs/${runId}/attempts/${attempt}/jobs`, runner));
|
|
57820
|
+
const listed = pages.flatMap((page) => page.jobs);
|
|
57821
|
+
const job = listed.find((candidate) => candidate.id === jobId);
|
|
57822
|
+
if (!job) {
|
|
57823
|
+
const shown = listed.slice(0, 30).map((j) => `${j.name} (${j.id})`).join(", ");
|
|
57824
|
+
return {
|
|
57825
|
+
result: { runId, repo, jobs: [] },
|
|
57826
|
+
exitCode: 1,
|
|
57827
|
+
message: `read run log: job ${jobId} is not in run ${runId} attempt ${attempt} \u2014 Actions job ids are minted per workflow run and per re-run attempt, so only an id from this attempt's listing matches; jobs in run ${runId} attempt ${attempt}: ${shown || "none"}${listed.length > 30 ? `, \u2026+${listed.length - 30} more` : ""}`
|
|
57828
|
+
};
|
|
57829
|
+
}
|
|
57830
|
+
const text = await readAppApi(`repos/${repo}/actions/jobs/${job.id}/logs`, runner);
|
|
57831
|
+
const redacted = redactPrivateCredential(text, env);
|
|
57832
|
+
const all = redacted.replace(/\r\n/g, "\n").split("\n");
|
|
57833
|
+
if (all.at(-1) === "") all.pop();
|
|
57834
|
+
const total = all.length;
|
|
57835
|
+
if (start > total) {
|
|
57836
|
+
return {
|
|
57837
|
+
result: { runId, repo, jobs: [] },
|
|
57838
|
+
exitCode: 1,
|
|
57839
|
+
message: `read run log: --lines ${start}:${requestedEnd} starts beyond the end of the log \u2014 job ${job.name} (${job.id}) attempt ${attempt} retains ${total} lines; nothing to return`
|
|
57840
|
+
};
|
|
57841
|
+
}
|
|
57842
|
+
const requestedCount = requestedEnd - start + 1;
|
|
57843
|
+
const selected = all.slice(start - 1, Math.min(requestedEnd, start - 1 + RUN_LOG_RANGE_LINE_CAP));
|
|
57844
|
+
let truncated = requestedCount > selected.length;
|
|
57845
|
+
let section = selected.join("\n");
|
|
57846
|
+
let charCut = false;
|
|
57847
|
+
if (section.length > RUN_LOG_RANGE_CHAR_CAP) {
|
|
57848
|
+
section = section.slice(0, RUN_LOG_RANGE_CHAR_CAP);
|
|
57849
|
+
charCut = true;
|
|
57850
|
+
truncated = true;
|
|
57851
|
+
}
|
|
57852
|
+
const lines2 = section.split("\n");
|
|
57853
|
+
if (charCut && lines2.at(-1) === "") lines2.pop();
|
|
57854
|
+
return {
|
|
57855
|
+
result: {
|
|
57856
|
+
runId,
|
|
57857
|
+
repo,
|
|
57858
|
+
attempt,
|
|
57859
|
+
job: { id: job.id, name: job.name, conclusion: job.conclusion },
|
|
57860
|
+
lines: lines2,
|
|
57861
|
+
start,
|
|
57862
|
+
end: start + lines2.length - 1,
|
|
57863
|
+
requestedEnd,
|
|
57864
|
+
total,
|
|
57865
|
+
truncated
|
|
57866
|
+
},
|
|
57867
|
+
exitCode: 0
|
|
57868
|
+
};
|
|
57869
|
+
}
|
|
57162
57870
|
async function readRunLog(id, o, runner) {
|
|
57163
57871
|
const runId = parseBrokerId(id, "run id");
|
|
57164
57872
|
const repo = logRepo(o.repo);
|
|
@@ -57184,14 +57892,17 @@ async function readRunLog(id, o, runner) {
|
|
|
57184
57892
|
return { result, exitCode: 0 };
|
|
57185
57893
|
}
|
|
57186
57894
|
function registerRunLogRead(run, runner) {
|
|
57187
|
-
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3))
|
|
57895
|
+
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136), or a bounded --lines range from one attempt-pinned job (#9773); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name or numeric job id").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3))).option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).addOption(new Option("--lines <start:end>", "read lines start:end of one job (needs --attempt and a numeric --job id; capped at 2000 lines and 32KiB characters; conflicts with --tail/--failed)").conflicts("failed")).option("--json", "emit the read as JSON (jobs with failedStep/errorLines/tail, or the --lines selection)").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
|
|
57188
57896
|
requireBrokerApp("read run log");
|
|
57189
57897
|
const env = brokerChildEnv();
|
|
57190
57898
|
try {
|
|
57191
|
-
const { result, message: message2, exitCode } = await readRunLog(id, o, runner);
|
|
57192
|
-
if (message2 && (!o.json || exitCode !== 0)) printLine(message2);
|
|
57899
|
+
const { result, message: message2, exitCode } = o.lines === void 0 ? await readRunLog(id, o, runner) : await readRunLogRange(id, o, runner, env);
|
|
57900
|
+
if (message2 && (!o.json || exitCode !== 0)) printLine(redactPrivateCredential(message2, env));
|
|
57193
57901
|
else if (o.json) printLine(redactPrivateCredential(JSON.stringify(result), env));
|
|
57194
|
-
else
|
|
57902
|
+
else if ("lines" in result) {
|
|
57903
|
+
printLine(redactPrivateCredential(`== ${result.job.name} (${result.job.id}) ${result.job.conclusion}: lines ${result.start}-${result.end} of ${result.total} (attempt ${result.attempt}${result.truncated ? ", truncated" : ""})`, env));
|
|
57904
|
+
for (const line of result.lines) printLine(redactPrivateCredential(line, env));
|
|
57905
|
+
} else for (const job of result.jobs) {
|
|
57195
57906
|
printLine(redactPrivateCredential(`== ${job.name} (${job.id}) ${job.conclusion}: failing step "${job.failedStep ?? "none"}"`, env));
|
|
57196
57907
|
for (const line of [...job.errorLines, ...job.tail]) printLine(redactPrivateCredential(line, env));
|
|
57197
57908
|
}
|
|
@@ -69280,14 +69991,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
69280
69991
|
}
|
|
69281
69992
|
|
|
69282
69993
|
// src/tenant-artifact.ts
|
|
69283
|
-
var
|
|
69994
|
+
var import_node_crypto22 = require("node:crypto");
|
|
69284
69995
|
var import_node_fs59 = require("node:fs");
|
|
69285
69996
|
var import_promises11 = require("node:fs/promises");
|
|
69286
69997
|
var import_node_path54 = require("node:path");
|
|
69287
69998
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
69288
69999
|
var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
|
|
69289
70000
|
async function sha256File(path2) {
|
|
69290
|
-
const hash = (0,
|
|
70001
|
+
const hash = (0, import_node_crypto22.createHash)("sha256");
|
|
69291
70002
|
for await (const chunk of (0, import_node_fs59.createReadStream)(path2)) hash.update(chunk);
|
|
69292
70003
|
return hash.digest("hex");
|
|
69293
70004
|
}
|
|
@@ -69879,13 +70590,13 @@ async function executeWaveLand(plan, deps) {
|
|
|
69879
70590
|
|
|
69880
70591
|
// src/dist-drift.ts
|
|
69881
70592
|
var import_node_child_process22 = require("node:child_process");
|
|
69882
|
-
var
|
|
70593
|
+
var import_node_crypto24 = require("node:crypto");
|
|
69883
70594
|
var import_node_fs62 = require("node:fs");
|
|
69884
70595
|
var import_node_os27 = require("node:os");
|
|
69885
70596
|
var import_node_path57 = require("node:path");
|
|
69886
70597
|
|
|
69887
70598
|
// ../scripts/distribution-digest.mjs
|
|
69888
|
-
var
|
|
70599
|
+
var import_node_crypto23 = require("node:crypto");
|
|
69889
70600
|
var import_node_fs61 = require("node:fs");
|
|
69890
70601
|
var import_node_path56 = require("node:path");
|
|
69891
70602
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
@@ -69898,7 +70609,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
69898
70609
|
return target;
|
|
69899
70610
|
}
|
|
69900
70611
|
function digestFiles(files) {
|
|
69901
|
-
const hash = (0,
|
|
70612
|
+
const hash = (0, import_node_crypto23.createHash)("sha256");
|
|
69902
70613
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
69903
70614
|
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs61.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs61.readFileSync)(file.absolute);
|
|
69904
70615
|
hash.update(file.relative, "utf8");
|
|
@@ -69929,7 +70640,7 @@ var DIST_ARTIFACTS = [
|
|
|
69929
70640
|
];
|
|
69930
70641
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
69931
70642
|
var ABSENT = "absent";
|
|
69932
|
-
var sha2562 = (bytes) => `sha256:${(0,
|
|
70643
|
+
var sha2562 = (bytes) => `sha256:${(0, import_node_crypto24.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
69933
70644
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
69934
70645
|
const committed = committedBytes ? sha2562(committedBytes) : ABSENT;
|
|
69935
70646
|
const rebuiltExpected = rebuiltBytes ? sha2562(rebuiltBytes) : ABSENT;
|
|
@@ -70498,10 +71209,10 @@ function findViolationsInSource(raw) {
|
|
|
70498
71209
|
if (imported.size === 0) return [];
|
|
70499
71210
|
const rawLines = raw.split("\n");
|
|
70500
71211
|
const src = stripComments(raw);
|
|
70501
|
-
const
|
|
71212
|
+
const call3 = new RegExp(CALL_SOURCE, "g");
|
|
70502
71213
|
const found = [];
|
|
70503
71214
|
let m;
|
|
70504
|
-
while (m =
|
|
71215
|
+
while (m = call3.exec(src)) {
|
|
70505
71216
|
const callee = m[2];
|
|
70506
71217
|
if (!imported.has(callee)) continue;
|
|
70507
71218
|
const open2 = m.index + m[0].length - 1;
|
|
@@ -70773,8 +71484,8 @@ function registerDeveloperCommands(program3) {
|
|
|
70773
71484
|
return { commandPaths: new Set(manifest.index.map((entry) => entry.path)), commandOptions: commandOptions2, source: "in-process" };
|
|
70774
71485
|
};
|
|
70775
71486
|
const resolved = resolveCommandInventory({ root, from: o.commandsFrom, inProcess });
|
|
70776
|
-
const { commandPaths, commandOptions } = resolved.inventory;
|
|
70777
|
-
const deps = { commandPaths, commandOptions, base: o.base, runbooksOnly: o.runbooksOnly };
|
|
71487
|
+
const { commandPaths, commandOptions, source } = resolved.inventory;
|
|
71488
|
+
const deps = { commandPaths, commandOptions, commandSource: source, base: o.base, runbooksOnly: o.runbooksOnly };
|
|
70778
71489
|
const result = o.live ? await runLiveDocRefs(root, deps, createRunbookLiveDeps(process.argv[1])) : runDocRefs(root, deps);
|
|
70779
71490
|
result.warnings.push(...resolved.warnings);
|
|
70780
71491
|
if (o.json) {
|
|
@@ -71471,7 +72182,20 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
|
|
|
71471
72182
|
appActorCommand = commandPath4;
|
|
71472
72183
|
setAuditCommand(commandPath4);
|
|
71473
72184
|
if (commandPath4 !== "whoami diagnose-hub") emitCliCallTelemetry(commandPath4);
|
|
71474
|
-
|
|
72185
|
+
const LAZY_APP_MINT_COMMANDS = /* @__PURE__ */ new Set([
|
|
72186
|
+
"ci cancel-orphans",
|
|
72187
|
+
"run cancel",
|
|
72188
|
+
"run rerun",
|
|
72189
|
+
// #9779/#9780: the vars verbs and archive-status validate --repo/name/value before the first
|
|
72190
|
+
// dispatch, so their scoped App mint waits for the action handler like the run writes above.
|
|
72191
|
+
"vars get",
|
|
72192
|
+
"vars list",
|
|
72193
|
+
"vars create",
|
|
72194
|
+
"vars update",
|
|
72195
|
+
"vars delete",
|
|
72196
|
+
"repo archive-status"
|
|
72197
|
+
]);
|
|
72198
|
+
if (LAZY_APP_MINT_COMMANDS.has(commandPath4)) return;
|
|
71475
72199
|
try {
|
|
71476
72200
|
await activateAppActor(commandPath4, process.env, () => APP_WRITE_COMMANDS.has(commandPath4) ? mintCommandWriteToken(appActorDeps(), commandPath4) : mintInstallationToken(appActorDeps()));
|
|
71477
72201
|
} catch (e) {
|
|
@@ -72313,6 +73037,15 @@ registerRunRerun(
|
|
|
72313
73037
|
async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), "run rerun")).token,
|
|
72314
73038
|
async () => fetchProjectsList(registryClientDeps(await loadConfig()))
|
|
72315
73039
|
);
|
|
73040
|
+
var varsAppClient = (commandPath4) => async () => (
|
|
73041
|
+
// The concrete client always provides restWithStatus (optional on the interface only so hand-built
|
|
73042
|
+
// test stubs of GitHubClient keep compiling).
|
|
73043
|
+
createGitHubClient({ token: async () => activeAppToken() ?? (await mintCommandWriteToken(appActorDeps(), commandPath4)).token })
|
|
73044
|
+
);
|
|
73045
|
+
var varsProjects = async () => fetchProjectsList(registryClientDeps(await loadConfig()));
|
|
73046
|
+
var varsActor = () => ({ credential: "app_installation", ...activeAppQuotaScope() ? { quotaScope: activeAppQuotaScope() } : {} });
|
|
73047
|
+
registerDevopsVarsCommands(program2, { appClient: (commandPath4) => varsAppClient(commandPath4)(), projects: varsProjects, actor: varsActor });
|
|
73048
|
+
registerRepoArchiveStatusCommand(program2, { appClient: (commandPath4) => varsAppClient(commandPath4)(), projects: varsProjects, actor: varsActor });
|
|
72316
73049
|
ci.command("reconcile").description("audit + optionally apply merge settings and product ruleset activation (master-admin)").option("--json", "machine-readable output").option("--repo <owner/repo>", "reconcile one repo instead of the full registry").option("--apply", "PATCH merge settings + activate product ruleset when missing (master role required); activation is skipped while the repo's gate has never passed (#3694)").option("--park-ruleset", "stop the product ruleset enforcing without deleting it \u2014 keeps its required contexts (master role required)").action(async (o) => {
|
|
72317
73050
|
if (o.apply && o.parkRuleset) {
|
|
72318
73051
|
return fail("ci reconcile: --apply and --park-ruleset ask for opposite things; pass one");
|
package/dist/runbook-sweep.cjs
CHANGED
|
@@ -8155,7 +8155,9 @@ function checkRunbookClaims(root, docs, deps) {
|
|
|
8155
8155
|
if (token === "--") break;
|
|
8156
8156
|
const flag = token.split("=")[0];
|
|
8157
8157
|
if (/^--[\w-]+$/.test(flag) && flag !== "--help" && !placeholder(flag) && !deps.commandOptions?.get(resolved)?.has(flag)) {
|
|
8158
|
-
|
|
8158
|
+
const known = deps.commandOptions?.get(resolved);
|
|
8159
|
+
const knownFlags = known?.size ? ` (known flags: ${[...known].sort().join(" ")})` : "";
|
|
8160
|
+
report("unknown-flag", `mmi-cli ${resolved}: ${flag}${knownFlags} (source: ${deps.commandSource ?? "unspecified"})`);
|
|
8159
8161
|
}
|
|
8160
8162
|
}
|
|
8161
8163
|
} else if (cmd === "npm") {
|
|
@@ -8391,7 +8393,7 @@ function runDocRefs(root, deps = {}) {
|
|
|
8391
8393
|
findings.push(...checkCommands(docs, commandPaths).findings);
|
|
8392
8394
|
}
|
|
8393
8395
|
const runbooks = Object.fromEntries(Object.entries(docs).filter(([doc, body]) => isRunbook(doc, body)));
|
|
8394
|
-
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile2, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions }));
|
|
8396
|
+
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile2, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions, commandSource: deps.commandSource }));
|
|
8395
8397
|
const warnings = [...refResult.warnings];
|
|
8396
8398
|
if (deps.runbooksOnly && !Object.keys(runbooks).length) {
|
|
8397
8399
|
warnings.push({ kind: "runbook-missing", doc: "docs/Guides/operator.md", line: 0, detail: "no operator runbook or <!-- runbook --> document exists" });
|
package/package.json
CHANGED