Skip to content

feat(cli): auto-detect embedded private packages [RED-862] - #1438

Open
sorccu wants to merge 10 commits into
mainfrom
simo/red-862-detect-embedded-packages
Open

feat(cli): auto-detect embedded private packages [RED-862]#1438
sorccu wants to merge 10 commits into
mainfrom
simo/red-862-detect-embedded-packages

Conversation

@sorccu

@sorccu sorccu commented Aug 13, 2026

Copy link
Copy Markdown
Member

Linear: RED-862

Stacked on #1435 — targets simo/red-855-embed-private-dep-tarballs and should be rebased/retargeted onto main once that PR merges. Note the test workflow only triggers on PRs targeting main/next/*, so CI checks will not run here until the retarget; the full unit suite (2013 tests) passes locally on this commit.

Adds automatic detection of private packages to embed in Playwright code bundles, complementing the explicit checks.embeddedPackages list from RED-855. Detection runs during deploy/test/pw-test, is enabled by default (checks.detectEmbeddedPackages, per-run --no-detect-embedded-packages, env CHECKLY_DETECT_EMBEDDED_PACKAGES), and fails soft: any detection problem degrades to a warning, never a failed run, while explicit embeddedPackages entries keep their strict, fatal guarantees.

Privacy model

Private package names never leave the machine unless explicitly opted in:

  1. Zero-network proofs — a public recorded tarball URL, or a public effective registry, classifies a package without any traffic.
  2. Scope mapping@scope:registry pointing at a non-public registry ⇒ embed, no lookup (over-embedding is safe by the bundle contract).
  3. Registry interrogation — remaining undecided packages are checked against the customer's own registry via the Sonatype Nexus REST API, using the .npmrc credentials: hosted ⇒ embed. A per-group source-repo visibility guard and a zero-hosted-repos guard prevent permission-filtered listings from minting unsound "public" verdicts; same-origin recorded sources that aren't Nexus-shaped are decided conservatively (hosted ⇒ embed, silence ⇒ still undecided).
  4. Opt-in fallbackchecks.detectEmbeddedPackagesFallback: "public-registry" allows an integrity diff against public npm for whatever remains undecided. This transmits those names, so it is off by default (skip + warning). Transmitted names yield durable cached verdicts, so a name never needs transmitting twice — including when a lookup fails mid-run (partial results are persisted).

Graph-frontier pruning (added after review of the initial version)

The fallback previously fetched one packument per unique undecided name — tens of thousands of requests for a large mirrored tree. Detection now extracts the dependency graph from the lockfile (pnpm v6/v9 snapshots/inline deps, npm v2/v3 nearest-node_modules resolution, aliases handled, git/file entries excluded) and runs the diff in frontier rounds: a package a provably public parent vouches for is assumed public without a lookup (its name is never transmitted — privacy strictly improves), while the exposed surface is always verified: workspace-direct dependencies, children of embedded packages, any version of a name with known private versions, parentless entries, and entries whose parents are still undecided (deferred to a later round). For a tree with ~100 direct dependencies, request count drops to roughly those roots plus the surface of private subtrees.

Soundness guardrails: assumed verdicts are refutable and never persisted to the immutable verdict cache; runs that assumed anything are not summary-cached (assumptions re-derive each run, and real registry verdicts take over once the registry becomes interrogable); the summary digest includes the fallback mode so flipping back to "skip" never serves an assumption-derived embed set; groups are processed inventory-capable-first so registry-proven embeds expose their children before any diff can assume them. The residual blind spot — a private artifact under a public name at an interior, fully-vouched tree position — cannot silently run wrong code (the runner install fails its lockfile integrity check loudly); the remedy (checks.embeddedPackages) is documented in the config JSDoc, the reference docs, and install troubleshooting.

Caching

Two levels in the CLI cache (node_modules/.cache/checkly, per-user dir as fallback): a detection summary keyed by lockfile bytes + registry config + credentials + explicit specs, and per-entry immutable integrity-proof verdicts. Degraded runs (skipped packages or a broken .npmrc, e.g. an unset ${VAR}) are never summary-cached, so problems keep warning until fixed. Warm runs make zero network requests.

Validated end-to-end against a real dockerized Nexus Repository CE instance: REST-based verdicts correct with zero public-registry traffic, scope tier fully offline, warm runs request-free.

