@rightkit/release 0.3.0 → 0.4.1
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 +74 -10
- package/addon-command.mjs +3 -4
- package/adoption-evidence.mjs +650 -0
- package/android-play-upload.mjs +2 -1
- package/apple-profile-create.mjs +113 -10
- package/apple-profile-renew.mjs +11 -3
- package/artifact-storage.mjs +35 -2
- package/bin/darwin/rightkit-swap +0 -0
- package/binary-license-notices.mjs +161 -0
- package/build-release.mjs +138 -47
- package/cargo-license-notices.mjs +111 -0
- package/cli/right-release.mjs +21 -2
- package/codesign-environment.mjs +2 -2
- package/content-digest.mjs +209 -0
- package/development.mjs +17 -6
- package/direct-bootstrap.mjs +5 -6
- package/disk-command.mjs +1 -0
- package/disk-policy.json +6 -2
- package/disk-stable-root.mjs +13 -9
- package/github-updater-release.mjs +70 -7
- package/in-place-install.mjs +17 -14
- package/install-app-windows.mjs +24 -7
- package/install-app.mjs +21 -40
- package/ios-asc-credentials.mjs +163 -0
- package/ios-build-context.mjs +129 -0
- package/ios-build-upload.mjs +282 -0
- package/ios-credentials-command.mjs +70 -0
- package/ios-dev.mjs +248 -0
- package/ios-host.mjs +76 -0
- package/ios-lock.mjs +75 -0
- package/ios-package-recipe.mjs +707 -0
- package/ios-testflight.mjs +266 -0
- package/ios-upload.mjs +313 -0
- package/local-publish.mjs +222 -53
- package/managed-cargo.mjs +8 -0
- package/mirror-root-artifact.mjs +5 -1
- package/native-cargo-layout.mjs +47 -4
- package/ownership-command.mjs +2 -1
- package/ownership-dependency-allowlist.json +3 -1
- package/ownership-evidence.mjs +30 -18
- package/ownership-key-command.mjs +69 -2
- package/ownership-mandatory-set.json +117 -67
- package/ownership-mandatory-set.mjs +141 -21
- package/ownership-repositories.json +1 -1
- package/ownership-scope.mjs +94 -0
- package/ownership.mjs +49 -7
- package/package-recipe-targets.mjs +41 -1
- package/package.json +1 -1
- package/pipeline-admission.mjs +375 -0
- package/pipeline-config.mjs +100 -0
- package/pipeline-generators.json +4 -0
- package/pipeline-generators.mjs +137 -0
- package/pipeline-scan.mjs +285 -0
- package/pipeline-tools.mjs +171 -0
- package/preflight.mjs +14 -7
- package/production-mode.mjs +42 -0
- package/project-type.mjs +3 -1
- package/protected-credentials.mjs +28 -1
- package/publication-artifacts.mjs +30 -5
- package/publication-gate.mjs +94 -32
- package/publish-cargo.mjs +117 -25
- package/publish-swift.mjs +12 -3
- package/publish-update.mjs +15 -2
- package/qa-build-features.mjs +16 -13
- package/qa-contract.mjs +30 -2
- package/qa-control-scan.mjs +8 -0
- package/release-config.mjs +330 -0
- package/release-invocation.mjs +118 -8
- package/release-switch-registry.json +126 -27
- package/release-switch-scan.mjs +88 -21
- package/release.mjs +82 -20
- package/rightkit-ownership.json +1 -1
- package/rightkit-sync.mjs +8 -0
- package/rightkit-versions.json +428 -194
- package/runtime-budget-crates.mjs +14 -0
- package/runtime-budgets.mjs +248 -47
- package/sign-updater.mjs +56 -59
- package/source-gate.mjs +8 -1
- package/stage-sidecar.mjs +207 -40
- package/swap-helper-bundle.mjs +7 -3
- package/target-distribution.mjs +5 -0
- package/upload-release.mjs +7 -2
- package/bin/darwin/rightkit-swap.sign.json +0 -6
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Every Right app has absolutely NO access to DevTools; the single exception is Vi
|
|
|
32
32
|
|
|
33
33
|
`release-switch-scan.mjs` proves that release code cannot use an environment variable, argument, settings key or build flag to select a test mode, data root, executable, library, model or identity. The runtime half is `rightkit_daemon::mode`: a build without the `test-mode` cargo feature reads no override selector at all. Registry: `release-switch-registry.json` (forbidden features, the `RIGHTKIT-TEST-MODE-COMPILED` marker, deny names with their class). Apps add their own deny names under `releaseSwitches.forbid`. Selector classes: S1 test activation, S2 authority or identity override, S3 executable/library/model, S4 internal state or data root (S1-S4: forbidden, no allow-list), S5 user-content location (allowed, user gesture), S6 diagnostics/tuning (allowed, bounded value).
|
|
34
34
|
|
|
35
|
-
1. **Feature closure (authoritative)** - `release.mjs` runs `cargo tree -e features,no-dev --locked --target <triple>` in the app repository (`resolveFeatureClosure`; forwards the build command's `--features`/`--no-default-features`; manifest resolves `targets.<os>.cargoManifest` -> `releaseSwitches.manifest` -> `src-tauri/Cargo.toml` -> `Cargo.toml` and must exist; features are also read from package.json scripts the hooks run (up to three levels) plus `releaseSwitches.features`; no manifest fails with `release-switch-manifest-missing` unless the app sets `releaseSwitches.rust: false` (the only skip); a cargo failure fails a real release, a dry run reports it) and passes it to `featureClosureFindings(...)` and `commandSwitchFindings(argv)` refuse `test-mode`, `qa-native`, `dev-native`, `qa-control`, `dev-inspect` anywhere in the resolved graph (finding id `forbidden-feature <name>`). The binary marker below is secondary evidence only. `qa-build-features.mjs` also lists `test-mode` as a forbidden feature, including on a non-optional `rightkit-daemon` dependency.
|
|
35
|
+
1. **Feature closure (authoritative)** - `release.mjs` runs `cargo tree -e features,no-dev --locked --target <triple>` in the app repository (`resolveFeatureClosure`; forwards the build command's `--features`/`--no-default-features`; manifest resolves `targets.<os>.cargoManifest` -> `releaseSwitches.manifest` -> `src-tauri/Cargo.toml` -> `Cargo.toml` and must exist; features are also read from package.json scripts the hooks run (up to three levels) plus `releaseSwitches.features`; no manifest fails with `release-switch-manifest-missing` unless the app sets `releaseSwitches.rust: false` (the only skip); a cargo failure fails a real release, a dry run reports it) and passes it to `featureClosureFindings(...)` and `commandSwitchFindings(argv)` refuse `test-mode`, `qa-native`, `dev-native`, `qa-control`, `dev-inspect` anywhere in the resolved graph (finding id `forbidden-feature <name>`). The binary marker below is secondary evidence only. `qa-build-features.mjs` also lists `test-mode` as a forbidden feature, including on a non-optional `rightkit-daemon` dependency. Two daemon host features are declarations rather than bans: `rightkit-daemon` `remote` is refused (`remote-feature-undeclared`) unless the app sets `releaseSwitches.remoteHost: true`, and the loopback feature `remote-loopback` (CodeRight's loopback HTTP + bearer host) is refused (`remote-loopback-undeclared`) unless the app sets `releaseSwitches.remoteLoopback: true`. Neither declaration covers the other; a `remote` reached only through `remote-loopback` in the `cargo tree` indentation is judged by the loopback rule, any other path to `remote` (a direct request, `testing`) still needs `remoteHost`, and a tree without indentation counts every `remote` as direct. Both values must be booleans.
|
|
36
36
|
2. **Inventory completeness** - `sourceSwitchFindings` walks app source (Rust, TS/JS, Swift, C; not tests, fixtures, `node_modules`, `target`) and rejects an environment read whose name is not in `switches.json` (`unregistered-read`), a non-literal read in a file not listed under `dynamicReads` (`dynamic-read`), an S1-S4 entry without `compiledOut` (`not-compiled-out`), and a malformed inventory (`inventory-invalid`). Argument flags are enforced for deny-listed flags only.
|
|
37
37
|
3. **Binary** - `scanSwitchPaths` / `bufferSwitchFindings` over Mach-O, PE and ELF files: the marker (`test-mode-capability`) and any deny name from S1-S4 (`release-switch-names`), each as Latin-1 and UTF-16LE. The receipt holds the sha256 and size of every scanned file; the caller adds source commit, target triple and resolved features.
|
|
38
38
|
|
|
@@ -56,7 +56,7 @@ Every Right app has absolutely NO access to DevTools; the single exception is Vi
|
|
|
56
56
|
|
|
57
57
|
- `name` (required, unique): environment variable, argument flag, settings key or `VITE_*` name.
|
|
58
58
|
- `class` (required): `S1`..`S6`. `channel` (required): `env`, `argv`, `settings`, `preference` or `frontend`.
|
|
59
|
-
- S1-S4 need `compiledOut`: `feature:<name>`, `cfg:test` or `
|
|
59
|
+
- S1-S4 need `compiledOut`: `feature:<name>`, `cfg:test`, `cfg:debug_assertions` or, for Swift, `swift:DEBUG` (the read sits inside `#if DEBUG`; the read exists only there). The source walk skips test directories, including Swift bundles named `Tests`, `UITests`, `<App>Tests` and `<App>UITests`. S5 needs `userGesture: true`. S6 needs `valueType` of `bool`, `int` or `enum`.
|
|
60
60
|
- `anchor` and `note` are informational. Fixtures: `fixtures/release-switches/` (`app/` is a clean example).
|
|
61
61
|
|
|
62
62
|
## Commands
|
|
@@ -74,9 +74,9 @@ Every Right app has absolutely NO access to DevTools; the single exception is Vi
|
|
|
74
74
|
| Candidate signing | `sign-candidate --platform win\|mac --artifact <dir> --candidate-receipt <receipt.json> --out <binding.json> [--manifest <sign.json>] [--publisher <subject>] [--dry-run]`: signs the exact unsigned candidate bytes in place instead of rebuilding from source. The candidate receipt (`{ schema: 1, files: [{ path, sha256 }] }`) is checked first and any mismatch refuses; Windows signs through Azure Artifact Signing (`--publisher` pins the MSIX signer subject), macOS through Developer ID with fixed identifiers from `--manifest`. Writes a binding receipt (`rebuilt: false`, candidate sha -> signed sha per file). Never builds, notarizes or publishes |
|
|
75
75
|
| Dev artifact signing | `sign-dev-artifact --artifact <dir> --manifest <sign.json> [--metadata <payload.json>] [--evidence <path>] [--dry-run]`: Developer ID signs an existing CI dev artifact per file with fixed identifiers (macOS). It verifies the recorded hashes in the payload metadata (default `payload.json`, `{ schema: 1, files: [{ path, sha256 }] }`) first, signs each sign-manifest entry (`file` relative to the artifact, `identifier` required, nested code before its bundle) through the dev-signing identity (missing Developer ID: `DEV_MAC_SIGNING_DEVELOPER_ID_REQUIRED`, no ad hoc fallback), verifies with `codesign --verify --strict`, reads back Identifier and TeamIdentifier, refreshes the metadata hashes, and writes evidence JSON. Never builds, notarizes or publishes. The payload metadata must sit outside every signed entry (`SIGN_DEV_ARTIFACT_METADATA_IN_SIGNED_ENTRY`). An optional declarative `refresh` in the sign manifest recomputes digests that other JSON files record about signed bytes: `{ "refreshRoot": "<dir inside the artifact>", "refresh": [{ "file", "pointer" (JSON pointer to an existing string), "compute": "sha256-file" \| "sha256-tree-v1" \| "verify-sha256-file", "of", "format"?, "currentMatches"? }] }` (`file` and `of` relative to `refreshRoot`; `sha256-tree-v1` = sha256 over the sorted relative paths, each as path, NUL, 8-byte big-endian length, bytes; `verify-sha256-file` writes nothing and refuses a digest that changed; `format` embeds the digest in a locator string through one `{value}`; `currentMatches` refuses to overwrite a value of another kind, such as a signed attestation locator). Order is enforced: operations run after the entries they read are signed and before the first entry that contains a refreshed file, so that bundle (the wrapper) signs last over the final bytes; an operation may not read what a later one writes. JSON is rewritten in place with key order kept, and a file the writer would have to reformat is refused. Evidence gains a `refresh` list |
|
|
76
76
|
| Checks | `deps`, `hardening`, `harden`, `hardeningscan`, `cargo-target` |
|
|
77
|
-
| Ownership | `ownership`, `ownership-approval`, `ownership-report` |
|
|
77
|
+
| Ownership | `ownership`, `ownership-approval`, `ownership-report`, `ownership-mandatory`, `adoption-evidence` |
|
|
78
78
|
| Test | `test` (the app's declared suites) |
|
|
79
|
-
| Legal | `license-notices npm` |
|
|
79
|
+
| Legal | `license-notices npm`, `license-notices cargo`, `license-notices binaries` |
|
|
80
80
|
| Other | `dev`, `doctor`, `suite-doctor`, `qa`, `dmg`, `cache`, `apple`, `inventory`, `model`, `addon`, `hooks`, `lsclean`, `generate-dmg-background`, `publish cargo`, `publish npm`, `publish swift` |
|
|
81
81
|
|
|
82
82
|
## Declared test suites, web build, npm notices and artifact mirroring
|
|
@@ -135,6 +135,26 @@ its declared MIT or ISC license, labelled as a fallback; any other license witho
|
|
|
135
135
|
app version, the `pnpm-lock.yaml` sha256 and `generated-by=right-release license-notices npm`, so equal inputs give equal bytes.
|
|
136
136
|
`--check` writes nothing and exits 1 when the file differs. `--output` and `--product-name` override the config.
|
|
137
137
|
|
|
138
|
+
### `right-release license-notices cargo`
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
legal: { cargoNotices: {
|
|
142
|
+
output: "legal/THIRD_PARTY_CARGO_LICENSES.html",
|
|
143
|
+
template: "legal/about.hbs",
|
|
144
|
+
config: "legal/about.toml", // optional cargo-about config
|
|
145
|
+
manifestPath: "src-tauri/Cargo.toml", // optional Cargo manifest
|
|
146
|
+
features: ["desktop"], // or allFeatures: true
|
|
147
|
+
} }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Runs `cargo about generate --locked --fail` through managed Cargo, with declared config, manifest, features & template.
|
|
151
|
+
Inputs & output stay inside app root. Missing inputs, unresolved licenses, command failure or empty output fail generation.
|
|
152
|
+
`--output` overrides config; `--check` writes nothing & exits 1 when generated bytes differ.
|
|
153
|
+
`rightkit-legal rust-licenses` delegates to this route.
|
|
154
|
+
|
|
155
|
+
`license-notices binaries` generates notices for declared native binaries; `--receipt` checks staged non-catalog programs
|
|
156
|
+
against declared coverage. Both routes accept `--config`, `--output` & `--check`.
|
|
157
|
+
|
|
138
158
|
### Mirroring an artifact (`mirror-root-artifact`)
|
|
139
159
|
|
|
140
160
|
```js
|
|
@@ -164,6 +184,12 @@ app in `notarize.app`. Declare the file in `updater.artifacts` as before. See `d
|
|
|
164
184
|
|
|
165
185
|
Audit ad hoc or on a weekly schedule with `right-release qa audit [--app <dir>] [--json] [--since <YYYY-MM-DD>]`. It reads the app repo's sealed manifests and state and lists source and installed QA status per release and platform (passed, missing, rejected, not-uploaded). It exits 0 when every uploaded build has passing QA and 1 when any uploaded build lacks it. An installed receipt can only chain to a build sealed with a passing source receipt, so a build sealed with `qa.status: "missing"` needs source QA and a rebuild before installed QA can pass.
|
|
166
186
|
|
|
187
|
+
## Local publish (`publish npm|cargo ... --local`, Mac or Windows)
|
|
188
|
+
|
|
189
|
+
`right-release publish cargo --crate <name> --local` (and the batch form `--crates a,b,c | --crates-file <file>`) publishes from the Mac **or from Windows** under the committed local-build grant (`.rightkit-local-build.json`, `allow: "build-test-publish"`, naming this repository); `publish npm --package @rightkit/<name> --local` stays Mac only (the npm token comes from the macOS Keychain and `@rightkit/release` ships a signed helper), and `--local` refuses any other host (Linux) and any host without the grant. The gate is the same on both hosts: the unit's own `ci/gate.config.json` commands run with zero failures and no skipped evidence, the artifact sha256 is recorded, and the existing `enforcePublicationGate` and publisher run, including the registry readback. The evidence carries one receipt for the publishing host: `hostPolicy: "darwin-only"` with a `darwin` run on the Mac, `hostPolicy: "win32-only"` (plus a `hostPolicyReason`) with a `win32` run on Windows; the gate requires that host's receipt and `isDarwinOnlyPolicy`, `isWin32OnlyPolicy` and `singleHostOfPolicy` in `publication-gate.mjs` are the one definition. A crate whose `ci/gate.config.json` `os` list lacks the publishing host (`macos` or `windows`) is refused. Windows publishes with the credential Cargo stores itself (`~/.cargo/credentials.toml`): no `--token` is passed, read or logged. A runtime-budget crate needs the measurement of the publishing host only, under the same evidence policy that the budget file may not contradict.
|
|
190
|
+
|
|
191
|
+
`ci/gate.config.json` `rust.<crate>.consumerRows` adds gate rows that check the crate through a separate Cargo workspace: `{ "manifestPath": "ci/consumers/<name>/Cargo.toml", "args": [...], "kind": "check", "targets": ["<triple>"], "locked": true }` runs `cargo check --manifest-path <manifestPath> --locked --target <triple> <args>` once per target, after the crate's own checks, and records each as an evidence step (so `--from-evidence` requires it). Only `manifestPath` is required; unknown fields are refused. Clippy with `-D warnings` runs on every `featureSets` entry, not only the first.
|
|
192
|
+
|
|
167
193
|
## Local app install (macOS)
|
|
168
194
|
|
|
169
195
|
Agents must not run `osascript`, `open`, `screencapture`, or `notifyutil -p` directly. Install a signed build through:
|
|
@@ -174,7 +200,7 @@ right-release install-app --dmg <path.dmg> [--app-name Pulse.app] [--bundle-id d
|
|
|
174
200
|
|
|
175
201
|
It mounts the image read-only and hidden (`-nobrowse`), verifies the app (`codesign --verify --deep --strict`, `spctl --assess`, `stapler validate`, bundle id, and Team ID against the installed copy; a Team ID change is refused) before touching the installed copy, stages a `ditto` copy beside it in the destination and verifies that copy, quits the running instance with SIGTERM (15 s, then SIGKILL), swaps the staged copy in (one atomic exchange when the `rightkit-swap` helper is found, otherwise two journaled renames; the old bundle is restored on failure), re-verifies the installed copy, only then moves the old bundle to `~/.Trash/<App> <timestamp>.app`, and relaunches only if it was running or `--relaunch` is given, using `open -g -j -b` (background, hidden, never activated). `--json` emits one lifecycle event per line (`start`, `verified`, `quit`, `replaced`, `relaunched`, `done`/`failed`, among others). `right-release install-app --recover [--dest <dir>] [--app-name <App.app>] [--json]` rolls an interrupted install forward or back from its journal; every install runs that recovery first. Win32 exits with "not implemented on win32". Exit codes: 0 ok, 1 failed, 2 usage, 3 unsupported platform.
|
|
176
202
|
|
|
177
|
-
The shipped `bin/darwin/rightkit-swap` is Developer ID signed.
|
|
203
|
+
The shipped `bin/darwin/rightkit-swap` is Developer ID signed. Local publish adds it via `right-release publish npm --package @rightkit/release --local` on the Mac (managed `rightkit cargo build --release -p rightkit-fs --bin rightkit-swap`, `codesign --options runtime --timestamp`, strict verify). CI qualification adds it via `publish.yml` build-swap → sign-swap → pack (`docs/CI.md`); sign-swap needs `APPLE_CERTIFICATE_BASE64`, `APPLE_CERTIFICATE_PASSWORD`, `APPLE_KEYCHAIN_PASSWORD` & `APPLE_DEVELOPER_ID` in the `release` environment, & stays blocked until root provisions the certificate pair. Exporting that identity requires an Aqua login session: SSH export fails `errSecInteractionNotAllowed` (-25308) & the Aqua signing agent exposes only codesign/updater/notarize/staple, no key export — do not retry either route. `install-app` refuses a helper that exists but is not validly signed (`SWAP_HELPER_UNSIGNED`, naming the fix); with no helper at all it uses the journaled two-rename fallback and logs it (`swap-fallback`, `replaced {swap}`).
|
|
178
204
|
|
|
179
205
|
Development builds (from `@rightkit/ci` `devArtifact`, Developer ID-signed but not notarized) install with `--dev` and exactly one of `--dmg`, `--zip <path.zip>` (extracted with `ditto -x -k`), or `--app <path.app>`: `right-release install-app --dev --zip dev-mac.zip`. `--dev` still requires `codesign --verify --deep --strict`, a Team ID, no Team ID change, and a bundle-id match; it skips only `spctl` and `stapler`, removes `com.apple.quarantine` from the new bundle only, prints `DEV BUILD (not notarized)`, and sets `dev: true` on every JSON event.
|
|
180
206
|
|
|
@@ -208,6 +234,27 @@ RightKit QA is a mandatory component: every Orthic Labs app adopts it. `assertQa
|
|
|
208
234
|
- **Warned:** a `@rightkit/qa`, `@rightkit/qa-chrome` or `rightkit-qa` pin behind the latest published version in `rightkit-versions.json` (`[right-release] QA warning: <app> runs RightKit QA behind the latest published version: <pin> -> <latest> (<file>)`). **Refused** when the lag is a breaking bump (`breakingBump` of `rightkit-drift`: a minor bump below 1.0, a major from 1.0). `@rightkit/qa-chrome` is lag-checked but its presence is not enforced.
|
|
209
235
|
- **Exempt:** only a repository that is not an app. `qa-adoption-exemptions.json` (`nonAppRepositories`: `{ repositoryIdentity, kind: "library" | "tooling", reason }`, matched against the git `origin`) is RightKit-owned, so a change needs a RightKit release; the RightKit source tree is recognised by `packages/release/package.json` named `@rightkit/release`. There is no per-app opt-out, config key or environment switch.
|
|
210
236
|
|
|
237
|
+
## Ownership commands
|
|
238
|
+
|
|
239
|
+
`right-release ownership [--app <dir>] [--json]` scans app copies of shared capabilities; `--staged` scans staged index.
|
|
240
|
+
`ownership enroll`, `approve-key` & `revoke-key` manage native signing keys. `ownership trust-sync` advances protected trust
|
|
241
|
+
to committed key log through authenticated approval; `trust-sync --check` previews required approval & `trust-compare --json`
|
|
242
|
+
reads current digest.
|
|
243
|
+
|
|
244
|
+
`right-release ownership-mandatory [--workspace <dir>] [--app <name>]... [--amendment v2.1] [--enforce] [--json]`
|
|
245
|
+
checks Table 1 owner declarations & minimum versions. Default mode reports findings; `--enforce` applies current mandatory
|
|
246
|
+
policy. M21/M23 remain pending until qualified owner receipts authorize signed policy activation through authenticated
|
|
247
|
+
trust-sync. Their `pendingOwners` & M22 `{ role, entrypoints }` applicability records are report metadata; neither changes
|
|
248
|
+
enforcement. D4 is ratified; pending activation does not mean pending ratification.
|
|
249
|
+
|
|
250
|
+
## Adoption evidence (`adoption-evidence`, PK17-6)
|
|
251
|
+
|
|
252
|
+
`right-release adoption-evidence --manifest <published-release-manifest.json> [--packets <dir>] [--app <dir> [--adoption <file>] [--target <target>]] [--json]` validates the handoff manifest (packet 01 section 6) and, with `--app`, an app's adoption record. Exit 0 no findings, 1 findings, 2 usage error. CHANGELOG and deletion-list prose never satisfy a check; every check reads a hash, a lockfile, a saved `cargo tree` or a JSON receipt.
|
|
253
|
+
|
|
254
|
+
- **Manifest:** schema; package field rules per event (`publish`, `qualify-existing`); capability `satisfied` recomputed from its packages (`fetchedConsumer.ok`, passing qualification rows, evidence paths); contract rollups; every task `ready` and `readyForAdoption` re-derived (an N/A or unresolved task is never ready, a deferred slice is never satisfied); the selected snapshot hash and the evidence snapshots when the manifest's directory is present; duplicate JSON keys are refused; and the AP id set must equal the ids extracted from the frozen packets `02-*.md`..`07-*.md` (`- [ ] AP-XX-nn` lines) exactly (`ap-missing`, `ap-extra`, `packet-duplicate-task`; no packets is `packets-missing`). `--packets` defaults to the directory above the manifest's directory.
|
|
255
|
+
- **Adoption record** (`<app>/rightkit-adoption.json`, schema 1): `app`, `manifestSha256` (the manifest file's hash; a stale record is `adoption-stale`), `target`, optional `locks`, `cargoTree { file, sha256 }` and `tasks.<AP-id>.adopted[] { capability, packages, entrypoints, receipt { file, sha256 } }`. A task must be `ready` in the manifest and every capability it requires must be adopted. Each adopted package must be pinned by the lockfile at exactly the manifest version and integrity from the crates.io registry (`Cargo.lock`), the npm registry (`pnpm-lock.yaml` v9) or the tag commit (`Package.resolved`), with no other version of the name in the lock. For cargo, the saved `cargo tree -e features` must show that exact version, every declared feature active and published, and the declared feature set equal to a passing qualification row for the target's host. The active target must be published and qualified.
|
|
256
|
+
- **Receipt** (JSON only, schema 1): `kind` `test` or `run`, `app`, `capability`, one of the adopted `entrypoint`s (a real file inside the app, optional `#symbol` that must appear in it), `packages` naming the pinned `<id>@<version>`, `target`, a `command`, `exitCode: 0`, `finishedAt`, and `lockfileSha256.<cargo|pnpm|swift>` equal to the current lockfile (a receipt from before the last lock change is refused).
|
|
257
|
+
|
|
211
258
|
## Entry points
|
|
212
259
|
|
|
213
260
|
Bin: `right-release` (`cli/right-release.mjs`). The package has no `exports` map. The CLI is the supported surface.
|
|
@@ -216,7 +263,7 @@ Bin: `right-release` (`cli/right-release.mjs`). The package has no `exports` map
|
|
|
216
263
|
|
|
217
264
|
| Dependency | Range | Note |
|
|
218
265
|
|---|---|---|
|
|
219
|
-
| `smol-toml` | `1.9.
|
|
266
|
+
| `smol-toml` | `1.9.1` (exact) | Used to parse lock and manifest files |
|
|
220
267
|
|
|
221
268
|
Peer dependencies: none.
|
|
222
269
|
|
|
@@ -344,6 +391,23 @@ The Android lane is native Kotlin with Gradle plus Rust `.so` libraries per ABI
|
|
|
344
391
|
- Secrets: the keystore passwords and the Play service-account JSON live in the OS credential store (macOS Keychain `rightkit-android-<name>`; a protected token file on Windows and Linux, so store the JSON as one line) or in a 0600 file outside the repository, selected by `{ credential: "<name>" }` or `{ file: "<path>" }`. They never appear in the environment, on argv, in config, or in a log; key material found in the environment refuses the command.
|
|
345
392
|
- Dev: a debug build installed by serial (never "the only device") with a package ending in `.dev`, `.qa`, `.test` or `.debug`; release tasks and signing properties are refused.
|
|
346
393
|
|
|
394
|
+
## iOS (archive, export, TestFlight, simulator and device install)
|
|
395
|
+
|
|
396
|
+
One declarative recipe for every iOS app (bogusyogi/rightkit#349); full reference and example config in `docs/IOS-PACKAGE-RECIPE.md`. Same split as Android: `build` never uploads, `upload` never builds, and the upload is a dry run unless `--apply`.
|
|
397
|
+
|
|
398
|
+
| Step | Command | Config |
|
|
399
|
+
|---|---|---|
|
|
400
|
+
| Build | `right-release build --platform ios` | `targets.ios`: `iosPackage` (`project`, `projectSpec?`, `scheme`, `teamId`, `bundleId`, `archive`, `ipa`, `extraBuildSettings?`, `prepare?`, `cleanup?`, `checks?`) and `artifacts` listing the IPA |
|
|
401
|
+
| Upload | `right-release upload --platform ios --release <sealed-id> [--apply]` | top-level `distribution: { provider: "app-store-connect", appId, bundleId, track: "testflight", groups (required), submitExternalBetaReview? (default false), credentials? }` |
|
|
402
|
+
| Credentials, once | `right-release ios-credentials store --key-id <KEYID> --issuer <uuid>` (`.p8` on stdin), `ios-credentials check` | Keychain service names, optional |
|
|
403
|
+
| Dev | `right-release dev --platform ios [--serial <device id>]` | `development.targets.ios`: `project`, `projectSpec?`, `scheme`, `teamId?`, `install: { kind: "simulator" \| "device", device, bundleId, ... }` |
|
|
404
|
+
|
|
405
|
+
- Build: `xcodegen generate` (required when `projectSpec` is named; a committed xcodeproj is never a fallback), `xcodebuild archive` with automatic signing, `-allowProvisioningUpdates`, the configured team and a date build number `YYYYMMDDNN` (the marketing version stays as the app sets it), then the archive checks (one version across the app, appex and Watch plists, QA/release-switch scan, forbidden strings), `xcodebuild -exportArchive`, the IPA checks (entitlements, distribution profile, code signature) and a receipt over the final IPA bytes. A failure deletes the archive, the IPA and the receipt. macOS only, in the GUI signing session; on Windows and Linux only config validation and `--dry-run` run.
|
|
406
|
+
- Credentials (D4): the App Store Connect `.p8`, key id and issuer id are read from the macOS login Keychain (`security find-generic-password -s <service> -a <account> -w`; defaults `rightkit-asc-api-key`, `rightkit-asc-key-id`, `rightkit-asc-issuer-id`). A key path, key id, issuer or key in config, and key material in the environment, are refused; the ASC environment variables are refused on managed hosts. Key material never reaches a log.
|
|
407
|
+
- Upload: the sealed IPA is uploaded in process through the App Store Connect build-upload resources (`buildUploads`, `buildUploadFiles`, pre-signed part uploads, commit, poll to `COMPLETE`), then the exact build is assigned to the TestFlight beta groups (in-process ES256 JWT). Idempotent calls are retried with bounded backoff (the create POSTs never), an interrupted upload for the same version and build is resumed or finished, pre-signed URLs are limited to `*.blobstore.apple.com` and may not carry credential headers, and the IPA is streamed in ranges. A per-release lock, a GUI-session check before the Keychain read and marker identity checks guard a rerun. No altool and no file ever holds the key. No App Store submission. Apps must git-ignore `.right-release/` (see `docs/IOS-PACKAGE-RECIPE.md`).
|
|
408
|
+
- Dev: simulator (`simctl boot/install/launch`) or physical device (`devicectl install` and `process launch`), chosen by id, built Debug with generated commands.
|
|
409
|
+
|
|
410
|
+
|
|
347
411
|
## Bundled FFmpeg decode sidecar (Ember and other media apps)
|
|
348
412
|
|
|
349
413
|
An app that decodes video through `rightkit-ffmpeg` ships the verified LGPL `ffmpeg` and `ffprobe` programs as a Tauri `externalBin` sidecar. right-release adds no new bundling mechanism: Tauri bundles the files (mac `Contents/MacOS`, Windows next to the app EXE), and right-release plans them from the catalog the app compiled against, ships the LGPL text and source offer, signs the binaries with the app, and seals their hashes. Set `targets.mac.mediaSidecar` and `targets.win.mediaSidecar`:
|
|
@@ -378,10 +442,10 @@ Any app that ships more than the ffmpeg sidecar (MailRight: `mailright-engine`,
|
|
|
378
442
|
|
|
379
443
|
`stage-sidecar` (the `sidecars` list in `right-release.config.mjs`, receipt `stage-sidecar.json`) covers these cases without an app script. Every download is https and digest-pinned, nothing runs through a shell, and a failed check stages nothing.
|
|
380
444
|
|
|
381
|
-
- **`cmake` (Orthic-Labs/rightkit#489):** `{ kind: "cmake", name: "whisper-cli", repo: "https://github.com/ggml-org/whisper.cpp", commit: "<40 hex>", target?: "whisper-cli", output?: "bin/whisper-cli", cmakeArgs?: ["-DBUILD_SHARED_LIBS=OFF"], platformArgs?: { mac: ["-DGGML_METAL=ON"], win: [...] }, generator?, smoke?: ["--help"], sha256?, dest?, clean?, platforms? }`. It fetches exactly that commit into the fixed `.right-release/cmake/<name>/src` (`git init`, `fetch --depth 1 origin <commit>`, detach; a checkout already at the commit is reused, one with local edits or at another commit is refused with `STAGE_SIDECAR_SOURCE_MISMATCH`), configures and builds `target` in `.right-release/cmake/<name>/build` (`CMAKE_OSX_ARCHITECTURES` is set from the target; `universal-apple-darwin` builds `arm64;x86_64` and checks `lipo -archs`), finds the executable (`output`, then `Release/` layouts, `.exe` on Windows), optionally pins its sha256, stages it as `<name>-<triple>[.exe]` and smoke-runs it when the target can run on this host. `cmakeArgs` and `platformArgs` accept only `-DNAME=value` definitions.
|
|
382
|
-
- **`prebuilt` (#490):** verifies a host-built binary already staged at `<stageDir>/<name>-<triple>` (or copied from `from`) against an app manifest, never building or downloading: `manifest: { file, entry: "binaries.{platform}", sha256?: "sha256", approvalSha256?: "approval_sha256", approvalAlgorithm?: "approval_hash_algorithm" }` (`{platform}` is `macos-aarch64`, `windows-x86_64`, ...). The raw sha256 must match; when an approval hash is named it must match too, with `raw-sha256` or `macho-codesign-strip-linkedit-v1` (`macho-approval-hash.mjs`: the sha256 of a thin Mach-O with its code signature removed and `__LINKEDIT` normalised, so a signed and an unsigned copy of one build agree; macOS only, `codesign --remove-signature` on a private copy). A missing file, entry or digest fails closed.
|
|
383
|
-
- **`remote` archives (#491):** `extract: "<member file name>"` (with `memberSha256?`, `executable?`) unpacks one member of a zip or tgz after the archive digest passes, so a Windows zip needs no `command` entry.
|
|
384
|
-
- **`ffmpeg` mixed variants, fetch and probes (#491):** `variants: { ffmpeg: "export-ass" }` lets one entry take the export-ass ffmpeg with the decode ffprobe (a tool not named uses `variant`). `sources: { ffmpeg: { url, sha256, member }, ffprobe: {...} }` downloads the bytes the catalog only pins: the archive is digest-checked, the member is unpacked and its sha256 must equal the APPROVED catalog row's content digest, so a swapped archive can never stage. `probe: true` runs `-version` and `-encoders` on a staged binary this host can run: the configuration must say `--disable-gpl` and `--disable-nonfree` and must not enable GPL, nonfree, libx264, libx265, libxvid or libfdk-aac, and ffmpeg may not list those encoders (`STAGE_SIDECAR_FFMPEG_PROBE_FAILED`; another architecture or OS is recorded as `skipped-cross-target`). The catalog has no Windows `export-ass` row yet (only `export-ass-zimg`); that row belongs to the rightkit-ffmpeg crate.
|
|
445
|
+
- **`cmake` (Orthic-Labs/rightkit#489):** `{ kind: "cmake", name: "whisper-cli", repo: "https://github.com/ggml-org/whisper.cpp", commit: "<40 hex>", target?: "whisper-cli", output?: "bin/whisper-cli", cmakeArgs?: ["-DBUILD_SHARED_LIBS=OFF"], platformArgs?: { mac: ["-DGGML_METAL=ON"], win: [...] }, generator?, smoke?: ["--help"], sha256?, dest?, clean?, platforms? }`. It fetches exactly that commit into the fixed `.right-release/cmake/<name>/src` (`git init`, `fetch --depth 1 origin <commit>`, detach; a checkout already at the commit is reused, one with local edits or at another commit is refused with `STAGE_SIDECAR_SOURCE_MISMATCH`), configures and builds `target` in `.right-release/cmake/<name>/build` (`CMAKE_OSX_ARCHITECTURES` is set from the target; `universal-apple-darwin` builds `arm64;x86_64` and checks `lipo -archs`), finds the executable (`output`, then `Release/` layouts, `.exe` on Windows), optionally pins its sha256, stages it as `<name>-<triple>[.exe]` and smoke-runs it when the target can run on this host. `cmakeArgs` and `platformArgs` accept only `-DNAME=value` definitions. Toolchain controls (#691, all optional, defaults unchanged): `cmakeBin: "<name or path>"` runs that cmake (a bare name is looked up on PATH, a path with separators is resolved against the app root); `generator` is per entry and `platformGenerators: { win: "Visual Studio 17 2022" }` overrides it for one platform (`-G`); `architecture: "x64"` (any `-A` platform: `x64`, `Win32`, `ARM64`) adds `-A <arch>` after `-G`. With no `cmakeBin`, a Windows host uses `cmake` from PATH (`where cmake`) when there is one, else the Visual Studio-bundled cmake (`vswhere.exe` under `%ProgramFiles(x86)%\Microsoft Visual Studio\Installer`, `-latest -products * -requires Microsoft.VisualStudio.Component.VC.CMake.Project -property installationPath`, then `Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe`), else plain `cmake`. The receipt row carries `cmakeBin` (when not plain `cmake`) and `generator`. There is no environment-variable override: the staging recipe has none for any other tool.
|
|
446
|
+
- **`prebuilt` (#490):** verifies a host-built binary already staged at `<stageDir>/<name>-<triple>` (or copied from `from`) against an app manifest, never building or downloading: `manifest: { file, entry: "binaries.{platform}", sha256?: "sha256", approvalSha256?: "approval_sha256", approvalAlgorithm?: "approval_hash_algorithm" }` (`{platform}` is `macos-aarch64`, `windows-x86_64`, ...). The raw sha256 must match; when an approval hash is named it must match too, with `raw-sha256` or `macho-codesign-strip-linkedit-v1` (`macho-approval-hash.mjs`: the sha256 of a thin Mach-O with its code signature removed and `__LINKEDIT` normalised, so a signed and an unsigned copy of one build agree; macOS only, `codesign --remove-signature` on a private copy). A missing file, entry or digest fails closed. With `from: { aarch64: "<path>", x86_64: "<path>" }` the entry also works on `universal-apple-darwin` (#692): each arch is verified against its own manifest entry (`macos-aarch64`, `macos-x86_64`; the approval algorithm default is the macOS one), then both are lipo-merged by the same helper `ffmpeg`, `onnxruntime` and `cmake` use into `<name>-universal-apple-darwin` (both archs required, `STAGE_SIDECAR_NOT_UNIVERSAL` otherwise; nothing is merged after a failed slice). With `from` omitted the inputs are the staged `<name>-aarch64-apple-darwin` and `<name>-x86_64-apple-darwin`; a plain string `from` is refused for universal. On a thin target an object `from` picks the entry for that arch. The row carries `platform: "macos-universal"` and per-arch `slices`.
|
|
447
|
+
- **`remote` archives (#491):** `extract: "<member file name>"` (with `memberSha256?`, `executable?`) unpacks one member of a zip or tgz after the archive digest passes, so a Windows zip needs no `command` entry. A bare name must be the base name of exactly one member; a name with `/` (`bin/x64-win/DirectML.dll`) must equal exactly one member path (case-sensitive), for archives that repeat a base name in several directories. Absolute, drive-prefixed, backslash, empty, `.`/`..` segment and glob-character paths are refused, and the output stays at `dest`.
|
|
448
|
+
- **`ffmpeg` mixed variants, fetch and probes (#491):** `variants: { ffmpeg: "export-ass" }` lets one entry take the export-ass ffmpeg with the decode ffprobe (a tool not named uses `variant`). `sources: { ffmpeg: { url, sha256, member }, ffprobe: {...} }` downloads the bytes the catalog only pins: the archive is digest-checked, the member is unpacked and its sha256 must equal the APPROVED catalog row's content digest, so a swapped archive can never stage. A source may instead be a local path (#692): `sources: { ffmpeg: { path: "../vendor/ffmpeg-lgpl", member?: "ffmpeg" } }` (also per arch: `{ aarch64: { path }, x86_64: { path } }`). `path` is relative to the app root and may step out of it; it names the binary itself or a directory holding `<tool>[.exe]` or `bin/<tool>[.exe]` (`member` renames the file). Nothing is downloaded and there is no archive digest, but the file must still equal the APPROVED catalog row content digest (`STAGE_SIDECAR_HASH_MISMATCH` otherwise); notices, probe and universal lipo are unchanged; a missing file is `STAGE_SIDECAR_BINARY_NOT_FOUND`. `probe: true` runs `-version` and `-encoders` on a staged binary this host can run: the configuration must say `--disable-gpl` and `--disable-nonfree` and must not enable GPL, nonfree, libx264, libx265, libxvid or libfdk-aac, and ffmpeg may not list those encoders (`STAGE_SIDECAR_FFMPEG_PROBE_FAILED`; another architecture or OS is recorded as `skipped-cross-target`). The catalog has no Windows `export-ass` row yet (only `export-ass-zimg`); that row belongs to the rightkit-ffmpeg crate.
|
|
385
449
|
- **`universal-apple-darwin` for `ffmpeg` and `onnxruntime` (#492):** each arch row is staged thin under `.right-release/slices/<triple>/` and the files are lipo-merged into `<stageDir>/<tool>-universal-apple-darwin` (onnxruntime: into `resourceDir`), requiring both archs and every row approved (onnxruntime also accepts `pinned`). For `ffmpeg`, `{arch}`, `{triple}` and `{stageDir}` in the `stage` command's arguments name the slice (it runs once per slice), `sources` need the per-arch form `{ ffmpeg: { aarch64: {...}, x86_64: {...} } }`, one source offer lists both slices, and the receipt rows carry `slices`. A merged file identical to the staged one is not rewritten.
|
|
386
450
|
|
|
387
451
|
## Declared Rust audit, dist-scan and the Windows Swift toolchain
|
package/addon-command.mjs
CHANGED
|
@@ -172,11 +172,10 @@ async function adopt(options) {
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
copyFileSync(path.join(source, "addon-manifest.json"), path.join(stage, "addon-manifest.json"));
|
|
175
|
-
// Directory rename is atomic on one filesystem.
|
|
176
|
-
//
|
|
177
|
-
// replacement fails.
|
|
175
|
+
// Directory rename is atomic on one filesystem. No `<destination>.previous` is retained (D01): the
|
|
176
|
+
// verified staged copy replaces the fixed path and any stale `.previous` is removed.
|
|
178
177
|
mkdirSync(output, { recursive: true });
|
|
179
|
-
swapInPlace({ staging: stage, destination });
|
|
178
|
+
swapInPlace({ staging: stage, destination, previous: null });
|
|
180
179
|
if (temporary) rmSync(temporary, { recursive: true, force: true });
|
|
181
180
|
const repoRoot = git(path.dirname(lockPath), ["rev-parse", "--show-toplevel"]);
|
|
182
181
|
const receipt = path.join(repoRoot, ".right-release", "addons", "adoptions", `${manifest.addon}-${manifest.platform}.json`);
|