@rightkit/release 0.6.10 → 0.6.11
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 +7 -5
- package/admission-files.mjs +3 -0
- package/admission.mjs +2 -2
- package/advisory-exceptions.mjs +41 -5
- package/android-play-upload.mjs +16 -15
- package/app-check.mjs +13 -9
- package/bin/rightkit-gate.mjs +762 -345
- package/broker-pins.mjs +152 -0
- package/build-release.mjs +80 -27
- package/cargo-ledger.mjs +62 -0
- package/cargo-target-shim.mjs +98 -26
- package/cli/right-release.mjs +28 -4
- package/clippy-equivalence.mjs +202 -0
- package/clippy-route.mjs +543 -0
- package/committed-source.mjs +39 -5
- package/crate-workspaces.mjs +87 -0
- package/create-mac-updater.mjs +14 -2
- package/deterministic-scans.mjs +196 -21
- package/development.mjs +13 -4
- package/direct-receipt.mjs +135 -0
- package/door-policy.mjs +10 -0
- package/door.mjs +6 -6
- package/freshness-census.mjs +257 -0
- package/gate-stages.mjs +76 -10
- package/hook-retirement.mjs +65 -0
- package/init-command.mjs +1 -1
- package/inventory-check.mjs +11 -8
- package/ios-upload.mjs +20 -15
- package/local-publish.mjs +77 -41
- package/local-qualify.mjs +5 -3
- package/local-runtime.mjs +5 -4
- package/mac-package-recipe.mjs +4 -1
- package/mirror-root-artifact.mjs +18 -5
- package/ownership-class-rules.mjs +1 -1
- package/ownership-evidence.mjs +37 -13
- package/ownership-hooks.mjs +20 -59
- package/ownership-lockfiles.mjs +14 -1
- package/ownership-policy-bundle.mjs +3 -2
- package/ownership-staged.mjs +2 -1
- package/ownership.mjs +24 -0
- package/package-recipe-targets.mjs +35 -6
- package/package.json +1 -1
- package/pipeline-admission.mjs +4 -4
- package/preflight.mjs +27 -4
- package/publish-cargo.mjs +4 -3
- package/recipe-build.mjs +38 -14
- package/release-artifact-path.mjs +341 -0
- package/release-invocation.mjs +8 -1
- package/release-qa-command.mjs +25 -9
- package/release-source-brand.mjs +17 -0
- package/release-source-identity.mjs +171 -0
- package/release.mjs +44 -17
- package/rightkit-versions.json +62 -42
- package/runtime-budgets.mjs +6 -2
- package/scan-digest.mjs +71 -5
- package/sign-updater.mjs +6 -3
- package/snapshot-broker.mjs +172 -0
- package/snapshot-check.mjs +12 -6
- package/snapshot-request.mjs +207 -5
- package/snapshot.mjs +123 -10
- package/source-split.mjs +73 -0
- package/stage-commands.mjs +2 -2
- package/stage-ledger.mjs +204 -0
- package/stage-sidecar-command.mjs +5 -1
- package/stage-sidecar.mjs +100 -20
- package/swap-helper-bundle.mjs +5 -2
- package/unit-gate.mjs +45 -3
- package/web-build.mjs +6 -4
- package/win-package-recipe.mjs +20 -7
package/README.md
CHANGED
|
@@ -213,14 +213,14 @@ Adrian, 2026-10-26: the apps lost every build route on 2026-10-25 and build agai
|
|
|
213
213
|
|
|
214
214
|
| Tier | Rules | Owed by |
|
|
215
215
|
|---|---|---|
|
|
216
|
-
| `compile` (default) | the 13 rules @rightkit/release 0.6.6 published (`COMPILE_TIER_IDS`) | compile admission: the RustRight broker,
|
|
216
|
+
| `compile` (default) | the 13 rules @rightkit/release 0.6.6 published (`COMPILE_TIER_IDS`) | compile admission: the RustRight broker, `rightkit-gate check`, `--to compile\|package`, `right-release build\|dev\|test\|doctor` |
|
|
217
217
|
| `release` | every rule, the ones added since 0.6.6 included (C3, SC, CA, LG, SF-01, OW-05, OW-06), plus the `shellcheck` scan and actionlint's shellcheck requirement | `rightkit-gate --to qualify` (it runs `check --rule-tier release` before installed QA and records `ruleTier` in the receipt) and `right-release release\|qa\|upload\|publish\|publish-update\|github\|sign-*\|create-mac-updater\|sign-candidate` (`RELEASE_TIER_COMMANDS`) |
|
|
218
218
|
|
|
219
219
|
`rightkit-gate check --rule-tier compile|release` selects the tier (`--tier patch|update` is the upload tier); `--list` names each rule's tier; `--only` is a view over the whole set. A rule is promoted to the compile tier by moving its id into `COMPILE_TIER_IDS` in RightKit, never per app and never through a baseline. RightKit's own preflight runs every rule.
|
|
220
220
|
|
|
221
|
-
## The
|
|
221
|
+
## The gate on a host without the broker, and tests that reach it
|
|
222
222
|
|
|
223
|
-
The gate chooses its executor itself (`gate-executor.mjs` `resolveExecutor`), never the caller: the RustRight broker on a managed Mac or Dell (chosen by host identity, not by `rustright` on PATH), a refusal on a broker-managed machine whose broker cannot be reached, and direct `cargo` on every Linux host and any other host (a cloud session, a CI runner). Every host runs the same gates in the same order (app rules, class A, then the compile scans); an unmanaged host is never sent to a broker and nothing is deferred to one.
|
|
223
|
+
The gate chooses its executor itself (`gate-executor.mjs` `resolveExecutor`), never the caller: the RustRight broker on a managed Mac or Dell (chosen by host identity, not by `rustright` on PATH), a refusal on a broker-managed machine whose broker cannot be reached, and direct `cargo` on every Linux host and any other host (a cloud session, a CI runner). Every host runs the same gates in the same order (app rules, class A, then the compile scans); an unmanaged host is never sent to a broker and nothing is deferred to one. Commit and push run no gate and no local hook check, and `install-hook` writes no git hook (below); when git's ref lines are nevertheless given on stdin (the form a pre-push hook receives), every ref is judged in any position (split on single ASCII spaces, so a Unicode space in a ref name cannot hide one), and a line that is not four fields is refused, never dropped, so the pushed commit is always compared with HEAD.
|
|
224
224
|
|
|
225
225
|
`scriptBrokerCall` reads `package.json` scripts only: it follows `pnpm <script>`, `pnpm run`, `npm run`, `yarn` and the wrapper words, not `node -e`, a node script, `sh -c`, or a run in another package (`pnpm --dir`, `--filter`, `-r`). A unit whose test reaches the broker that way says so in the app's own `ci/gate.config.json`, in the `npm` entry the unit's `quietTests` already uses (the key is the unit name: the package name, or the directory with `/` as `-` for a unit of a pnpm root below the repository root):
|
|
226
226
|
|
|
@@ -231,9 +231,11 @@ The gate chooses its executor itself (`gate-executor.mjs` `resolveExecutor`), ne
|
|
|
231
231
|
|
|
232
232
|
The `reason` is required and is printed. The file is read from the gated commit, so the declaration is a reviewed change in the diff that adds it. Where the Rust gate is deferred, the unit's `test:npm:<unit>` scan is deferred with it and named `declared in ci/gate.config.json npm.<unit>.requiresBroker: <reason>`, never reported as passed; on a host with the broker, in CI, on a protected ref and with the opt-out it runs as it did. A declaration of any other shape (`true`, a string, an empty or blank `reason`, an array) is ignored with a note and the test runs where it can: a malformed entry never skips a test. `false` or `null` is no declaration, and a call the scripts do show is reported from the scripts. The gate does not read node scripts for a literal `rustright` or `cargo`: a literal cannot tell a spawn from a command table, a mention or a test fixture, so the declaration is the way.
|
|
233
233
|
|
|
234
|
-
##
|
|
234
|
+
## `install-hook` writes no git hook, and retires the ones an earlier release wrote
|
|
235
235
|
|
|
236
|
-
|
|
236
|
+
Commit and push run no gate and no local hook check (`docs/GATE-CONTRACT.md`: the gate is for building; CI runs preflight and the full check on every push). `rightkit-gate install-hook` (a `prepare` script; `right-release hooks install` is an alias) registers the one-door agent hook in `.claude/settings.json` and writes no git hook: no `.githooks/pre-push`, no forwarder for the hooks in `.git/hooks`, no pre-commit ownership step, and it no longer sets `core.hooksPath`.
|
|
237
|
+
|
|
238
|
+
It removes what an earlier release wrote, each file matched by its own text only: `.githooks/pre-push` by its header (line 2: `# Mandatory local [check and] gate before every push (rightkit-gate install-hook). Never bypass with --no-verify.`), a `.githooks/<name>` forwarder by its header naming that same hook, and the ownership pre-commit hook by the marker `rightkit-ownership-precommit v1` in its first three lines (in `.githooks`, in `.git/hooks`, or in the directory `core.hooksPath` names); the foreign pre-commit the ownership step kept as `pre-commit.rightkit-foreign` goes back to `pre-commit`. A foreign hook, a symbolic link, a directory and any file whose header differs are never read for change, moved or deleted. It unsets the repository's own `core.hooksPath` only when its value is exactly `.githooks` and nothing foreign remains in that directory (an emptied `.githooks` is removed, and the hooks in `.git/hooks` run again); otherwise it leaves the setting and says so. The global and system git configuration is never written. `--force` is accepted and ignored: there is nothing to overwrite. `right-release ownership --staged` stays as a manual command; broker admission and the release gate remain the authoritative ownership checks. An app deletes its committed `.githooks/pre-push` after the release carrying this change is published.
|
|
237
239
|
|
|
238
240
|
## The rustfmt scan reads each file's edition from its crate
|
|
239
241
|
|
package/admission-files.mjs
CHANGED
|
@@ -69,6 +69,9 @@ export const FILES_ROWS = Object.freeze([
|
|
|
69
69
|
run(ctx) {
|
|
70
70
|
const inputs = ctx.buildInputs();
|
|
71
71
|
if (!inputs.ok) return { skipped: "build-inputs is reported on its own" };
|
|
72
|
+
// A door source split (row 7.04): `repoRoot` is then the snapshot, the commit's bytes verified by the door at the start and end of the
|
|
73
|
+
// stage, and there is no checkout cleanliness to ask. Every input resolved under it by construction.
|
|
74
|
+
if (ctx.releaseSource?.mode === "snapshot") return undefined;
|
|
72
75
|
const dirty = dirtyBuildInputs(ctx.repoRoot, inputs.value.buildInputs, ctx.commit());
|
|
73
76
|
return dirty.length > 0 ? [`dirty files can change the packaged app; resolve them before release:\n${dirty.map((file) => `- ${file}`).join("\n")}`] : undefined;
|
|
74
77
|
},
|
package/admission.mjs
CHANGED
|
@@ -60,7 +60,7 @@ function gitHead(repoRoot, spawn) {
|
|
|
60
60
|
export async function runAdmission({
|
|
61
61
|
root, repoRoot = root, configPath, config, platform, target, tiers = TIERS, env = process.env, spawn = spawnSync, registry,
|
|
62
62
|
log = () => {}, hostPlatform = process.platform, mode = "build", skipChecks = false, skipDoctorOwned = false, commit,
|
|
63
|
-
fs = { existsSync, readdirSync, readFileSync, statSync }, feed, rows = ADMISSION_ROWS, resolveInputs = resolveBuildInputs,
|
|
63
|
+
fs = { existsSync, readdirSync, readFileSync, statSync }, feed, rows = ADMISSION_ROWS, resolveInputs = resolveBuildInputs, releaseSource = null,
|
|
64
64
|
} = {}) {
|
|
65
65
|
const findings = [];
|
|
66
66
|
const results = [];
|
|
@@ -78,7 +78,7 @@ export async function runAdmission({
|
|
|
78
78
|
const exists = fs.existsSync;
|
|
79
79
|
const projectType = memo(() => resolveProjectType({ appRoot: root, config, target }));
|
|
80
80
|
const ctx = {
|
|
81
|
-
root, repoRoot, configPath, config, platform, target, env, spawn, registry, log, hostPlatform, mode, skipChecks, fs, exists, feed, out,
|
|
81
|
+
root, repoRoot, releaseSource, configPath, config, platform, target, env, spawn, registry, log, hostPlatform, mode, skipChecks, fs, exists, feed, out,
|
|
82
82
|
// The target as the config declares it: the build controller judges a few rules on it, the worker on the expanded one.
|
|
83
83
|
rawTarget: config?.targets?.[platform],
|
|
84
84
|
projectType,
|
package/advisory-exceptions.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// Advisory exception gate.
|
|
1
|
+
// Advisory exception gate. Every crate is its own Cargo workspace (rightkit#1285), and a crate that ignores an advisory keeps its
|
|
2
|
+
// own single source, crates/<crate>/.rightkit/advisory-exceptions.json (CRATE_EXCEPTIONS_FILE): the crate's deny.toml
|
|
2
3
|
// [advisories].ignore and .cargo/audit.toml must match it exactly, every entry needs an owner,
|
|
3
4
|
// evidence and an unexpired absolute reviewBy date, and the entry's versions must match what
|
|
4
|
-
// Cargo.lock actually holds, `dependents` must equal the exact set of direct reverse
|
|
5
|
-
// dependencies of the affected versions in Cargo.lock, and every hop of `path` must be a real
|
|
6
|
-
// dependency edge (scope drift fails in both directions).
|
|
5
|
+
// the crate's Cargo.lock actually holds, `dependents` must equal the exact set of direct reverse
|
|
6
|
+
// dependencies of the affected versions in that Cargo.lock, and every hop of `path` must be a real
|
|
7
|
+
// dependency edge (scope drift fails in both directions). A crate with no ignore needs no ledger.
|
|
8
|
+
// There is no repository-level ledger. Policy: Astra 2026-10-22.
|
|
7
9
|
import fs from 'node:fs';
|
|
8
10
|
import path from 'node:path';
|
|
9
11
|
import { fileURLToPath } from 'node:url';
|
|
@@ -12,6 +14,8 @@ export const EXCEPTIONS_FILE = 'ci/advisory-exceptions.json';
|
|
|
12
14
|
export const DENY_FILE = 'deny.toml';
|
|
13
15
|
export const AUDIT_FILE = '.cargo/audit.toml';
|
|
14
16
|
export const LOCK_FILE = 'Cargo.lock';
|
|
17
|
+
/** The per-workspace ledger, relative to the workspace root (a crate directory). */
|
|
18
|
+
export const CRATE_EXCEPTIONS_FILE = '.rightkit/advisory-exceptions.json';
|
|
15
19
|
|
|
16
20
|
const REQUIRED = ['id', 'crate', 'path', 'owner', 'rationale', 'evidence', 'reviewBy', 'approvedBy'];
|
|
17
21
|
const ID_RE = /^RUSTSEC-\d{4}-\d{4}$/;
|
|
@@ -359,12 +363,44 @@ export function checkAdvisoryExceptions({ root = process.cwd(), now = new Date()
|
|
|
359
363
|
return problems;
|
|
360
364
|
}
|
|
361
365
|
|
|
366
|
+
// ---- per crate ----
|
|
367
|
+
|
|
368
|
+
// A non-empty `ignore = [...]` list (an empty list followed by a later `]` is not one).
|
|
369
|
+
const IGNORE_LIST_RE = /\bignore\s*=\s*\[[^\]]*[^\]\s][^\]]*\]/s;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Every crate workspace of the repository (crates/<x> holding a Cargo.toml) checked against its own ledger. A crate whose deny.toml or
|
|
373
|
+
* .cargo/audit.toml ignores an advisory must have its ledger; a crate with the ledger is checked in full against its own deny.toml,
|
|
374
|
+
* .cargo/audit.toml and Cargo.lock; a crate with neither is fine. Problems are prefixed with the crate directory.
|
|
375
|
+
* `io` = { readFile(absPath), readdir(absDir), exists(absPath) }, for tests.
|
|
376
|
+
*/
|
|
377
|
+
export function checkCrateAdvisoryExceptions({ root = process.cwd(), now = new Date(), allowPending = false, io = {} } = {}) {
|
|
378
|
+
const readFile = io.readFile ?? (p => fs.readFileSync(p, 'utf8'));
|
|
379
|
+
const readdir = io.readdir ?? (d => fs.readdirSync(d));
|
|
380
|
+
const exists = io.exists ?? fs.existsSync;
|
|
381
|
+
const cratesDir = path.join(root, 'crates');
|
|
382
|
+
if (!exists(cratesDir)) return [];
|
|
383
|
+
const problems = [];
|
|
384
|
+
for (const entry of [...readdir(cratesDir)].map(String).sort()) {
|
|
385
|
+
const dir = `crates/${entry}`;
|
|
386
|
+
if (entry.startsWith('.') || !exists(path.join(root, dir, 'Cargo.toml'))) continue;
|
|
387
|
+
const here = path.join(root, dir);
|
|
388
|
+
const ignores = [DENY_FILE, AUDIT_FILE].some(f => { try { return IGNORE_LIST_RE.test(readFile(path.join(here, f))); } catch { return false; } });
|
|
389
|
+
if (!exists(path.join(here, CRATE_EXCEPTIONS_FILE))) {
|
|
390
|
+
if (ignores) problems.push(`${dir}: ${DENY_FILE} or ${AUDIT_FILE} ignores an advisory but ${CRATE_EXCEPTIONS_FILE} does not exist`);
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
for (const p of checkAdvisoryExceptions({ root: here, now, allowPending, exceptionsFile: CRATE_EXCEPTIONS_FILE, readFile: f => readFile(path.join(here, f)) })) problems.push(`${dir}: ${p}`);
|
|
394
|
+
}
|
|
395
|
+
return problems;
|
|
396
|
+
}
|
|
397
|
+
|
|
362
398
|
// ---- CLI ----
|
|
363
399
|
|
|
364
400
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
365
401
|
const args = process.argv.slice(2);
|
|
366
402
|
const root = path.resolve(import.meta.dirname, '..', '..');
|
|
367
|
-
const problems =
|
|
403
|
+
const problems = checkCrateAdvisoryExceptions({ root, allowPending: args.includes('--allow-pending') });
|
|
368
404
|
if (problems.length) {
|
|
369
405
|
for (const p of problems) console.error(`advisory-exceptions: ${p}`);
|
|
370
406
|
console.error(`advisory-exceptions: ${problems.length} problem(s)`);
|
package/android-play-upload.mjs
CHANGED
|
@@ -23,15 +23,14 @@
|
|
|
23
23
|
import { createSign } from "node:crypto";
|
|
24
24
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
25
25
|
import path from "node:path";
|
|
26
|
-
import { spawnSync } from "./background-process.mjs";
|
|
27
26
|
import { defaultReleaseConfigFile, importReleaseConfig } from "./release-config.mjs";
|
|
28
27
|
import { describeAabProblems, scanAabBuffer } from "./android-aab-scan.mjs";
|
|
29
28
|
import { sealedManifestAllowsContentDevtools } from "./devtools-policy.mjs";
|
|
30
29
|
import { targetDistribution } from "./target-distribution.mjs";
|
|
31
30
|
import { AndroidReleaseError, createAndroidCredentialStore, resolveAndroidSecret, validateSecretSource } from "./android-release.mjs";
|
|
32
31
|
import { assertArtifactUnchanged, digestBytes, verifyPublicationRelease } from "./publication-artifacts.mjs";
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
32
|
+
import { assertBindsSource, assertInsideSource, assertReleaseSource, gitRootOf, isSnapshotSource, resolveReleaseSource } from "./release-source-identity.mjs";
|
|
33
|
+
import { verifyOwnershipEvidence } from "./ownership-evidence.mjs";
|
|
35
34
|
|
|
36
35
|
export const PLAY_PROVIDER = "google-play";
|
|
37
36
|
export const PLAY_TRACK = "internal";
|
|
@@ -183,26 +182,28 @@ export async function uploadToPlay({ distribution, aabBytes, accessToken, fetchI
|
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
// ---- authorisation and the command --------------------------------------------------------------------------------
|
|
186
|
-
const git = (cwd, args) => {
|
|
187
|
-
const result = spawnSync("git", args, { cwd, encoding: "utf8", windowsHide: true });
|
|
188
|
-
if (result.status !== 0) throw new Error(`git ${args.join(" ")} failed: ${result.stderr}`);
|
|
189
|
-
return result.stdout.trim();
|
|
190
|
-
};
|
|
191
185
|
|
|
192
186
|
// Local checks only; no network, no secret. The sealed release is the only input: build-free by construction.
|
|
193
|
-
export async function authorizePlayUpload({ releaseId, cwd = process.cwd(), configName = defaultReleaseConfigFile(cwd) }) {
|
|
187
|
+
export async function authorizePlayUpload({ releaseId, cwd = process.cwd(), configName = defaultReleaseConfigFile(cwd), env = process.env }) {
|
|
194
188
|
if (!RELEASE_ID.test(releaseId ?? "")) refuse("PLAY_RELEASE_REQUIRED", "--release <sealed-id> is required");
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
189
|
+
// Source identity (release-source-identity.mjs, item 7 row 7.04): the checkout's clean HEAD outside a door run, the selected commit and
|
|
190
|
+
// its verified snapshot inside one (HEAD, status and branch of the checkout are then never read).
|
|
191
|
+
const release = resolveReleaseSource({ cwd, env });
|
|
192
|
+
assertReleaseSource(release, { commandId: "right-release upload", env });
|
|
193
|
+
const split = isSnapshotSource(release);
|
|
194
|
+
const repoRoot = gitRootOf(release);
|
|
195
|
+
const commit = release.commit;
|
|
199
196
|
const configPath = path.resolve(cwd, configName);
|
|
197
|
+
assertInsideSource(release, configPath, "the release config");
|
|
200
198
|
if (!existsSync(configPath)) refuse("PLAY_CONFIG_INVALID", `release config is required: ${configName}`);
|
|
201
199
|
// A legacy config is a module that runs on import (a JSON config is data): loaded only after the tree is proven clean.
|
|
202
200
|
const config = await importReleaseConfig(configPath, { query: `upload=${Date.now()}` });
|
|
203
201
|
const distribution = validatePlayDistribution(targetDistribution(config, "android"));
|
|
204
202
|
const sealedDir = path.join(repoRoot, ".right-release", "sealed", releaseId, "android");
|
|
205
|
-
|
|
203
|
+
// The sealed release and its signing receipts are where the build wrote them (the checkout's `.right-release`).
|
|
204
|
+
const sealed = verifyPublicationRelease({ sealedDir, repoRoot, appRoot: split ? release.checkoutAppRoot : path.dirname(configPath), commit, app: config.app, version: config.version, platform: "android", configPath, releaseId },
|
|
205
|
+
split ? { verifyOwnership: (expected, root, scanRoot, scope) => verifyOwnershipEvidence(expected, root, scanRoot, scope, release) } : {});
|
|
206
|
+
if (split) assertBindsSource(release, sealed.manifest, "the sealed release");
|
|
206
207
|
const entries = sealed.manifest.files.filter((entry) => entry.role === "aab");
|
|
207
208
|
if (entries.length !== 1) refuse("PLAY_AAB_MISSING", "the sealed release must hold exactly one aab entry");
|
|
208
209
|
const aab = sealed.artifacts.get(entries[0].name);
|
|
@@ -226,7 +227,7 @@ export async function runPlayUpload({
|
|
|
226
227
|
fetchImpl = fetch, credentialStore, sign, nowSeconds, authorize = authorizePlayUpload,
|
|
227
228
|
}) {
|
|
228
229
|
refusePlayEnvSecrets(env);
|
|
229
|
-
const auth = await authorize({ releaseId, configName, cwd });
|
|
230
|
+
const auth = await authorize({ releaseId, configName, cwd, env });
|
|
230
231
|
const aabEvidence = { name: auth.entry.name, sha256: auth.entry.sha256, sizeBytes: auth.entry.sizeBytes };
|
|
231
232
|
const plan = playUploadPlan({ distribution: auth.distribution, aab: aabEvidence });
|
|
232
233
|
const stateRoot = path.join(auth.repoRoot, ".right-release", "state", releaseId, "android");
|
package/app-check.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The app check: step 1 of docs/PIPELINE-ARCHITECTURE.md for any repository that uses RightKit, shipped in @rightkit/release so an
|
|
2
|
-
// app runs it without a RightKit checkout (`rightkit-gate check`, `right-release check`,
|
|
2
|
+
// app runs it without a RightKit checkout (`rightkit-gate check`, `right-release check`, and the start of every
|
|
3
3
|
// right-release command that builds or ships; see CHECKED_COMMANDS). RightKit's own scripts/preflight.mjs imports these rules and
|
|
4
4
|
// adds the ones that police RightKit itself.
|
|
5
5
|
//
|
|
@@ -1131,7 +1131,7 @@ export const RULES = Object.freeze([
|
|
|
1131
1131
|
// asked of them. The compile tier is the rule set every app was green on when it last built through the gate: the rules @rightkit/release
|
|
1132
1132
|
// 0.6.6 published. Every rule added since (consumer, shell, Legion, ownership and source-closure contracts) is release-tier until the
|
|
1133
1133
|
// synthesis waves make it true in every app, and is promoted here, in RightKit, never per app and never through a baseline.
|
|
1134
|
-
// compile gates compile admission: the RustRight broker,
|
|
1134
|
+
// compile gates compile admission: the RustRight broker, rightkit-gate check, --to compile|package, right-release build|dev|test.
|
|
1135
1135
|
// release the whole set: --to qualify|release and every right-release command that qualifies, signs, ships or uploads.
|
|
1136
1136
|
export const TIERS = Object.freeze(["compile", "release"]);
|
|
1137
1137
|
export const COMPILE_TIER_IDS = Object.freeze(new Set([
|
|
@@ -1513,7 +1513,7 @@ export const UNCHECKED_COMMANDS = new Map([
|
|
|
1513
1513
|
["identity-scan", "asserts the signature of a path named on the command line"],
|
|
1514
1514
|
["apple-profile-create", "provisioning-profile credentials; no app source"],
|
|
1515
1515
|
["ios-credentials", "stores or checks App Store Connect credentials; no app source"],
|
|
1516
|
-
["hooks", "
|
|
1516
|
+
["hooks", "retires the git hooks an earlier release wrote (install-hook); writes none"],
|
|
1517
1517
|
["suite-doctor", "verifies the Right Suite contract tests; a report"],
|
|
1518
1518
|
["cache", "host build-cache maintenance"],
|
|
1519
1519
|
["disk", "host disk maintenance"],
|
|
@@ -1578,14 +1578,18 @@ export const appDirectory = (args, cwd) => {
|
|
|
1578
1578
|
* check is green (nothing is printed then), else { exitCode: 1, lines } for the caller to print: the findings and why nothing
|
|
1579
1579
|
* was started. A check that cannot run is red too.
|
|
1580
1580
|
*/
|
|
1581
|
-
export function guardCommand({ args, cwd = process.cwd(), run = runAppCheck } = {}) {
|
|
1582
|
-
const
|
|
1583
|
-
if (!
|
|
1581
|
+
export function guardCommand({ args, cwd = process.cwd(), run = runAppCheck, source = null } = {}) {
|
|
1582
|
+
const checkMode = checkModeFor(args);
|
|
1583
|
+
if (!checkMode) return null;
|
|
1584
1584
|
const name = args[0].startsWith("-") ? "build" : checkedCommandOf(args) ?? args[0];
|
|
1585
|
-
|
|
1586
|
-
|
|
1585
|
+
// A source split (source-split.mjs): the process runs in the door's snapshot of `source.commit`, which has no git directory. The
|
|
1586
|
+
// rules read that commit from the checkout `source.repo`, whatever the command's usual mode (the snapshot IS the commit, so a
|
|
1587
|
+
// "worktree" command checks it as a commit; the checkout's own working tree is never what is built).
|
|
1588
|
+
const mode = source ? "commit" : checkMode;
|
|
1589
|
+
const dir = source ? source.repo : appDirectory(args, cwd);
|
|
1590
|
+
const repo = source ? source.repo : repoRoot(dir);
|
|
1587
1591
|
if (!repo) return { exitCode: 1, lines: [`right-release ${name}: the app check needs a git repository and ${dir} is not inside one; nothing was started`] };
|
|
1588
|
-
const result = run({ repo, mode, tier: tierForCommand(name, args), label: "check", refuse: BUILD_REFUSES });
|
|
1592
|
+
const result = run({ repo, mode, ...(source ? { commit: source.commit } : {}), tier: tierForCommand(name, args), label: "check", refuse: BUILD_REFUSES });
|
|
1589
1593
|
if (result.exitCode === 0) return null;
|
|
1590
1594
|
const why = result.exitCode === 2 ? "could not run" : "found problems";
|
|
1591
1595
|
return { exitCode: 1, lines: [...result.lines, ...result.errLines, `right-release ${name}: the app check ${why} in ${repo}; nothing was started. Fix the above and commit, then run it again.`] };
|