Known follow-ups (minor, from review)

  • No positive test pins the restAccessRemediable tags or the combined two-tier failure message.
  • An unresolvable scope mapping produces one fail-soft download warning per package (plus the config-problem warning) instead of a single aggregate.
  • A few small dedups (a thrice-spelled error predicate, the classifyEntries catch mirroring the tier decision below it).
  • Graph pruning went through 5 review rounds (2 beyond the cap, user-authorized), converging at round 5 with zero confirmed findings. Rounds 4–5 restructured the algorithm materially: query priority is now frontier → assumption → minimal stall-breakers → last resort, so a dependency cycle no longer drags its descendant closure into the public registry (previously up to ~44% of transmitted names on a real large lockfile came from that path); assumption runs only when no query can add evidence; no version of a name is assumed while a sibling version is undecided anywhere in the run. Remaining accepted items: per-round putVerdicts calls interact with the 10k-entry verdict-cache truncation cap (pre-existing mechanism, extreme caches only); cross-group interplay is heuristically ordered (inventory-capable groups first) rather than guaranteed, and cross-group undecided parents do not defer assumption — both documented; propagation.publicKeys/assumedCount bookkeeping is spread across three write sites (hardening opportunity); the pnpm parser iterates packages twice (once for entries, once for v6 edges). A 6th round (substantive-findings-only) confirmed one issue, now fixed: the privacy docs under-stated what the fallback transmits (names resolved at multiple versions are also always verified — the docs now name that third category instead of claiming "other names are never transmitted"). It also flagged a performance regression for projects mixing an inventory-decided Nexus group with an assumption-using diff group: losing summary caching meant re-enumerating the Nexus inventory every run. Fixed in a follow-up commit by snapshotting the registry's RAW responses (never derived verdicts, which would be unsound for topology-dependent inventory 'public' results): repeat runs recompute verdicts from snapshotted data and make no requests. Privacy guardrails: listings persist as a name/format/type projection (proxy upstream URLs with embedded credentials never reach disk), inventories are restricted to lockfile-referenced keys, filenames hash the credentials, and clean runs write no snapshot. Failures are never snapshotted (retried live), and a snapshot whose listing fails a visibility guard triggers a single live listing re-fetch — the minimum that notices a registry-side permission grant — reusing the inventory while the listing is unchanged. This went through its own 3-round review (rounds found and fixed: a catalog-spill privacy issue, a dead-write class, stale-file resurrection, and structural simplifications); remaining accepted notes: InstanceState invariants span two methods, and the NexusRegistryApi handle is resolved twice per group.

🤖 Generated with Claude Code

sorccu and others added 5 commits August 14, 2026 14:20
Adds the config surface for embedding private dependency tarballs into
the Playwright Check Suite code bundle: a TSDoc'd checks.embeddedPackages
option (package names or name@exact-version pins), runtime shape
validation at config load, a reusable spec parser, and plumbing through
ProjectParseOpts into Session for deploy, test, validate, pw-test and
debug parse-project.

Scaffolding only: resolution/fetch services and bundling wiring land in
follow-up commits on this branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure services that turn checks.embeddedPackages entries into verified
registry tarballs: lockfile enumeration (pnpm-lock.yaml v6/v9,
package-lock.json v2/v3, with precise reasons for git/file/workspace/
integrity-less entries), .npmrc parsing with scope-aware registry
resolution, nerf-dart auth matching and npm_config_* env layering, SRI
integrity helpers, a content-addressed per-user tarball cache
(CHECKLY_CACHE_DIR override, atomic writes, self-healing corrupt
entries) with a read-only npm cacache lookup tier, and a memoized
materializer running the CLI cache -> npm cache -> registry download
source chain with proxy-aware axios and credential-redacted errors.

Consumed by the Playwright bundler in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es [RED-855]

Wires the embedded-packages services into the CLI: a memoized
session-level materializer shared by validation and bundling, project
validation that resolves checks.embeddedPackages against the lockfile
before any bundling (grouped, readable diagnostics; skipped when the
project has no Playwright checks), and Playwright bundling that appends
the verified tarballs at the runner contract path
.checkly/embedded-packages/<name>@<version>.tgz via explicit archive
paths, independent of workspace layout.

Includes offline integration tests driven by a pre-seeded
CHECKLY_CACHE_DIR with committed deterministic tarball fixtures, plus
TSDoc and AI-context documentation.

The runner half that serves the embedded tarballs during install is
RED-856; CLI releases containing this feature must wait for it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t [RED-855]

The materializer cacache test seeded a fake npm cache at ~/.npm, but on
Windows npm caches under %LOCALAPPDATA%\npm-cache, so the lookup missed
and the test fell through to a recorded network request. Pin the
location via npm_config_cache, which production honors on every
platform, and add direct coverage for the win32 LOCALAPPDATA lookup
branch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…[RED-855]

