@mutmutco/codex-plugin 4.6.28 → 4.6.30
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +146 -36
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/hotfix/suite.md +1 -1
- package/skills/rcand/suite.md +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -9092,6 +9092,73 @@ var require_dist = __commonJS({
|
|
|
9092
9092
|
}
|
|
9093
9093
|
});
|
|
9094
9094
|
|
|
9095
|
+
// src/ci-hosted-usage.ts
|
|
9096
|
+
var ci_hosted_usage_exports = {};
|
|
9097
|
+
__export(ci_hosted_usage_exports, {
|
|
9098
|
+
projectHostedUsage: () => projectHostedUsage,
|
|
9099
|
+
renderHostedUsage: () => renderHostedUsage
|
|
9100
|
+
});
|
|
9101
|
+
function projectHostedUsage(config, now = Date.now()) {
|
|
9102
|
+
const summary = object(config.hostedUsage);
|
|
9103
|
+
const state = object(summary?.state);
|
|
9104
|
+
const tick = object(state?.lastTick);
|
|
9105
|
+
const ok = object(state?.lastOk);
|
|
9106
|
+
const record2 = object(summary?.record);
|
|
9107
|
+
const unknowns = [];
|
|
9108
|
+
let lastTick = null;
|
|
9109
|
+
if (tick && typeof tick.ok === "boolean") {
|
|
9110
|
+
lastTick = { ok: tick.ok };
|
|
9111
|
+
if (iso(tick.at)) lastTick.at = tick.at;
|
|
9112
|
+
if (typeof tick.complete === "boolean") lastTick.complete = tick.complete;
|
|
9113
|
+
for (const k of ["scanned", "usedUsd", "remainingUsd", "paceUsdPerDay"]) {
|
|
9114
|
+
if (typeof tick[k] === "number" && Number.isFinite(tick[k])) lastTick[k] = tick[k];
|
|
9115
|
+
}
|
|
9116
|
+
if (iso(tick.asOf)) lastTick.asOf = tick.asOf;
|
|
9117
|
+
}
|
|
9118
|
+
const lastOk = ok && iso(ok.at) && typeof ok.scanned === "number" && Number.isSafeInteger(ok.scanned) && ok.scanned >= 0 ? { at: ok.at, scanned: ok.scanned, ...typeof ok.complete === "boolean" && { complete: ok.complete } } : null;
|
|
9119
|
+
const allowanceInputs = record2 && ["usedUsd", "remainingUsd", "paceUsdPerDay"].every((k) => typeof record2[k] === "number" && Number.isFinite(record2[k])) && iso(record2.asOf) && typeof record2.source === "string" && /^[\w .:/-]{1,64}$/.test(record2.source) ? { usedUsd: record2.usedUsd, remainingUsd: record2.remainingUsd, paceUsdPerDay: record2.paceUsdPerDay, asOf: record2.asOf, source: record2.source } : null;
|
|
9120
|
+
const deployedRevision = typeof summary?.deployedRevision === "string" && /^[\w.+-]{1,128}$/.test(summary.deployedRevision) ? summary.deployedRevision : null;
|
|
9121
|
+
if (!summary) unknowns.push("hostedUsage unavailable (server may predate this read or summary read failed)");
|
|
9122
|
+
if (!deployedRevision) unknowns.push("deployedRevision unavailable");
|
|
9123
|
+
if (!lastTick?.at) unknowns.push("lastTick.at unavailable");
|
|
9124
|
+
if (!lastOk) unknowns.push("lastOk unavailable");
|
|
9125
|
+
if (lastTick?.complete === void 0) unknowns.push("tick completeness unavailable");
|
|
9126
|
+
if (!allowanceInputs) unknowns.push("allowance inputs and freshness unavailable");
|
|
9127
|
+
if (summary && summary.record !== null && !allowanceInputs) unknowns.push("unrecognized allowance projection");
|
|
9128
|
+
const age = allowanceInputs ? now - Date.parse(allowanceInputs.asOf) : null;
|
|
9129
|
+
return {
|
|
9130
|
+
source: { route: "GET /org/config", projection: "ORG#config hostedUsage", state: "CIHOSTED#v1/STATE", usage: "CIHOSTED#v1/USAGE" },
|
|
9131
|
+
deployedRevision,
|
|
9132
|
+
observedAt: new Date(now).toISOString(),
|
|
9133
|
+
lastTick,
|
|
9134
|
+
lastOk,
|
|
9135
|
+
allowanceInputs,
|
|
9136
|
+
decision: allowanceInputs ? "allowance served" : summary?.record === null ? "no allowance: record absent/malformed" : null,
|
|
9137
|
+
complete: lastTick?.complete ?? null,
|
|
9138
|
+
fresh: age === null ? null : age >= 0 && age < 12 * 36e5,
|
|
9139
|
+
unknowns
|
|
9140
|
+
};
|
|
9141
|
+
}
|
|
9142
|
+
function renderHostedUsage(read) {
|
|
9143
|
+
const a = read.allowanceInputs;
|
|
9144
|
+
return [
|
|
9145
|
+
`${read.source.route} \u2014 ${read.source.projection} (${read.source.state}, ${read.source.usage})`,
|
|
9146
|
+
`revision=${read.deployedRevision ?? "unknown"} observedAt=${read.observedAt}`,
|
|
9147
|
+
`${read.decision ?? "decision unknown"}; complete=${read.complete ?? "unknown"} fresh=${read.fresh ?? "unknown"}`,
|
|
9148
|
+
`lastTick=${read.lastTick?.at ?? "unknown"} ok=${read.lastTick?.ok ?? "unknown"}; lastOk=${read.lastOk?.at ?? "unknown"}`,
|
|
9149
|
+
...a ? [`Runtime allowance: used=$${a.usedUsd} remaining=$${a.remainingUsd} pace=$${a.paceUsdPerDay}/day asOf=${a.asOf} source=${a.source}`] : [],
|
|
9150
|
+
...read.unknowns.length ? [`Unknowns: ${read.unknowns.join("; ")}`] : []
|
|
9151
|
+
].join("\n");
|
|
9152
|
+
}
|
|
9153
|
+
var object, iso;
|
|
9154
|
+
var init_ci_hosted_usage = __esm({
|
|
9155
|
+
"src/ci-hosted-usage.ts"() {
|
|
9156
|
+
"use strict";
|
|
9157
|
+
object = (v) => v !== null && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
9158
|
+
iso = (v) => typeof v === "string" && Number.isFinite(Date.parse(v)) && new Date(v).toISOString() === v;
|
|
9159
|
+
}
|
|
9160
|
+
});
|
|
9161
|
+
|
|
9095
9162
|
// src/index.ts
|
|
9096
9163
|
var index_exports = {};
|
|
9097
9164
|
__export(index_exports, {
|
|
@@ -28181,10 +28248,10 @@ var rollout_plan_default = {
|
|
|
28181
28248
|
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)."
|
|
28182
28249
|
},
|
|
28183
28250
|
baseline: {
|
|
28184
|
-
version: "4.6.
|
|
28185
|
-
tag: "v4.6.
|
|
28186
|
-
commit: "
|
|
28187
|
-
npm: "@mutmutco/cli@4.6.
|
|
28251
|
+
version: "4.6.30",
|
|
28252
|
+
tag: "v4.6.30",
|
|
28253
|
+
commit: "52569fa5c01e",
|
|
28254
|
+
npm: "@mutmutco/cli@4.6.30"
|
|
28188
28255
|
},
|
|
28189
28256
|
exitCriterion: "fleet-n-of-n",
|
|
28190
28257
|
hubOnlyShortcut: "forbidden",
|
|
@@ -28201,14 +28268,14 @@ var rollout_plan_default = {
|
|
|
28201
28268
|
repo: "mutmutco/mmi-hub",
|
|
28202
28269
|
role: "canary",
|
|
28203
28270
|
schedule: "train",
|
|
28204
|
-
v3Target: "v4.6.
|
|
28271
|
+
v3Target: "v4.6.30"
|
|
28205
28272
|
}
|
|
28206
28273
|
],
|
|
28207
28274
|
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.",
|
|
28208
28275
|
rollback: {
|
|
28209
28276
|
independent: true,
|
|
28210
|
-
mechanism: "npm dist-tag latest -> 4.6.
|
|
28211
|
-
v3Target: "v4.6.
|
|
28277
|
+
mechanism: "npm dist-tag latest -> 4.6.30 and redeploy the Hub Lambda from tag v4.6.30 (52569fa5c01e); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
28278
|
+
v3Target: "v4.6.30 (@mutmutco/cli@4.6.30, tag commit 52569fa5c01e \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
28212
28279
|
}
|
|
28213
28280
|
},
|
|
28214
28281
|
{
|
|
@@ -31376,15 +31443,15 @@ function parseRuntimeIdentity(log, expected) {
|
|
|
31376
31443
|
if (proofs.length !== 1) return unavailable("invalid-proof");
|
|
31377
31444
|
const encoded = proofs[0].slice(prefix.length).trim();
|
|
31378
31445
|
if (encoded.length > 16384) return unavailable("invalid-proof");
|
|
31379
|
-
const
|
|
31446
|
+
const object2 = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
31380
31447
|
const keys = (v, allowed) => Object.keys(v).length === allowed.length && Object.keys(v).every((k) => allowed.includes(k));
|
|
31381
31448
|
const text = (v, re) => typeof v === "string" && re.test(v);
|
|
31382
31449
|
try {
|
|
31383
31450
|
const p = JSON.parse(encoded);
|
|
31384
|
-
if (JSON.stringify(p) !== encoded || !
|
|
31451
|
+
if (JSON.stringify(p) !== encoded || !object2(p)) return unavailable("invalid-proof");
|
|
31385
31452
|
if (p.version !== 1) return unavailable("unsupported-version");
|
|
31386
31453
|
const parts = expected.repo.split("/");
|
|
31387
|
-
if (parts.length !== 2 || !parts.every(Boolean) || !
|
|
31454
|
+
if (parts.length !== 2 || !parts.every(Boolean) || !object2(p.target) || !keys(p.target, ["slug", "stage"]) || p.target.slug !== parts[1].toLowerCase() || p.target.stage !== expected.stage) return unavailable("target-mismatch");
|
|
31388
31455
|
if (p.status === "unavailable") {
|
|
31389
31456
|
if (!keys(p, ["version", "target", "status", "reason"]) || typeof p.reason !== "string" || !["selection-unavailable", "inspection-failed", "snapshot-changed", "output-too-large"].includes(p.reason)) return unavailable("invalid-proof");
|
|
31390
31457
|
return unavailable(p.reason);
|
|
@@ -31392,7 +31459,7 @@ function parseRuntimeIdentity(log, expected) {
|
|
|
31392
31459
|
if (!keys(p, ["version", "target", "status", "slot", "replicas"]) || p.status !== "available" || p.slot !== "blue" && p.slot !== "green" || !Array.isArray(p.replicas) || !p.replicas.length) return unavailable("invalid-proof");
|
|
31393
31460
|
const seen = /* @__PURE__ */ new Set();
|
|
31394
31461
|
for (const r of p.replicas) {
|
|
31395
|
-
if (!
|
|
31462
|
+
if (!object2(r) || !keys(r, ["service", "container", "configuredRef", "actualImageID", "repoDigests", "health", "state"]) || !text(r.service, /^[A-Za-z0-9][A-Za-z0-9_-]*$/) || !text(r.container, /^[a-f0-9]{64}$/) || !text(r.configuredRef, /^[A-Za-z0-9][A-Za-z0-9._/@:-]*$/) || !text(r.actualImageID, /^sha256:[a-f0-9]{64}$/) || !Array.isArray(r.repoDigests) || !r.repoDigests.every((d) => text(d, /^[A-Za-z0-9][A-Za-z0-9._/:-]*@sha256:[a-f0-9]{64}$/)) || (typeof r.health !== "string" || !["healthy", "unhealthy", "starting", "not-configured", "unknown"].includes(r.health)) || r.state !== "running" || seen.has(r.container)) return unavailable("invalid-proof");
|
|
31396
31463
|
seen.add(r.container);
|
|
31397
31464
|
}
|
|
31398
31465
|
return p;
|
|
@@ -39296,8 +39363,9 @@ function blessedHubActionPins(constants = gate_budget_exports) {
|
|
|
39296
39363
|
}
|
|
39297
39364
|
return pins;
|
|
39298
39365
|
}
|
|
39366
|
+
var PIN_COMMIT_FORMAT = "commit-format:runbook-unaffected";
|
|
39299
39367
|
function pinTableDigest(pins) {
|
|
39300
|
-
return (0, import_node_crypto11.createHash)("sha1").update([...pins].map(([a, s]) => `${a}@${s}`).sort().join("\n")).digest("hex");
|
|
39368
|
+
return (0, import_node_crypto11.createHash)("sha1").update([PIN_COMMIT_FORMAT, ...[...pins].map(([a, s]) => `${a}@${s}`).sort()].join("\n")).digest("hex");
|
|
39301
39369
|
}
|
|
39302
39370
|
var HUB_ACTION_USE = /^(\s*-?\s*uses:\s*['"]?mutmutco\/mmi-hub\/\.github\/actions\/)([\w.-]+)@([^\s'"#]+)([^\n]*)$/i;
|
|
39303
39371
|
function staleHubActionPins(body, pins = blessedHubActionPins()) {
|
|
@@ -39417,7 +39485,9 @@ async function runPinTick(input) {
|
|
|
39417
39485
|
if (!next.changes.length) continue;
|
|
39418
39486
|
lines2.push(...next.changes.map((c) => `- \`${path2}:${c.line}\` ${c.action}: \`${c.from.slice(0, 12)}\` \u2192 \`${c.to.slice(0, 12)}\``));
|
|
39419
39487
|
const tmp = (0, import_node_path29.join)((0, import_node_os17.tmpdir)(), `mmi-pin-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
39420
|
-
const put = { message: `ci: pin MMI-Hub actions in ${path2} to the blessed SHAs
|
|
39488
|
+
const put = { message: `ci: pin MMI-Hub actions in ${path2} to the blessed SHAs
|
|
39489
|
+
|
|
39490
|
+
Runbook-Unaffected: SHA-only MMI-Hub action pin move; no workflow step changes`, content: encodeGitHubContents(next.body), branch };
|
|
39421
39491
|
if (file.sha) put.sha = file.sha;
|
|
39422
39492
|
(0, import_node_fs32.writeFileSync)(tmp, JSON.stringify(put), "utf8");
|
|
39423
39493
|
try {
|
|
@@ -42384,9 +42454,19 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
42384
42454
|
}
|
|
42385
42455
|
}
|
|
42386
42456
|
}
|
|
42387
|
-
const
|
|
42457
|
+
const isConflicting = (state) => state.mergeStateStatus === "DIRTY" || state.mergeable === "CONFLICTING";
|
|
42458
|
+
const conflictError = () => `pr land #${prNumber}: branch conflicts with base \u2014 merge origin/development into the branch (never rebase a pushed branch), rebuild any checked-in artifacts, then push before landing${hasQueue ? `; ${prQueueReadHint(prNumber, repo)}` : ""}`;
|
|
42459
|
+
const readPrState = async () => {
|
|
42460
|
+
const state = await deps.readPrState(prNumber, repo);
|
|
42461
|
+
if (!isConflicting(state)) return state;
|
|
42462
|
+
await (deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms))))(PR_HEAD_UPDATE_API_READ_DELAY_MS);
|
|
42463
|
+
return deps.readPrState(prNumber, repo);
|
|
42464
|
+
};
|
|
42465
|
+
let prState;
|
|
42466
|
+
const checksWaitError = async (checksWait) => {
|
|
42388
42467
|
if (checksWait.status === "conflicting") {
|
|
42389
|
-
|
|
42468
|
+
prState = await readPrState();
|
|
42469
|
+
return isConflicting(prState) ? conflictError() : void 0;
|
|
42390
42470
|
}
|
|
42391
42471
|
if (checksWait.status === "failure" || checksWait.status === "timeout" || checksWait.status === "rate-limited") {
|
|
42392
42472
|
return `checks-wait ${checksWait.status}${checksWait.detail ? `: ${checksWait.detail}` : ""}`;
|
|
@@ -42394,11 +42474,11 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
42394
42474
|
return void 0;
|
|
42395
42475
|
};
|
|
42396
42476
|
base.checksWait = await deps.waitForChecks(prNumber, repo);
|
|
42397
|
-
const waitError = checksWaitError(base.checksWait);
|
|
42398
|
-
if (waitError) return { ...base, error: hasQueue ? `${waitError} \u2014 ${prQueueReadHint(prNumber, repo)}` : waitError };
|
|
42477
|
+
const waitError = await checksWaitError(base.checksWait);
|
|
42478
|
+
if (waitError) return { ...base, error: hasQueue && base.checksWait.status !== "conflicting" ? `${waitError} \u2014 ${prQueueReadHint(prNumber, repo)}` : waitError };
|
|
42399
42479
|
const driftAfterChecks = await headDrift("after checks");
|
|
42400
42480
|
if (driftAfterChecks) return driftAfterChecks;
|
|
42401
|
-
|
|
42481
|
+
prState = prState ?? await readPrState();
|
|
42402
42482
|
if (!hasQueue && pinned && prState.mergeStateStatus === "BEHIND") {
|
|
42403
42483
|
return {
|
|
42404
42484
|
...base,
|
|
@@ -42413,16 +42493,11 @@ async function runPrLandOnce(prNumber, options, deps) {
|
|
|
42413
42493
|
return { ...base, error: `pr land #${prNumber}: ${e.message}` };
|
|
42414
42494
|
}
|
|
42415
42495
|
base.checksWait = await deps.waitForChecks(prNumber, repo);
|
|
42416
|
-
const reWaitError = checksWaitError(base.checksWait);
|
|
42417
|
-
if (reWaitError) return { ...base, error: hasQueue ? `${reWaitError} \u2014 ${prQueueReadHint(prNumber, repo)}` : reWaitError };
|
|
42418
|
-
prState = await
|
|
42419
|
-
}
|
|
42420
|
-
if (prState.mergeStateStatus === "DIRTY" || prState.mergeable === "CONFLICTING") {
|
|
42421
|
-
return {
|
|
42422
|
-
...base,
|
|
42423
|
-
error: `pr land #${prNumber}: branch conflicts with base \u2014 rebase/merge development, rebuild any checked-in artifacts, then re-push before landing${hasQueue ? `; ${prQueueReadHint(prNumber, repo)}` : ""}`
|
|
42424
|
-
};
|
|
42496
|
+
const reWaitError = await checksWaitError(base.checksWait);
|
|
42497
|
+
if (reWaitError) return { ...base, error: hasQueue && base.checksWait.status !== "conflicting" ? `${reWaitError} \u2014 ${prQueueReadHint(prNumber, repo)}` : reWaitError };
|
|
42498
|
+
prState = await readPrState();
|
|
42425
42499
|
}
|
|
42500
|
+
if (isConflicting(prState)) return { ...base, error: conflictError() };
|
|
42426
42501
|
if (hasQueue && !deps.enqueueQueue) throw new Error("queue-only enqueue adapter unavailable; no merge dispatched");
|
|
42427
42502
|
const merge = await mergeAutoWithTransientRetry(prNumber, repo, hasQueue ? { ...deps, mergeAuto: (pr, target) => deps.enqueueQueue(pr, target, pinned) } : deps);
|
|
42428
42503
|
base.mergeStatus = merge.mergeStatus;
|
|
@@ -58712,6 +58787,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
58712
58787
|
if (zip !== (o.out !== void 0)) {
|
|
58713
58788
|
const shapes = "api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs and repos/{owner}/{repo}/releases/assets/{id}";
|
|
58714
58789
|
if (!zip && o.out !== void 0) {
|
|
58790
|
+
if (req.kind === "contents") return fail(`${shapes} \u2014 for this file's bytes, drop --out and use \`--accept raw\` with a redirect: \`mmi-cli devops api get "${req.path}" --accept raw > <file>\` (decoded bytes on stdout, no base64 or JSON.parse). This refusal exits 1 on stderr with nothing written: check the exit code and never discard stderr (2>&1 / >/dev/null) before parsing.`);
|
|
58715
58791
|
return fail(`${shapes} \u2014 drop --out and let the JSON body go to stdout (pipe it into node/jq, or use --jq <expr>), or use the typed read (e.g. \`mmi-cli devops read run list --repo <owner/repo> --json databaseId,workflowName,event,status,conclusion\`). This refusal exits 1 on stderr with nothing written: check the exit code and never discard stderr (2>&1 / >/dev/null) before parsing.`);
|
|
58716
58792
|
}
|
|
58717
58793
|
return fail(`${shapes} \u2014 append \`--out <file>\` to this command (e.g. \`MMI_ACTOR=app mmi-cli devops api get ${req.path} --out .jerv/tmp/job.log\`), or for job logs use the typed read \`MMI_ACTOR=app mmi-cli devops read run log <runId> --repo <owner/repo> --job <name|id>\`. Shell redirection (\`>\` / \`2>&1\`) is not a substitute: this refusal exits 1 on stderr with nothing written to stdout, so \`>\` leaves a zero-byte file \u2014 delete it and check the exit code.`);
|
|
@@ -71172,6 +71248,24 @@ function parseWindow(raw, nowMs) {
|
|
|
71172
71248
|
}
|
|
71173
71249
|
return { ok: true, value: { since: formatUtc(nowMs - span), label: value, isDefault: false } };
|
|
71174
71250
|
}
|
|
71251
|
+
function parseUntil(raw, nowMs) {
|
|
71252
|
+
if (raw === void 0) return { ok: true, value: void 0 };
|
|
71253
|
+
const value = raw.trim();
|
|
71254
|
+
if (ABSOLUTE_WINDOW_RE.test(value)) return { ok: true, value: { until: value, label: value } };
|
|
71255
|
+
const rel = RELATIVE_WINDOW_RE.exec(value);
|
|
71256
|
+
if (!rel) {
|
|
71257
|
+
return {
|
|
71258
|
+
ok: false,
|
|
71259
|
+
reason: `--until must be a relative window like 15m, 6h or 2d, or a UTC time like 2026-10-07T13:10Z (got '${value}')`
|
|
71260
|
+
};
|
|
71261
|
+
}
|
|
71262
|
+
const unit = rel[2] === "m" ? MINUTE_MS : rel[2] === "h" ? 60 * MINUTE_MS : 24 * 60 * MINUTE_MS;
|
|
71263
|
+
const span = Number(rel[1]) * unit;
|
|
71264
|
+
if (span > TENANT_LOG_WINDOW_MAX_MS) {
|
|
71265
|
+
return { ok: false, reason: `--until ${value} is wider than 30d \u2014 the journal keeps 7 days, so a wider window buys no history and unbounded work` };
|
|
71266
|
+
}
|
|
71267
|
+
return { ok: true, value: { until: formatUtc(nowMs - span), label: value } };
|
|
71268
|
+
}
|
|
71175
71269
|
function tenantLogUnitPrefix(slug, stage) {
|
|
71176
71270
|
if (!SLUG_RE.test(slug)) return { ok: false, reason: `'${slug}' is not a tenant slug \u2014 refusing to build a unit namespace from it` };
|
|
71177
71271
|
if (!TENANT_LOG_STAGES.includes(stage)) {
|
|
@@ -71263,6 +71357,11 @@ async function readTenantLogs(deps, opts) {
|
|
|
71263
71357
|
const nowMs = deps.now();
|
|
71264
71358
|
const window = parseWindow(opts.since, nowMs);
|
|
71265
71359
|
if (!window.ok) return window;
|
|
71360
|
+
const until = parseUntil(opts.until, nowMs);
|
|
71361
|
+
if (!until.ok) return until;
|
|
71362
|
+
if (until.value && Date.parse(until.value.until) <= Date.parse(window.value.since)) {
|
|
71363
|
+
return { ok: false, reason: `--until ${until.value.label} must be after --since ${window.value.label}` };
|
|
71364
|
+
}
|
|
71266
71365
|
if (opts.grep !== void 0 && opts.grep.length === 0) return { ok: false, reason: "--grep needs a non-empty literal needle" };
|
|
71267
71366
|
const slug = opts.repo.includes("/") ? (opts.repo.split("/").pop() ?? "").toLowerCase() : opts.repo.toLowerCase();
|
|
71268
71367
|
const facts = await deps.deployFacts(slug);
|
|
@@ -71273,7 +71372,8 @@ async function readTenantLogs(deps, opts) {
|
|
|
71273
71372
|
stage: coords.value.stage,
|
|
71274
71373
|
action: "logs",
|
|
71275
71374
|
lines: tail.value,
|
|
71276
|
-
logSince: window.value.since
|
|
71375
|
+
logSince: window.value.since,
|
|
71376
|
+
logUntil: until.value?.until
|
|
71277
71377
|
});
|
|
71278
71378
|
const credentials = deps.credentials?.() ?? [];
|
|
71279
71379
|
const bounded = applyBound(redactJournal(read.logs ?? "", credentials), tail.value);
|
|
@@ -71296,12 +71396,15 @@ async function readTenantLogs(deps, opts) {
|
|
|
71296
71396
|
since: window.value.since,
|
|
71297
71397
|
sinceLabel: window.value.label,
|
|
71298
71398
|
sinceIsDefault: window.value.isDefault,
|
|
71399
|
+
...until.value ? { until: until.value.until, untilLabel: until.value.label } : {},
|
|
71299
71400
|
truncated: bounded.truncated,
|
|
71300
71401
|
dropped: bounded.dropped,
|
|
71301
71402
|
received: bounded.received
|
|
71302
71403
|
},
|
|
71303
|
-
...opts.grep ? { grep: { needle: opts.grep, matched: filtered.matched } } : {},
|
|
71304
|
-
|
|
71404
|
+
...opts.grep ? { grep: { needle: opts.grep, matched: filtered.matched, scanned: lines2.length } } : {},
|
|
71405
|
+
// #9878: report units from the BOUNDED lines, not the grep survivors — a grep miss must still say
|
|
71406
|
+
// which units answered, or a filter miss reads as data loss.
|
|
71407
|
+
units: observedUnits(lines2, coords.value.unitPrefix),
|
|
71305
71408
|
lines: filtered.lines,
|
|
71306
71409
|
conclusion: read.conclusion,
|
|
71307
71410
|
...read.runUrl ? { runUrl: read.runUrl } : {},
|
|
@@ -71314,18 +71417,18 @@ function renderTenantLogs(r) {
|
|
|
71314
71417
|
`runtime tenant logs ${r.repo} ${r.stage} \u2014 journald on the stage's own box, slot resolved from the tenant's coords (no unit name, no slot flag)`,
|
|
71315
71418
|
` tenant ${r.slug} (DEPLOY#${r.stage})${r.box ? ` box ${r.box}` : ""}${r.deployedTag ? ` tag ${r.deployedTag}` : ""}`,
|
|
71316
71419
|
` units ${r.unitPrefix}* (journald tag namespace derived from the coords)`,
|
|
71317
|
-
` window since ${r.bound.since}${r.bound.sinceIsDefault ? " (default 1h)" : ""} \u2014 journald keys by container NAME, so a deploy-recreated container still answers`,
|
|
71420
|
+
` window since ${r.bound.since}${r.bound.sinceIsDefault ? " (default 1h)" : ""}${r.bound.until ? ` until ${r.bound.until}` : ""} \u2014 journald keys by container NAME, so a deploy-recreated container still answers`,
|
|
71318
71421
|
` bound last ${r.bound.tail} line(s) of that window (max ${r.bound.tailMax}, default ${r.bound.tailDefault})`
|
|
71319
71422
|
];
|
|
71320
71423
|
if (r.bound.truncated) {
|
|
71321
71424
|
out.push(` NOTE the box returned ${r.bound.received} line(s); the bound kept the last ${r.bound.tail} and dropped ${r.bound.dropped}`);
|
|
71322
71425
|
}
|
|
71323
71426
|
if (r.grep) {
|
|
71324
|
-
out.push(` grep '${r.grep.needle}' kept ${r.grep.matched} of ${r.
|
|
71427
|
+
out.push(` grep '${r.grep.needle}' kept ${r.grep.matched} of ${r.grep.scanned} bounded line(s) \u2014 the filter runs on the BOUNDED tail, not the whole journal; widen --tail or narrow with --since/--until to search further back`);
|
|
71325
71428
|
}
|
|
71326
71429
|
out.push(` observed ${r.units.length ? r.units.map((u) => `${u.unit} (${u.lines})`).join(", ") : `no ${r.unitPrefix}* lines in this window`}`);
|
|
71327
71430
|
if (!r.lines.length) {
|
|
71328
|
-
out.push(" (no lines \u2014 the window is empty for this tenant stage; widen --since rather than concluding the app was quiet)");
|
|
71431
|
+
out.push(r.grep && r.grep.scanned > 0 ? ` NOTE grep matched 0 of ${r.grep.scanned} bounded line(s) \u2014 that is a filter miss, not an empty window; narrow with --since/--until or widen --tail` : " (no lines \u2014 the window is empty for this tenant stage; widen --since rather than concluding the app was quiet)");
|
|
71329
71432
|
} else {
|
|
71330
71433
|
out.push("---8<---");
|
|
71331
71434
|
out.push(...r.lines);
|
|
@@ -73402,7 +73505,7 @@ tenant.command("status <owner/repo> <stage>").description("read tenant runtime r
|
|
|
73402
73505
|
console.log(JSON.stringify(result, null, 2));
|
|
73403
73506
|
if (result.publicProbe?.ok === false || result.edgeCertificate?.missing) process.exitCode = 1;
|
|
73404
73507
|
});
|
|
73405
|
-
tenant.command("logs <owner/repo> <stage>").description("READ-ONLY: read a tenant stage's application journal (journald, so a deploy-recreated container still answers) \u2014 the unit namespace comes from the tenant's DEPLOY coords and the blue/green slot is chosen on the box, never from a flag; bounded and redacted (#9230)").option("--tail <n>", `lines to return (1-${TENANT_LOG_TAIL_MAX}, default ${TENANT_LOG_TAIL_DEFAULT}); the applied bound is printed`).option("--since <window>", "window start: a relative window like 15m, 6h or 2d, or a UTC time like 2026-10-07T13:10Z (default 1h)").option("--grep <needle>", "keep only returned lines containing this literal text (applied to the bounded tail, not the whole journal)").option("--json", "machine-readable output").action(async (repo, stage, o) => {
|
|
73508
|
+
tenant.command("logs <owner/repo> <stage>").description("READ-ONLY: read a tenant stage's application journal (journald, so a deploy-recreated container still answers) \u2014 the unit namespace comes from the tenant's DEPLOY coords and the blue/green slot is chosen on the box, never from a flag; bounded and redacted (#9230)").option("--tail <n>", `lines to return (1-${TENANT_LOG_TAIL_MAX}, default ${TENANT_LOG_TAIL_DEFAULT}); the applied bound is printed`).option("--since <window>", "window start: a relative window like 15m, 6h or 2d, or a UTC time like 2026-10-07T13:10Z (default 1h)").option("--until <time>", "window end: same forms as --since (default: now) \u2014 the box returns the newest N lines OF the window, so end it earlier to reach older lines on a busy tenant (#9878)").option("--grep <needle>", "keep only returned lines containing this literal text (applied to the bounded tail, not the whole journal)").option("--json", "machine-readable output").action(async (repo, stage, o) => {
|
|
73406
73509
|
try {
|
|
73407
73510
|
const cfg = await loadConfig();
|
|
73408
73511
|
const reg = registryClientDeps(cfg);
|
|
@@ -73410,12 +73513,12 @@ tenant.command("logs <owner/repo> <stage>").description("READ-ONLY: read a tenan
|
|
|
73410
73513
|
{
|
|
73411
73514
|
deployFacts: (slug) => fetchDeployFactsBySlug(slug, reg),
|
|
73412
73515
|
// The one dispatch a log read may make. Never watched=false: the run's OUTPUT is the answer.
|
|
73413
|
-
control: (opts) => runTenantControl(trainApplyDeps(), { repo: opts.repo, stage: opts.stage, action: opts.action, watch: true, lines: opts.lines, logSince: opts.logSince }),
|
|
73516
|
+
control: (opts) => runTenantControl(trainApplyDeps(), { repo: opts.repo, stage: opts.stage, action: opts.action, watch: true, lines: opts.lines, logSince: opts.logSince, logUntil: opts.logUntil }),
|
|
73414
73517
|
now: Date.now,
|
|
73415
73518
|
// The credentials this process holds, so the repo's exact-string redactor can strip them.
|
|
73416
73519
|
credentials: () => [process.env.GH_TOKEN].filter((c) => !!c)
|
|
73417
73520
|
},
|
|
73418
|
-
{ repo, stage, tail: o.tail, since: o.since, grep: o.grep }
|
|
73521
|
+
{ repo, stage, tail: o.tail, since: o.since, until: o.until, grep: o.grep }
|
|
73419
73522
|
);
|
|
73420
73523
|
if (!result.ok) return failGraceful(`runtime tenant logs: ${result.reason}`);
|
|
73421
73524
|
if (o.json) {
|
|
@@ -74132,6 +74235,13 @@ ci.command("audit").description("read-only fleet scan: gate workflow, ruleset co
|
|
|
74132
74235
|
else console.log(renderCiAuditText(report));
|
|
74133
74236
|
if (!report.ok) process.exitCode = 1;
|
|
74134
74237
|
});
|
|
74238
|
+
ci.command("hosted-usage").description("read-only hosted tick and lane-demand allowance proof from org config").option("--json", "machine-readable output").action(async (o) => {
|
|
74239
|
+
const org = await fetchOrgConfig(registryClientDeps(await loadConfig()));
|
|
74240
|
+
if (!org) return failGraceful("ci hosted-usage: Hub org config unavailable");
|
|
74241
|
+
const { projectHostedUsage: projectHostedUsage2, renderHostedUsage: renderHostedUsage2 } = await Promise.resolve().then(() => (init_ci_hosted_usage(), ci_hosted_usage_exports));
|
|
74242
|
+
const read = projectHostedUsage2(org);
|
|
74243
|
+
console.log(o.json ? JSON.stringify(read, null, 2) : renderHostedUsage2(read));
|
|
74244
|
+
});
|
|
74135
74245
|
registerCiChecksWait(ci);
|
|
74136
74246
|
registerCancelOrphans(ci, async () => (await mintCommandWriteToken(appActorDeps(), "ci cancel-orphans")).token);
|
|
74137
74247
|
var runRoot = program2.command("run").description("bounded workflow-run writes \u2014 `run cancel <id>` and `run rerun <id> [--failed]`, both on the org App actions:write profile");
|
package/cli/package.json
CHANGED
package/package.json
CHANGED
package/skills/hotfix/suite.md
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). On your own, run only the read-only previews `mmi-cli devops suite rcand --dry-run` and `mmi-cli devops suite hotfix <member> --dry-run`; `suite release` and `suite abandon` have no dry-run, so never run them yourself. A real suite rcand, release, hotfix or abandon runs only on the owner's go, executed by the owner or the suite orchestrator. A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
|
|
1
|
+
In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: run `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` instead (a lead-only cut needs `--suite-solo --owner-go`). On your own, run only the read-only previews `mmi-cli devops suite rcand --dry-run` and `mmi-cli devops suite hotfix <member> --dry-run`; `suite release` and `suite abandon` have no dry-run, so never run them yourself. A real suite rcand, release, hotfix or abandon runs only on the owner's go, executed by the owner or the suite orchestrator. A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync. A suite member never asks for, proposes or prompts an rc, release or hotfix — the suite orchestrator is the only voice that brings a cut to Jervaise, so a member session states what landed and stops instead of offering a cut.
|
|
2
2
|
|
package/skills/rcand/suite.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: the suite path is `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` (a lead-only cut needs `--suite-solo --owner-go`). `--owner-go` records Jervaise's explicit go for that one cut; it is never the agent's own flag. Never pass it on your own authority: a held train or a stale MM-Work cut (`suite abandon --owner-go`, then `suite rcand --owner-go`) is handed to the suite orchestrator with that exact command sequence, and runs only after the owner says go. On your own, run only the read-only previews `mmi-cli devops suite rcand --dry-run` and `mmi-cli devops suite hotfix <member> --dry-run`; `suite release` and `suite abandon` have no dry-run, so never run them yourself. A real suite rcand, release, hotfix or abandon runs only on the owner's go, executed by the owner or the suite orchestrator. A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync.
|
|
1
|
+
In the suite lead (MM-Work) a plain rcand, release or hotfix refuses: the suite path is `mmi-cli devops suite rcand --owner-go` or `mmi-cli devops suite release --owner-go` (a lead-only cut needs `--suite-solo --owner-go`). `--owner-go` records Jervaise's explicit go for that one cut; it is never the agent's own flag. Never pass it on your own authority: a held train or a stale MM-Work cut (`suite abandon --owner-go`, then `suite rcand --owner-go`) is handed to the suite orchestrator with that exact command sequence, and runs only after the owner says go. On your own, run only the read-only previews `mmi-cli devops suite rcand --dry-run` and `mmi-cli devops suite hotfix <member> --dry-run`; `suite release` and `suite abandon` have no dry-run, so never run them yourself. A real suite rcand, release, hotfix or abandon runs only on the owner's go, executed by the owner or the suite orchestrator. A suite member cannot run standalone rcand, release or hotfix: cut from the suite lead; only a backward-compatible solo backend release/hotfix with explicit owner approval may pass `--suite-solo --owner-go`, and its MM-Work development pin PR must land before the suite is in sync. A suite member never asks for, proposes or prompts an rc, release or hotfix — the suite orchestrator is the only voice that brings a cut to Jervaise, so a member session states what landed and stops instead of offering a cut.
|
|
2
2
|
|
|
3
3
|
Support sessions (MMI-Hub, Jerv-Hub, Jerv-JervCode) are not suite members and hold standing owner authorization (2026-10-25) to cut and ship releases at their discretion during suite runs without repeated per-release approval, and under the owner's 2026-11-01 ruling each support session strives during a suite run to finish its own board and cut a patch release for landed-but-unreleased merges so suite members pick the update up in that run instead of waiting for a later cut, keeping all standard release preflight, gate, and verification checks.
|
|
4
4
|
|