@rightkit/release 0.2.174 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -1
- package/bin/darwin/rightkit-swap +0 -0
- package/bin/darwin/rightkit-swap.sign.json +1 -1
- package/cli/right-release.mjs +29 -3
- package/command-spec.mjs +80 -0
- package/deps-cargo.mjs +55 -0
- package/deps.mjs +7 -0
- package/dist-scan.mjs +102 -0
- package/dmg-build.mjs +53 -4
- package/icon-pack.mjs +117 -0
- package/init-command.mjs +116 -0
- package/install-app.mjs +36 -3
- package/local-publish.mjs +317 -29
- package/macho-approval-hash.mjs +181 -0
- package/managed-cargo.mjs +49 -1
- package/media-sidecar.mjs +20 -4
- package/mirror-root-artifact.mjs +69 -21
- package/npm-license-notices.mjs +159 -0
- package/ownership-dependency-allowlist.json +1 -0
- package/ownership.mjs +37 -4
- package/package-recipe-targets.mjs +15 -1
- package/package.json +1 -1
- package/pe-content.mjs +133 -0
- package/publication-gate.mjs +70 -3
- package/publish-cargo.mjs +26 -7
- package/release-dependencies-command.mjs +45 -0
- package/release-dependencies.mjs +60 -0
- package/release-switch-scan.mjs +17 -7
- package/release.mjs +75 -4
- package/rightkit-ownership.json +46 -2
- package/rightkit-versions.json +5 -4
- package/runtime-budgets.mjs +14 -7
- package/stage-sidecar.mjs +505 -68
- package/test-command.mjs +190 -0
- package/toolchain-swift.mjs +239 -0
- package/web-build.mjs +67 -0
package/release.mjs
CHANGED
|
@@ -45,6 +45,7 @@ import { resolveMacosDeveloperIdIdentity } from "./macos-signing-identity.mjs";
|
|
|
45
45
|
import { DEFAULT_STORAGE_THRESHOLD_BYTES, findInstallerCopyScripts, findLargeRepositorySubdirectories } from "./installer-storage-doctor.mjs";
|
|
46
46
|
import { macosCodesignEnvironment } from "./codesign-environment.mjs";
|
|
47
47
|
import { assertNoQaControl, assertNoWebBundleMocks } from "./qa-control-scan.mjs";
|
|
48
|
+
import { assertReleaseDependencies } from "./release-dependencies.mjs";
|
|
48
49
|
import { assertNoForbiddenBuildFeatures } from "./qa-build-features.mjs";
|
|
49
50
|
import { expandRecipeBuildCommands } from "./recipe-build.mjs";
|
|
50
51
|
import { assertNoReleaseSwitches, commandTokens, resolveFeatureClosure } from "./release-switch-scan.mjs";
|
|
@@ -374,6 +375,7 @@ try {
|
|
|
374
375
|
// A `node recipe-build.mjs --plan <JSON>` prePackage hides each step's `--features` in one argument: the closure and the
|
|
375
376
|
// command token scan see every prepare / build / postBuild step (a plan that cannot be read or validated fails closed).
|
|
376
377
|
const closureCommands = [target.prePackage, target.package, nativeAssembly?.packageHook, nativeFinalizer, ...commandList(target.postSign), target.gradle].filter(Boolean).flatMap((c) => expandRecipeBuildCommands(c, { root }));
|
|
378
|
+
assertReleaseDependencies({ root, config, target, commands: closureCommands });
|
|
377
379
|
let treeText;
|
|
378
380
|
try {
|
|
379
381
|
const closure = resolveFeatureClosure({
|
|
@@ -1132,9 +1134,13 @@ function signExistingRefusal(source) {
|
|
|
1132
1134
|
// The renderer comes from this monorepo's own packages/ci when present, else from the repository's installed @rightkit/ci; with
|
|
1133
1135
|
// neither the file is refused, because an unverifiable sign-existing.yml must not be accepted.
|
|
1134
1136
|
async function loadRightKitCiRenderer(root) {
|
|
1135
|
-
|
|
1137
|
+
return (await loadRightKitCiModule(root, "sync.mjs", "renderLanes"))?.renderLanes ?? null;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
async function loadRightKitCiModule(root, file, exportName) {
|
|
1141
|
+
const candidates = [pathToFileURL(fileURLToPath(new URL(`../ci/${file}`, import.meta.url))).href];
|
|
1136
1142
|
try {
|
|
1137
|
-
candidates.push(pathToFileURL(createRequire(path.join(root, "package.json")).resolve(
|
|
1143
|
+
candidates.push(pathToFileURL(createRequire(path.join(root, "package.json")).resolve(`@rightkit/ci/${file}`)).href);
|
|
1138
1144
|
} catch {
|
|
1139
1145
|
// not installed in the repository; the sibling candidate may still apply
|
|
1140
1146
|
}
|
|
@@ -1142,7 +1148,7 @@ async function loadRightKitCiRenderer(root) {
|
|
|
1142
1148
|
try {
|
|
1143
1149
|
if (href.startsWith("file:") && !existsSync(fileURLToPath(href))) continue;
|
|
1144
1150
|
const loaded = await import(href);
|
|
1145
|
-
if (typeof loaded
|
|
1151
|
+
if (typeof loaded[exportName] === "function") return loaded;
|
|
1146
1152
|
} catch {
|
|
1147
1153
|
// try the next candidate
|
|
1148
1154
|
}
|
|
@@ -1165,6 +1171,57 @@ async function signExistingRenderRefusal(root, manifest, source) {
|
|
|
1165
1171
|
}
|
|
1166
1172
|
|
|
1167
1173
|
|
|
1174
|
+
// `workflows` block entries render from code, so they are admitted only when the block passes @rightkit/ci's own
|
|
1175
|
+
// validateWorkflows and each file is byte-for-byte the @rightkit/ci render. With no resolvable @rightkit/ci they are refused.
|
|
1176
|
+
async function validateDeclaredWorkflowBlock(root, appName, manifest, declaredFiles) {
|
|
1177
|
+
const validator = (await loadRightKitCiModule(root, "workflows.mjs", "validateWorkflows"))?.validateWorkflows;
|
|
1178
|
+
const renderLanes = await loadRightKitCiRenderer(root);
|
|
1179
|
+
if (!validator || !renderLanes) fail(`${appName} right-git-ci-only policy cannot verify .rightgit.json workflows: @rightkit/ci is not available (install @rightkit/ci as a devDependency)`);
|
|
1180
|
+
const errors = [];
|
|
1181
|
+
validator(manifest, errors);
|
|
1182
|
+
if (errors.length > 0) fail(`${appName} right-git-ci-only policy refuses the .rightgit.json workflows block: ${errors.join("; ")}`);
|
|
1183
|
+
let rendered;
|
|
1184
|
+
try {
|
|
1185
|
+
rendered = renderLanes(manifest, root);
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
fail(`${appName} right-git-ci-only policy cannot render the .rightgit.json workflows block: ${error.message}`);
|
|
1188
|
+
}
|
|
1189
|
+
for (const file of declaredFiles) {
|
|
1190
|
+
const expected = rendered.find((entry) => entry.filename === file)?.content;
|
|
1191
|
+
const actual = await readFile(path.join(root, ".github", "workflows", file), "utf8");
|
|
1192
|
+
if (typeof expected !== "string" || actual.replace(/\r\n?/g, "\n") !== expected.replace(/\r\n?/g, "\n")) {
|
|
1193
|
+
fail(`${appName} ${file} is not byte-for-byte the @rightkit/ci render of its .rightgit.json workflows entry (run right-git sync)`);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// Structural backstop for declared workflows (after render equality): the one top-level permissions block is exactly
|
|
1199
|
+
// `contents: read`, no other permissions key exists, no secrets reference, every `uses:` is commit-pinned, and every
|
|
1200
|
+
// checkout step sets persist-credentials: false. Steps are split on step-list indentation, not matched as free text.
|
|
1201
|
+
function declaredWorkflowPolicyRefusal(appName, workflow, source) {
|
|
1202
|
+
const refuse = (why) => fail(`${appName} right-git managed ${workflow} violates the declared-workflow policy: ${why}`);
|
|
1203
|
+
const lines = source.replace(/\r\n?/g, "\n").split("\n");
|
|
1204
|
+
const permissionsAt = lines.flatMap((line, i) => (/^\s*permissions\s*:/.test(line) ? [i] : []));
|
|
1205
|
+
if (permissionsAt.length !== 1 || lines[permissionsAt[0]] !== "permissions:" || lines[permissionsAt[0] + 1] !== " contents: read" || /^ {2,}\S/.test(lines[permissionsAt[0] + 2] ?? "")) refuse("permissions must be exactly one top-level `contents: read`");
|
|
1206
|
+
if (/\bsecrets\b/i.test(source)) refuse("it references the secrets context");
|
|
1207
|
+
let step = null;
|
|
1208
|
+
const steps = [];
|
|
1209
|
+
for (const line of lines) {
|
|
1210
|
+
if (/^ {6}- /.test(line)) { step = [line]; steps.push(step); }
|
|
1211
|
+
else if (step && (/^ {7,}\S/.test(line) || line === "")) step.push(line);
|
|
1212
|
+
else step = null;
|
|
1213
|
+
}
|
|
1214
|
+
for (const body of steps) {
|
|
1215
|
+
const uses = /^ {6}- uses: (\S+)|^ {8}uses: (\S+)/m.exec(body.join("\n"));
|
|
1216
|
+
if (!uses) continue;
|
|
1217
|
+
const ref = uses[1] ?? uses[2];
|
|
1218
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+@[0-9a-f]{40}$/.test(ref)) refuse(`action ${ref} is not commit-pinned`);
|
|
1219
|
+
if (/^actions\/checkout@/.test(ref) && !body.some((line) => /^ {10}persist-credentials: false\s*$/.test(line))) refuse("a checkout step does not set persist-credentials: false");
|
|
1220
|
+
if (body.some((line) => /persist-credentials:\s*true/.test(line))) refuse("persist-credentials: true");
|
|
1221
|
+
}
|
|
1222
|
+
if (/^\s*(?:- )?uses:/m.test(lines.filter((l) => !steps.some((b) => b.includes(l))).join("\n"))) refuse("a uses: sits outside a step list");
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1168
1225
|
async function validateHostedWorkflows(root, appName, workflows, policy) {
|
|
1169
1226
|
if (policy !== "right-git-ci-only") {
|
|
1170
1227
|
fail(`${appName} hosted workflow files are forbidden: ${workflows.join(", ")}`);
|
|
@@ -1188,15 +1245,29 @@ async function validateHostedWorkflows(root, appName, workflows, policy) {
|
|
|
1188
1245
|
|| lanes.some((lane) => !safeLanes.has(lane))) {
|
|
1189
1246
|
fail(`${appName} right-git-ci-only policy requires compile=github-actions-only plus unique .rightgit.json lanes drawn from [${[...safeLanes].map((lane) => `"${lane}"`).join(", ")}] and including "ci" or "swift-check"`);
|
|
1190
1247
|
}
|
|
1191
|
-
|
|
1248
|
+
// Workflows declared under the manifest's `workflows` block (@rightkit/ci workflows.mjs: explicit `file`,
|
|
1249
|
+
// else `<name>.yml`) are managed files too, in addition to the lane files.
|
|
1250
|
+
const declaredWorkflows = manifest?.workflows;
|
|
1251
|
+
if (declaredWorkflows !== undefined && (declaredWorkflows === null || typeof declaredWorkflows !== "object" || Array.isArray(declaredWorkflows))) {
|
|
1252
|
+
fail(`${appName} right-git-ci-only policy requires .rightgit.json workflows to be an object`);
|
|
1253
|
+
}
|
|
1254
|
+
const declaredFiles = Object.entries(declaredWorkflows ?? {}).map(([name, config]) => {
|
|
1255
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) fail(`${appName} right-git-ci-only policy requires .rightgit.json workflows.${name} to be an object`);
|
|
1256
|
+
const file = typeof config?.file === "string" ? config.file : `${name}.yml`;
|
|
1257
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,60}\.yml$/.test(file)) fail(`${appName} right-git-ci-only policy refuses workflow file name ${JSON.stringify(file)} declared by .rightgit.json`);
|
|
1258
|
+
return file;
|
|
1259
|
+
});
|
|
1260
|
+
const expectedWorkflows = [...new Set([...lanes.map((lane) => `${lane}.yml`), ...declaredFiles])].sort();
|
|
1192
1261
|
if (JSON.stringify(workflows) !== JSON.stringify(expectedWorkflows)) {
|
|
1193
1262
|
fail(`${appName} right-git-ci-only policy requires workflows declared by .rightgit.json; expected ${expectedWorkflows.join(", ")}, got: ${workflows.join(", ")}`);
|
|
1194
1263
|
}
|
|
1264
|
+
if (declaredFiles.length > 0) await validateDeclaredWorkflowBlock(root, appName, manifest, declaredFiles);
|
|
1195
1265
|
for (const workflow of expectedWorkflows) {
|
|
1196
1266
|
const source = await readFile(path.join(root, ".github", "workflows", workflow), "utf8");
|
|
1197
1267
|
if (!source.startsWith("# Managed by right-git")) {
|
|
1198
1268
|
fail(`${appName} ${workflow} is not marked as right-git managed`);
|
|
1199
1269
|
}
|
|
1270
|
+
if (declaredFiles.includes(workflow)) declaredWorkflowPolicyRefusal(appName, workflow, source);
|
|
1200
1271
|
const jobSpan = (jobName) => {
|
|
1201
1272
|
const marker = `\n ${jobName}:\n`;
|
|
1202
1273
|
const start = source.indexOf(marker);
|
package/rightkit-ownership.json
CHANGED
|
@@ -671,6 +671,17 @@
|
|
|
671
671
|
"Fingerprints identify review candidates; published two-host replacement qualification is not recorded."
|
|
672
672
|
]
|
|
673
673
|
},
|
|
674
|
+
{
|
|
675
|
+
"package": "rightkit-crypto",
|
|
676
|
+
"capability": "crypto-primitives",
|
|
677
|
+
"summary": "Neutral SHA-256, HMAC-SHA256, constant-time compare, base64/base64url, hex, BLAKE3 text hash, canonical JSON and HMAC approval tokens",
|
|
678
|
+
"fingerprints": [],
|
|
679
|
+
"detection": "declaration-only",
|
|
680
|
+
"policyNotes": [
|
|
681
|
+
"Declaration-only: apps call this crate instead of hand-rolling HMAC, constant-time compare, base64 or approval tokens.",
|
|
682
|
+
"Not a general cryptography suite: no encryption, signatures or key derivation."
|
|
683
|
+
]
|
|
684
|
+
},
|
|
674
685
|
{
|
|
675
686
|
"package": "rightkit-daemon",
|
|
676
687
|
"capability": "local-services",
|
|
@@ -1320,6 +1331,17 @@
|
|
|
1320
1331
|
"Fingerprints identify review candidates (competing dependencies and distinctive implementation code); published two-host replacement qualification is not recorded."
|
|
1321
1332
|
]
|
|
1322
1333
|
},
|
|
1334
|
+
{
|
|
1335
|
+
"package": "rightkit-google-identity",
|
|
1336
|
+
"capability": "google-identity",
|
|
1337
|
+
"summary": "Google sign-in building blocks: PKCE, redirect, token request body, ID-token claim validation and refresh-token store; Android Credential Manager seam",
|
|
1338
|
+
"fingerprints": [],
|
|
1339
|
+
"detection": "declaration-only",
|
|
1340
|
+
"policyNotes": [
|
|
1341
|
+
"Declaration-only: apps call this crate instead of hand-rolling Google OAuth request shapes.",
|
|
1342
|
+
"ID-token signatures are not verified here; callers must not grant access on claims alone."
|
|
1343
|
+
]
|
|
1344
|
+
},
|
|
1323
1345
|
{
|
|
1324
1346
|
"package": "rightkit-hooks",
|
|
1325
1347
|
"capability": "lifecycle-hooks",
|
|
@@ -1416,6 +1438,17 @@
|
|
|
1416
1438
|
"Fingerprints identify review candidates; published two-host replacement qualification is not recorded."
|
|
1417
1439
|
]
|
|
1418
1440
|
},
|
|
1441
|
+
{
|
|
1442
|
+
"package": "rightkit-identity",
|
|
1443
|
+
"capability": "installation-identity",
|
|
1444
|
+
"summary": "Installation identity with clone detection and startup claim, build identity (git rev, dirty flag, profile, channel, UTC stamp), device pairing with signed transcript, owner approval and resume tickets, bearer-token device auth with per-IP rate limit",
|
|
1445
|
+
"fingerprints": [],
|
|
1446
|
+
"detection": "declaration-only",
|
|
1447
|
+
"policyNotes": [
|
|
1448
|
+
"Declaration-only: apps call this crate instead of writing their own install ids, pairing transcripts or bearer-token checks.",
|
|
1449
|
+
"Key agreement is out of scope: the caller supplies the shared secret."
|
|
1450
|
+
]
|
|
1451
|
+
},
|
|
1419
1452
|
{
|
|
1420
1453
|
"package": "rightkit-job-queue",
|
|
1421
1454
|
"capability": "persistent-jobs",
|
|
@@ -2946,12 +2979,12 @@
|
|
|
2946
2979
|
{
|
|
2947
2980
|
"package": "rightkit-spend",
|
|
2948
2981
|
"capability": "spend-ledger",
|
|
2949
|
-
"summary": "Spend reservation, allowance and compare-and-swap ledger on SQLite: integer micro-units, idempotent reserve on request id, in-flight and overrun handling, opaque allowance periods",
|
|
2982
|
+
"summary": "Spend reservation, allowance and compare-and-swap ledger on SQLite: integer micro-units, idempotent reserve on request id, in-flight and overrun handling, opaque allowance periods, optional account key and per-reservation currency, billed-unknown commit, caller-transaction (`_in`) variants, strict transition mode",
|
|
2950
2983
|
"fingerprints": [],
|
|
2951
2984
|
"detection": "declaration-only",
|
|
2952
2985
|
"policyNotes": [
|
|
2953
2986
|
"Declaration-only: the ledger tables and reservation state machine are not an ownership fingerprint.",
|
|
2954
|
-
"Price rows live in rightkit-llm; the ledger stores opaque route and price-version text
|
|
2987
|
+
"Price rows live in rightkit-llm; the ledger stores opaque route and price-version text; an allowance holds no currency and a reservation holds only the currency text the caller passes."
|
|
2955
2988
|
]
|
|
2956
2989
|
},
|
|
2957
2990
|
{
|
|
@@ -3078,6 +3111,17 @@
|
|
|
3078
3111
|
"Fingerprints identify review candidates; published two-host replacement qualification is not recorded."
|
|
3079
3112
|
]
|
|
3080
3113
|
},
|
|
3114
|
+
{
|
|
3115
|
+
"package": "rightkit-time",
|
|
3116
|
+
"capability": "time-civil",
|
|
3117
|
+
"summary": "RFC 3339 parse and format, fixed UTC offsets, civil date math and HTTP-date formatting",
|
|
3118
|
+
"fingerprints": [],
|
|
3119
|
+
"detection": "declaration-only",
|
|
3120
|
+
"policyNotes": [
|
|
3121
|
+
"Declaration-only: apps call this crate instead of hand-rolling RFC 3339, civil date math or HTTP-date formatting.",
|
|
3122
|
+
"No time zone database: named zones and DST rules are not owned here; callers pass a fixed offset."
|
|
3123
|
+
]
|
|
3124
|
+
},
|
|
3081
3125
|
{
|
|
3082
3126
|
"package": "rightkit-transcript",
|
|
3083
3127
|
"capability": "timed-transcripts",
|
package/rightkit-versions.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@rightkit/qa": "0.4.9",
|
|
32
32
|
"@rightkit/qa-chrome": "0.1.8",
|
|
33
33
|
"@rightkit/react-shell": "0.6.8",
|
|
34
|
-
"@rightkit/release": "0.
|
|
34
|
+
"@rightkit/release": "0.3.0",
|
|
35
35
|
"@rightkit/settings-ui": "0.5.0",
|
|
36
36
|
"@rightkit/tauri": "0.2.1",
|
|
37
37
|
"@rightkit/tauri-shell": "0.1.1",
|
|
@@ -211,7 +211,8 @@
|
|
|
211
211
|
"0.2.164",
|
|
212
212
|
"0.2.165",
|
|
213
213
|
"0.2.172",
|
|
214
|
-
"0.2.173"
|
|
214
|
+
"0.2.173",
|
|
215
|
+
"0.2.174"
|
|
215
216
|
],
|
|
216
217
|
"@rightkit/qa": [
|
|
217
218
|
"0.1.0",
|
|
@@ -458,7 +459,7 @@
|
|
|
458
459
|
"rightkit-browser": "0.3.1",
|
|
459
460
|
"rightkit-cache": "0.2.0",
|
|
460
461
|
"rightkit-channel-memory": "0.2.1",
|
|
461
|
-
"rightkit-child-process": "0.4.
|
|
462
|
+
"rightkit-child-process": "0.4.3",
|
|
462
463
|
"rightkit-compose": "0.1.0",
|
|
463
464
|
"rightkit-contracts": "0.1.5",
|
|
464
465
|
"rightkit-control": "0.3.0",
|
|
@@ -542,7 +543,7 @@
|
|
|
542
543
|
"@rightkit/qa": "0.4.9",
|
|
543
544
|
"@rightkit/qa-chrome": "0.1.8",
|
|
544
545
|
"@rightkit/react-shell": "0.6.8",
|
|
545
|
-
"@rightkit/release": "0.2.
|
|
546
|
+
"@rightkit/release": "0.2.174",
|
|
546
547
|
"@rightkit/settings-ui": "0.5.0",
|
|
547
548
|
"@rightkit/shell": "0.1.1",
|
|
548
549
|
"@rightkit/tauri": "0.2.1",
|
package/runtime-budgets.mjs
CHANGED
|
@@ -755,16 +755,18 @@ export function appRuntimeBudgetAdoptionCheck({ appRoot, app, version, cargoLock
|
|
|
755
755
|
const used = Object.keys(classes).filter((name) => locked.has(name));
|
|
756
756
|
if (used.length === 0) return null;
|
|
757
757
|
const problems = [];
|
|
758
|
+
// A crate whose ceilings or scale RightKit has not declared yet cannot be measured against anything, so it
|
|
759
|
+
// never blocks an app: it is reported as pending (warn) until the crate declares its budget.
|
|
760
|
+
const pending = used.filter((name) => !classes[name].ceilings || !classes[name].requiredScale);
|
|
761
|
+
const measurable = used.filter((name) => !pending.includes(name));
|
|
758
762
|
const adoptionPath = path.join(appRoot, APP_ADOPTION_FILE);
|
|
759
763
|
let records = [];
|
|
760
|
-
if (!exists(adoptionPath)) problems.push(`${APP_ADOPTION_FILE} is missing`);
|
|
764
|
+
if (!exists(adoptionPath)) { if (measurable.length) problems.push(`${APP_ADOPTION_FILE} is missing`); }
|
|
761
765
|
else {
|
|
762
766
|
try { records = JSON.parse(readFile(adoptionPath, "utf8")).measurements ?? []; } catch (error) { problems.push(`${APP_ADOPTION_FILE} is not valid JSON: ${error.message}`); }
|
|
763
767
|
}
|
|
764
|
-
for (const crate of
|
|
768
|
+
for (const crate of measurable) {
|
|
765
769
|
const { ceilings, requiredScale } = classes[crate];
|
|
766
|
-
if (!ceilings) { problems.push(`${crate}: ceilings undeclared (declare them in its runtime-budget.json and regenerate ${CLASSES_FILE})`); continue; }
|
|
767
|
-
if (!requiredScale) { problems.push(`${crate}: requiredScale undeclared`); continue; }
|
|
768
770
|
const mine = records.map((m, index) => ({ m, index })).filter(({ m }) => m?.crate === crate && m.scope === "installed" && locked.get(crate).has(m.crateVersion));
|
|
769
771
|
if (mine.length === 0) { problems.push(`${crate} ${[...locked.get(crate)].join("/")}: no installed-scope measurement of ${app}`); continue; }
|
|
770
772
|
const good = mine.filter(({ m, index }) => {
|
|
@@ -781,9 +783,14 @@ export function appRuntimeBudgetAdoptionCheck({ appRoot, app, version, cargoLock
|
|
|
781
783
|
});
|
|
782
784
|
if (good.length === 0 && !problems.some((p) => p.startsWith(`${crate}`))) problems.push(`${crate}: no qualifying measurement`);
|
|
783
785
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
786
|
+
const pendingNote = pending.length ? `; pending RightKit budget declaration (not blocking): ${pending.join(", ")}` : "";
|
|
787
|
+
if (problems.length) {
|
|
788
|
+
return { name: RUNTIME_BUDGET_CHECK, status: "fail", detail: `${app} uses held-resource crate(s) ${measurable.join(", ")}: ${problems.join("; ")}${pendingNote}`, fix: `record an installed measurement of ${app} for each crate in ${APP_ADOPTION_FILE} (docs/RUNTIME-BUDGETS.md)` };
|
|
789
|
+
}
|
|
790
|
+
if (pending.length) {
|
|
791
|
+
return { name: RUNTIME_BUDGET_CHECK, status: "warn", detail: `${app} uses held-resource crate(s) whose RightKit ceilings are not declared yet: ${pending.join(", ")}${measurable.length ? `; measured: ${measurable.join(", ")}` : ""}`, fix: "RightKit declares the crate ceilings; then record installed measurements (docs/RUNTIME-BUDGETS.md)" };
|
|
792
|
+
}
|
|
793
|
+
return { name: RUNTIME_BUDGET_CHECK, status: "ok", detail: `${app} has installed measurements for ${used.join(", ")}` };
|
|
787
794
|
}
|
|
788
795
|
|
|
789
796
|
// ---------------------------------------------------------------- record (measurement provenance)
|