The embedded-packages cache now defaults to the workspace root's
node_modules/.cache/checkly — the conventional tool-cache location that
incremental installs leave alone and node_modules-caching CI setups
persist automatically — so warm caches travel with the project instead
of living in a per-user directory. The cache is multi-root: reads also
consult the per-user platform directory, and writes fall back to it
when the project location is not writable (e.g. a read-only checkout),
with CHECKLY_CACHE_DIR remaining the single-location override.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sorccu
sorccu force-pushed the simo/red-855-embed-private-dep-tarballs branch from 79eeeb3 to 20c3a23 Compare August 14, 2026 05:22
@sorccu
sorccu force-pushed the simo/red-862-detect-embedded-packages branch from 9dac2ed to 3118b06 Compare August 14, 2026 05:22
Entries may now contain * wildcards (@acme/*, acme-*, @acme/*-utils),
each matching any run of characters except /, so a pattern never
crosses the scope separator. Wildcards resolve against the workspace
lockfile only and combine with exact version pins. Matches that cannot
be embedded are skipped — workspace members silently, git/file/URL and
integrity-less dependencies via a warning diagnostic — while a spec
whose only matches cannot be embedded, or that matches nothing at all,
remains an error. Each wildcard announces what it selected during
bundling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sorccu
sorccu force-pushed the simo/red-862-detect-embedded-packages branch 2 times, most recently from 70f9592 to 13706a2 Compare August 18, 2026 07:11
sorccu and others added 2 commits August 18, 2026 16:23
…e upload size limit

The API rejects an oversized code bundle upload with HTTP 413, which
previously surfaced as a raw "Payload content length greater than
maximum allowed: <bytes>" message. Map 413 responses to a typed
PayloadTooLargeError and convert it at the upload site into a
BundleTooLargeError that names the bundle size, the server-reported
limit (parsed from the response rather than hard-coded), and how to
reduce the bundle — mentioning embedded packages only when the bundle
actually contains them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Automatically detect lockfile packages that Checkly runners cannot fetch
from the public npm registry and embed their tarballs into Playwright
code bundles, alongside the explicit checks.embeddedPackages list.
Enabled by default (checks.detectEmbeddedPackages, per-run
--no-detect-embedded-packages).

Private package names never leave the machine unless explicitly opted
in: detection uses zero-network public proofs, @scope:registry mappings,
and the project's own Sonatype Nexus REST API, with an opt-in
public-registry integrity diff (checks.detectEmbeddedPackagesFallback)
for anything left undecided. Results are cached (summary keyed by
lockfile + registry config + credentials + explicit specs, plus
immutable per-entry integrity-proof verdicts); degraded runs are never
summary-cached. All detection failures fail soft with warnings naming
the actual cause; explicit entries remain strictly fatal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sorccu
sorccu force-pushed the simo/red-855-embed-private-dep-tarballs branch from 3c2e150 to 7b7ad3c Compare August 18, 2026 07:23
@sorccu
sorccu force-pushed the simo/red-862-detect-embedded-packages branch 3 times, most recently from 3052299 to 54b9b9b Compare August 18, 2026 11:25
…graph frontier [RED-862]

The opt-in public-registry fallback previously fetched one packument per
unique undecided package name — tens of thousands of requests for a
large mirrored tree. Detection now extracts the dependency graph from
the lockfile and assumes packages public when a provably public parent
vouches for them, verifying only the exposed surface: workspace-direct
dependencies, children of embedded packages, versions of names with
known private versions, parentless entries, and entries whose parents
are still undecided. Assumed verdicts are refutable and never persisted
to the immutable verdict cache, and runs that used any assumption are
not summary-cached so real registry verdicts take over as soon as the
registry becomes interrogable. The residual blind spot — a private
artifact under a public name at an interior, fully-vouched tree
position — fails loudly at runner install via the lockfile integrity
check and is documented with its remedy in the config JSDoc, the
reference docs, and install troubleshooting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sorccu
sorccu force-pushed the simo/red-862-detect-embedded-packages branch from 54b9b9b to 960fbb7 Compare August 18, 2026 12:20
… cache a summary [RED-862]

Degraded and assumption-using detection runs deliberately never cache
their summary, which previously meant re-interrogating the private
registry (repositories listing plus a paged component walk) on every
deploy/test. The registry's raw responses are now snapshotted in the
CLI cache, keyed by the detection input digest and the (hashed)
instance identity: repeat runs recompute their verdicts from data
identical to what the registry returned under the same inputs, making
warm runs request-free.

Soundness and privacy guardrails: data is cached, never verdicts;
only successful full interrogations are snapshotted, so failures are
retried every run; the listing is persisted as a name/format/type
projection (registry-side configuration such as proxy upstream URLs,
which can embed credentials, never reaches disk) and the inventory is
restricted to keys the run's lockfile references; clean runs whose
summary caches write no snapshot at all. A snapshot serves its whole
instance or not at all: when its listing fails any group's visibility
guard, only the listing is re-fetched live — one request, the minimum
that can notice a registry-side permission grant — with the inventory
reused while the listing is unchanged and the snapshot discarded (and
deleted) when it differs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sorccu
sorccu force-pushed the simo/red-855-embed-private-dep-tarballs branch from 7b7ad3c to 5401953 Compare August 20, 2026 06:27
Base automatically changed from simo/red-855-embed-private-dep-tarballs to main August 21, 2026 09:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant