diff --git a/.github/scripts/tests/ci-profile.test.mjs b/.github/scripts/tests/ci-profile.test.mjs index 313e8ece372..1401fd62899 100644 --- a/.github/scripts/tests/ci-profile.test.mjs +++ b/.github/scripts/tests/ci-profile.test.mjs @@ -232,6 +232,21 @@ test("export paces batches and retries only rejected transient events", async () await assert.rejects(sendEvents([events[0]], "test-only", "paperclip-ci", async () => ({ ok: true, json: async () => [{ status: 400 }] })), /status 400/); }); +function assertNoPrWorkflowCredentials(source) { + assert.doesNotMatch(source, /HONEYCOMB.*KEY/); + for (const [expression] of source.matchAll(/\$\{\{(?:'(?:[^']|'')*'|(?!\}\})[\s\S])*\}\}/g)) { + assert.doesNotMatch(expression, /\bsecrets\b/); + } +} + +test("PR credential guard permits filenames but rejects secret expressions", () => { + assertNoPrWorkflowCredentials("run: node --test scripts/docker-compose-secrets.test.mjs"); + for (const source of ["env: ${{ secrets.EXAMPLE }}", "env: ${{ secrets['EXAMPLE'] }}", "env: ${{ toJSON(secrets) }}", "env: ${{ format('}}', secrets.EXAMPLE) }}"]) { + assert.throws(() => assertNoPrWorkflowCredentials(source)); + } + assert.throws(() => assertNoPrWorkflowCredentials("HONEYCOMB_API_KEY: example")); +}); + test("workflow exporter runs trusted code and keeps keys out of the PR jobs", () => { const workflow = readFileSync(new URL("../../workflows/ci-profile.yml", import.meta.url), "utf8"); assert.match(workflow, /ref: \$\{\{ github.sha \}\}/); @@ -239,15 +254,7 @@ test("workflow exporter runs trusted code and keeps keys out of the PR jobs", () assert.match(workflow, /environment: ci-profiling/); assert.doesNotMatch(workflow, /head_sha|secrets: inherit|contents: write|pull-requests: write|pnpm install/); const pr = readFileSync(new URL("../../workflows/pr-trusted.yml", import.meta.url), "utf8"); - // A script filename such as docker-compose-secrets.test.mjs is not a - // GitHub secret lookup. Check expression context, including bracket access. - const secretReference = /HONEYCOMB.*KEY|\$\{\{[^}]*\bsecrets\b/; - assert.doesNotMatch("run: node --test scripts/docker-compose-secrets.test.mjs", secretReference); - for (const lookup of ["${{ secrets.PR_TOKEN }}", "${{ secrets['PR_TOKEN'] }}", "${{\n secrets.PR_TOKEN\n }}", "${{ toJSON(secrets) }}", "${{ secrets.HONEYCOMB_API_KEY }}"]) { - assert.match(lookup, secretReference); - } - - assert.doesNotMatch(pr, secretReference); + assertNoPrWorkflowCredentials(pr); assert.match(pr, /if: always\(\) && env.PAPERCLIP_CI_PROFILE == 'true'/); assert.doesNotMatch(pr, /PAPERCLIP_CI_PROFILE:.*labels/); assert.equal(pr.match(/PAPERCLIP_CI_PROFILE: \$\{\{ needs.gate.outputs.profile_ci \}\}/g).length, 9); diff --git a/doc/ci-profiling.md b/doc/ci-profiling.md index 49aac6a4ebc..954e8fd597d 100644 --- a/doc/ci-profiling.md +++ b/doc/ci-profiling.md @@ -195,3 +195,13 @@ Its configured readiness window stays long. The Git regression keeps all four filename lanes above 32 MiB, while the staging case transfers and deletes 4,000 files whose filename list exceeds 2 MiB. It still checks every staged file, unusual names, snapshot ownership, cleanup, and bounded command lengths. + +The Linux public-install check serves metadata read from the actual packed +archives through a temporary loopback registry inside its download container. +This avoids npm's file-spec manifest extraction of every archive before the +real install. npm still verifies each archive's SHA-512 and installs the same +packed bytes into an empty consumer. Other dependency metadata redirects to +the public npm registry; public tarball URLs remain unchanged. The temporary +registry closes before the existing offline lifecycle rebuild and runtime +probes. No lifecycle, network-isolation, lock-preservation or provider checks +are skipped. Native ARM and Mac qualification keep their existing file installs. diff --git a/package.json b/package.json index 93ef1dee218..2c4bad85f4b 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "smoke:posthog-live": "node scripts/smoke/posthog-live.mjs", "smoke:pipelines-tutorial": "./scripts/smoke/pipelines-tutorial-smoke.sh", "smoke:terminal-bench-loop-skill": "node scripts/smoke/terminal-bench-loop-skill-smoke.mjs", - "test:release-registry": "node scripts/ci-profile/node-test.mjs scripts/ci-profile/reporters.test.mjs scripts/__tests__/canary-startup-playwright.test.mjs scripts/__tests__/cursor-public-install-sandbox.test.mjs scripts/__tests__/grok-public-install-sandbox.test.mjs scripts/__tests__/public-install-closure.test.mjs scripts/__tests__/prefetch-child.test.mjs scripts/__tests__/stage-bundled-packages.test.mjs scripts/__tests__/pack-staged-packages.test.mjs scripts/__tests__/retain-runner-qualification-packages.test.mjs scripts/__tests__/runner-release-crate-cache.test.mjs scripts/__tests__/vitest-worker-isolation.test.mjs scripts/verify-release-registry-state.test.mjs scripts/release-package-map.test.mjs scripts/check-release-package-bootstrap.test.mjs scripts/check-no-git-push.test.mjs scripts/release-lib.test.mjs scripts/release-registry-versions.test.mjs scripts/link-plugin-dev-sdk.test.js scripts/acpx-patch-packaging.test.mjs scripts/service-onboard-smoke.test.mjs scripts/docker-onboard-smoke.test.mjs scripts/preview-artifacts.test.mjs scripts/cloud-migrator-artifacts.test.mjs", + "test:release-registry": "node scripts/ci-profile/node-test.mjs scripts/ci-profile/reporters.test.mjs scripts/__tests__/canary-startup-playwright.test.mjs scripts/__tests__/cursor-public-install-sandbox.test.mjs scripts/__tests__/grok-public-install-sandbox.test.mjs scripts/__tests__/public-install-closure.test.mjs scripts/__tests__/public-install-registry.test.mjs scripts/__tests__/prefetch-child.test.mjs scripts/__tests__/stage-bundled-packages.test.mjs scripts/__tests__/pack-staged-packages.test.mjs scripts/__tests__/retain-runner-qualification-packages.test.mjs scripts/__tests__/runner-release-crate-cache.test.mjs scripts/__tests__/vitest-worker-isolation.test.mjs scripts/verify-release-registry-state.test.mjs scripts/release-package-map.test.mjs scripts/check-release-package-bootstrap.test.mjs scripts/check-no-git-push.test.mjs scripts/release-lib.test.mjs scripts/release-registry-versions.test.mjs scripts/link-plugin-dev-sdk.test.js scripts/acpx-patch-packaging.test.mjs scripts/service-onboard-smoke.test.mjs scripts/docker-onboard-smoke.test.mjs scripts/preview-artifacts.test.mjs scripts/cloud-migrator-artifacts.test.mjs", "storybook-visual:baseline": "node scripts/storybook-visual-baseline.mjs", "test:storybook-visual": "node scripts/storybook-visual-baseline.mjs download && node scripts/storybook-visual-baseline.mjs verify && pnpm build-storybook && npx playwright test --config tests/storybook-visual/playwright.config.ts", "test:storybook-visual:update": "node scripts/storybook-visual-baseline.mjs download && pnpm build-storybook && npx playwright test --config tests/storybook-visual/playwright.config.ts --update-snapshots && node scripts/storybook-visual-baseline.mjs pack", diff --git a/scripts/__tests__/grok-public-install-sandbox.test.mjs b/scripts/__tests__/grok-public-install-sandbox.test.mjs index a3cbfbd7f2b..b2bf6dd1e6b 100644 --- a/scripts/__tests__/grok-public-install-sandbox.test.mjs +++ b/scripts/__tests__/grok-public-install-sandbox.test.mjs @@ -211,7 +211,7 @@ test('the image and Grok downloads start before staging and join without weakeni assert.doesNotMatch(source, /provision-grok\.mjs'\), prerequisite\]/); // Both downloads start before any package is staged and before the first sandbox runs. const staging = position('publicInstallClosure(repo)'); - const firstSandbox = position("isolated(['npm', 'install', '--ignore-scripts', '--omit=dev'"); + const firstSandbox = position("isolated(['node', '/packages/public-install-registry.mjs', '/packages'], { download: true })"); assert.ok(imagePull < staging && grokProvision < staging); assert.ok(position("await awaited('image', imageReady)") < firstSandbox); // The binary moves to the canonical host path only after npm is proven not to diff --git a/scripts/__tests__/public-install-registry.test.mjs b/scripts/__tests__/public-install-registry.test.mjs new file mode 100644 index 00000000000..5669ed5f461 --- /dev/null +++ b/scripts/__tests__/public-install-registry.test.mjs @@ -0,0 +1,110 @@ +import assert from 'node:assert/strict'; +import { execFileSync, spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import test from 'node:test'; +import { startPackedRegistry, writePackedRegistryCatalog } from '../public-install-registry.mjs'; + +const script = fileURLToPath(new URL('../public-install-registry.mjs', import.meta.url)); +const scratch = async body => { + const root = mkdtempSync(join(tmpdir(), 'public-install-registry-')); + try { await body(root); } finally { rmSync(root, { recursive: true, force: true }); } +}; +const environment = root => ({ ...process.env, HOME: root, npm_config_cache: join(root, 'cache'), + npm_config_userconfig: join(root, 'no-npmrc'), npm_config_audit: 'false', npm_config_fund: 'false', npm_config_update_notifier: 'false', npm_config_fetch_retries: '0', npm_config_fetch_timeout: '10000' }); +const run = (command, args, cwd) => new Promise((resolve, reject) => { + const child = spawn(command, args, { cwd, env: environment(cwd), stdio: ['ignore', 'pipe', 'pipe'] }); + let output = ''; + for (const stream of [child.stdout, child.stderr]) stream.on('data', chunk => { output += chunk; }); + child.once('error', reject); + child.once('exit', (code, signal) => code === 0 ? resolve(output) : reject(new Error(`${command} exited ${signal ?? code}: ${output}`))); +}); +const fixture = (root, { publicDependency = false } = {}) => { + const assets = join(root, 'assets'); mkdirSync(assets); + const packs = [ + { name: '@ci/leaf', version: '1.0.0', main: 'index.js' }, + { name: '@ci/optional', version: '1.0.0' }, + { name: '@ci/root', version: '1.0.0', dependencies: { '@ci/leaf': '1.0.0', 'bundled-leaf': '1.0.0', ...(publicDependency ? { 'is-number': '7.0.0' } : {}) }, + optionalDependencies: { '@ci/optional': '1.0.0' }, bundleDependencies: ['bundled-leaf'], bin: { 'ci-root': 'index.js' }, + scripts: { postinstall: "node -e \"require('node:fs').writeFileSync('lifecycle-ran', 'ok')\"" } }, + ]; + for (const [index, manifest] of packs.entries()) { + const source = join(root, `source-${index}`); mkdirSync(source); + writeFileSync(join(source, 'package.json'), JSON.stringify(manifest)); + writeFileSync(join(source, 'index.js'), '#!/usr/bin/env node\nmodule.exports = 42;\n'); + if (manifest.bundleDependencies) { + const bundle = join(source, 'node_modules/bundled-leaf'); mkdirSync(bundle, { recursive: true }); + writeFileSync(join(bundle, 'package.json'), JSON.stringify({ name: 'bundled-leaf', version: '1.0.0', main: 'index.js' })); + writeFileSync(join(bundle, 'index.js'), 'module.exports = "bundled";\n'); + } + execFileSync('npm', ['pack', '--ignore-scripts', '--pack-destination', assets], { cwd: source, env: environment(root), stdio: 'pipe' }); + writeFileSync(join(source, 'package.json'), JSON.stringify({ ...manifest, version: '9.9.9' })); + } + return assets; +}; +const installedFiles = directory => { + const entries = []; + const walk = (root, relative = '') => { + for (const item of readdirSync(root, { withFileTypes: true })) { + if (item.name === '.package-lock.json') continue; + const name = join(relative, item.name), path = join(root, item.name); + if (item.isDirectory()) walk(path, name); + else entries.push([name, item.isSymbolicLink() ? `link:${readlinkSync(path)}` : createHash('sha256').update(readFileSync(path)).digest('hex')]); + } + }; + walk(directory); + return entries.sort(([a], [b]) => a.localeCompare(b)); +}; + +test('real npm installs identical packed bytes, bundles, optional dependencies and bins without running scripts early', { timeout: 90_000 }, async () => scratch(async root => { + const assets = fixture(root, { publicDependency: true }), catalog = await writePackedRegistryCatalog(assets); + assert.ok(catalog.every(entry => entry.manifest.version === '1.0.0'), 'metadata must come from packed bytes, not changed source'); + const before = join(root, 'file-consumer'), after = join(root, 'registry-consumer'); + for (const cwd of [before, after]) { mkdirSync(cwd); writeFileSync(join(cwd, 'package.json'), '{"private":true}'); } + await run('npm', ['install', '--ignore-scripts', '--omit=dev', ...catalog.map(entry => join(assets, entry.file))], before); + await run(process.execPath, [script, assets], after); + assert.deepEqual(installedFiles(join(after, 'node_modules')), installedFiles(join(before, 'node_modules'))); + for (const cwd of [before, after]) { + const marker = join(cwd, 'node_modules/@ci/root/lifecycle-ran'); + assert.equal(existsSync(marker), false); + const lock = readFileSync(join(cwd, 'package-lock.json'), 'utf8'); + const publicPackage = JSON.parse(lock).packages['node_modules/is-number']; + assert.equal(publicPackage.version, '7.0.0'); + assert.equal(publicPackage.resolved, 'https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz'); + await run('npm', ['rebuild', '--offline', '--ignore-scripts=false', '--foreground-scripts'], cwd); + assert.equal(readFileSync(marker, 'utf8'), 'ok'); + assert.equal(readFileSync(join(cwd, 'package-lock.json'), 'utf8'), lock); + } + assert.deepEqual(installedFiles(join(after, 'node_modules')), installedFiles(join(before, 'node_modules'))); +})); + +test('the registry exposes only known tarballs, redirects other packages to npm and closes its listener', async () => scratch(async root => { + const assets = join(root, 'assets'); mkdirSync(assets); + writeFileSync(join(assets, 'registry-packages.json'), JSON.stringify([{ file: 'pkg.tgz', manifest: { name: '@ci/pkg', version: '1.0.0' }, integrity: 'sha512-YQ==' }])); + writeFileSync(join(assets, 'pkg.tgz'), 'packed bytes'); + const registry = await startPackedRegistry(assets); + try { + const metadata = await (await fetch(`${registry.url}/@ci%2fpkg`)).json(); + assert.equal(metadata.versions['1.0.0'].dist.integrity, 'sha512-YQ=='); + assert.equal(await (await fetch(metadata.versions['1.0.0'].dist.tarball)).text(), 'packed bytes'); + const external = await fetch(`${registry.url}/@other%2fpkg`, { redirect: 'manual' }); + assert.equal(external.status, 302); + assert.equal(external.headers.get('location'), 'https://registry.npmjs.org/%40other%2Fpkg'); + assert.equal((await fetch(`${registry.url}/-/900.tgz`)).status, 404); + assert.equal((await fetch(`${registry.url}/some%2foutside%2ffile`)).status, 404); + assert.equal((await fetch(`${registry.url}/@ci%2fpkg`, { method: 'POST' })).status, 405); + } finally { await registry.close(); } + await assert.rejects(fetch(`${registry.url}/@ci%2fpkg`)); +})); + +test('npm rejects changed tarball bytes against the catalog integrity and the CLI reports failure', { timeout: 90_000 }, async () => scratch(async root => { + const assets = fixture(root), catalog = await writePackedRegistryCatalog(assets); + const archive = join(assets, catalog[0].file); + const bytes = readFileSync(archive); + bytes[4] ^= 1; writeFileSync(archive, bytes); + const consumer = join(root, 'consumer'); mkdirSync(consumer); writeFileSync(join(consumer, 'package.json'), '{"private":true}'); + await assert.rejects(run(process.execPath, [script, assets], consumer), /EINTEGRITY/); +})); diff --git a/scripts/public-install-registry.mjs b/scripts/public-install-registry.mjs new file mode 100644 index 00000000000..b0b6d80c994 --- /dev/null +++ b/scripts/public-install-registry.mjs @@ -0,0 +1,88 @@ +import assert from 'node:assert/strict'; +import { execFileSync, spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { createReadStream, readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { createServer } from 'node:http'; +import { join } from 'node:path'; +import { pipeline } from 'node:stream/promises'; +import { fileURLToPath } from 'node:url'; + +const catalogFile = 'registry-packages.json'; +const packageName = /^(?:@[a-z0-9_.-]+\/)?[a-z0-9_.-]+$/; + +export async function writePackedRegistryCatalog(directory) { + const entries = []; + for (const file of readdirSync(directory).filter(file => file.endsWith('.tgz')).sort()) { + const archive = join(directory, file); + const manifest = JSON.parse(execFileSync('tar', ['-xOf', archive, 'package/package.json'], { + encoding: 'utf8', maxBuffer: 1024 * 1024, + })); + const hash = createHash('sha512'); + for await (const chunk of createReadStream(archive)) hash.update(chunk); + entries.push({ file, manifest, integrity: `sha512-${hash.digest('base64')}` }); + } + assert.ok(entries.length, 'The registry requires actual packed artifacts'); + writeFileSync(join(directory, catalogFile), JSON.stringify(entries)); + return entries; +} + +export async function startPackedRegistry(directory) { + const entries = JSON.parse(readFileSync(join(directory, catalogFile), 'utf8')); + const packages = new Map(); + for (const entry of entries) { + assert.match(entry.file, /^[a-zA-Z0-9._-]+\.tgz$/); + assert.match(entry.manifest.name, packageName); + assert.equal(typeof entry.manifest.version, 'string'); + assert.match(entry.integrity, /^sha512-[A-Za-z0-9+/]+={0,2}$/); + assert.ok(!packages.has(entry.manifest.name), 'Duplicate packed package'); + packages.set(entry.manifest.name, entry); + } + let url; + const server = createServer((request, response) => { + if (request.method !== 'GET') { response.writeHead(405).end(); return; } + let path; + try { path = decodeURIComponent(new URL(request.url, 'http://localhost').pathname.slice(1)); } + catch { response.writeHead(400).end(); return; } + const tarball = /^-\/(\d+)\.tgz$/.exec(path); + if (tarball) { + const entry = entries[Number(tarball[1])]; + if (!entry) { response.writeHead(404).end(); return; } + response.writeHead(200, { 'content-type': 'application/octet-stream' }); + pipeline(createReadStream(join(directory, entry.file)), response).catch(() => response.destroy()); + return; + } + if (!packageName.test(path)) { response.writeHead(404).end(); return; } + const entry = packages.get(path); + if (!entry) { + response.writeHead(302, { location: `https://registry.npmjs.org/${encodeURIComponent(path)}` }).end(); + return; + } + const { manifest, integrity } = entry; + const version = { ...manifest, dist: { tarball: `${url}/-/${entries.indexOf(entry)}.tgz`, integrity } }; + response.writeHead(200, { 'content-type': 'application/json' }); + response.end(JSON.stringify({ name: manifest.name, 'dist-tags': { latest: manifest.version }, versions: { [manifest.version]: version } })); + }); + await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, '127.0.0.1', resolve); }); + url = `http://127.0.0.1:${server.address().port}`; + return { + url, + specs: entries.map(({ manifest }) => `${manifest.name}@${manifest.version}`), + close: () => new Promise((resolve, reject) => { + server.close(error => error ? reject(error) : resolve()); + server.closeAllConnections(); + }), + }; +} + +if (process.argv[1] === fileURLToPath(import.meta.url)) { + assert.equal(process.argv.length, 3, 'Usage: public-install-registry.mjs PACKED_ASSETS'); + const registry = await startPackedRegistry(process.argv[2]); + try { + const status = await new Promise((resolve, reject) => { + const child = spawn('npm', ['install', '--registry', registry.url, '--replace-registry-host=never', '--ignore-scripts', '--omit=dev', ...registry.specs], { stdio: 'inherit' }); + child.once('error', reject); + child.once('exit', (code, signal) => signal ? reject(new Error(`npm install received ${signal}`)) : resolve(code)); + }); + process.exitCode = status; + } finally { await registry.close(); } +} diff --git a/scripts/verify-grok-npm-install.mjs b/scripts/verify-grok-npm-install.mjs index ea01fa01bb0..2ff9a5e8a8a 100644 --- a/scripts/verify-grok-npm-install.mjs +++ b/scripts/verify-grok-npm-install.mjs @@ -18,6 +18,7 @@ import { GROK_PUBLIC_INSTALL_IMAGE, GROK_PUBLIC_INSTALL_LIFECYCLE, assertInstall import { startPrefetch, stopPrefetches } from './prefetch-child.mjs'; import { retainRunnerQualificationPackages } from './retain-runner-qualification-packages.mjs'; import { bundledInstallTimings, startBundledInstalls } from './stage-bundled-packages.mjs'; +import { writePackedRegistryCatalog } from './public-install-registry.mjs'; const repo = resolve(dirname(fileURLToPath(import.meta.url)), '..'); async function verifyMacConsumer(packageDirectory, sourceRevision, receiptPath) { assert.equal(process.env.GITHUB_ACTIONS, 'true', 'Mac clean consumers run only on disposable hosted CI'); @@ -358,7 +359,11 @@ try { await awaited('image', imageReady); // npm resolution is intentionally the public consumer graph, not the pnpm // workspace graph. Freeze that result before any lifecycle script can run. - isolated(['npm', 'install', '--ignore-scripts', '--omit=dev', ...readdirSync(assets).filter(file => file.endsWith('.tgz')).map(file => `/packages/${file}`)], { download: true }); + const metadataStarted = performance.now(); + await writePackedRegistryCatalog(assets); + cpSync(join(repo, 'scripts/public-install-registry.mjs'), join(assets, 'public-install-registry.mjs')); + timing.registry_metadata_ms = Math.round(performance.now() - metadataStarted); + isolated(['node', '/packages/public-install-registry.mjs', '/packages'], { download: true }); phase('consumer_install'); assert.equal(existsSync(sentinel), false, 'Dependency download must not run lifecycle scripts'); const consumerLock = readFileSync(join(consumer, 'package-lock.json'), 'utf8'); @@ -408,7 +413,7 @@ try { renameSync(stagedGrok, prerequisite); isolated(['node', '/packages/probe.mjs', 'present'], { prerequisite }); phase('grok_probes'); - console.log(JSON.stringify({ schema: 'paperclip.grok.public-npm-install.v1', sourceRevision, releaseVersion, lifecycleScriptsEnabled: true, lifecycleSentinelVerified: true, lifecycleNetwork: 'none', consumerImage: GROK_PUBLIC_INSTALL_IMAGE, consumerUid, consumerLockPreserved: true, cleanNpmInstall: true, packageCount: needed.size, codexPackedPayloadsOmitted: true, codexPackedPackagesChecked: tarballs.length, ...codex, builtinLauncherPresent: true, separateGrokPackage: false, npmProvisionedBinary: false, missingPrerequisiteRejected: true, provisionedBinaryVerified: true, commandLeaseVerified: true, providerCalls: 0 })); + console.log(JSON.stringify({ schema: 'paperclip.grok.public-npm-install.v1', artifactTransport: 'loopback-registry', sourceRevision, releaseVersion, lifecycleScriptsEnabled: true, lifecycleSentinelVerified: true, lifecycleNetwork: 'none', consumerImage: GROK_PUBLIC_INSTALL_IMAGE, consumerUid, consumerLockPreserved: true, cleanNpmInstall: true, packageCount: needed.size, codexPackedPayloadsOmitted: true, codexPackedPackagesChecked: tarballs.length, ...codex, builtinLauncherPresent: true, separateGrokPackage: false, npmProvisionedBinary: false, missingPrerequisiteRejected: true, provisionedBinaryVerified: true, commandLeaseVerified: true, providerCalls: 0 })); // Exercise Pi's public CLI and installed server, never a private workspace // package or binary override. Public dependency downloads are explicit and // isolated; the actual admission probe runs without a network or credentials.