feat(compile): zero-config bindings — auto-compile default + faithfulness marker - #7137
feat(compile): zero-config bindings — auto-compile default + faithfulness marker#7137jdalton wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
🚧 Files skipped from review as they are similar to previous changes (6)
📝 WalkthroughWalkthroughThe compiler now classifies well-known bindings as full or partial, compiles reachable packages by default, and detects installed packages shadowed by partial bundled bindings. Text builds report these selections, while strict mode rejects them. ChangesBinding faithfulness and zero-config compilation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CompilePipeline
participant ModuleCollector
participant NodeModules
participant WellKnownBinding
participant CompilationContext
CompilePipeline->>ModuleCollector: Collect native imports
ModuleCollector->>NodeModules: Search ancestor node_modules copies
NodeModules-->>ModuleCollector: Installed package status
ModuleCollector->>WellKnownBinding: Check is_faithful()
WellKnownBinding-->>ModuleCollector: Full or Partial
ModuleCollector->>CompilationContext: Record partial binding selection
CompilePipeline->>CompilationContext: Read recorded selections
CompilePipeline-->>CompilePipeline: Report note or enforce strict mode
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Moves perry toward "it just works" for dependents (Socket Firewall is the driving case) — no hand-maintained perry.compilePackages list and no node_modules offloading needed. Auto-compile default: a project that does not pin perry.compilePackages now gets its whole reachable node_modules graph compiled automatically (host_config injects the universal "*" and auto-satisfies the allow.compilePackages two-key check). Perry is a compiler, not a supply-chain gate — "this package is not on a list" is no longer a hard error; a dependency that genuinely cannot compile surfaces as an ordinary compile error. Native-shimmed packages still resolve to their bundled bindings (the PerryTS#3527 wildcard expansion skips them). Opt out with an explicit array, or compilePackages: false / [] to compile nothing and re-arm the V8-free gate. perry.compilePackages: "auto" (and allow.compilePackages: true) are the explicit spelling of the default. Faithfulness marker: well-known bindings gain compat = "full" | "partial" (absent => partial, conservative) in well_known_bindings.toml, parsed into BindingCompat with an is_faithful() helper. When perry auto-prefers a partial binding over an installed node_modules copy it prints a one-line transparency note; PERRY_REQUIRE_FAITHFUL_BINDINGS=1 turns that into a refusal (default off, so existing builds are byte-identical). Faithfulness audit (conservative — default to partial unless clearly complete): dotenv / nanoid / slugify / uuid => full (small, complete pure ports); undici (dispatcher-only), node-forge (PKI-only), lru-cache => explicit partial with rationale; every other binding inherits the partial default. Design note + firewall-cleanup guidance: docs/src/native-libraries/zero-config-and-faithfulness.md
66e3e92 to
1f285c8
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@changelog.d/zero-config-binding-faithfulness.md`:
- Around line 27-52: Remove the duplicate ### Added heading in the changelog by
moving the perry.compilePackages: "auto" bullet into the existing first ###
Added section, keeping all Added entries grouped before ### Changed.
- Line 1: Rename the changelog fragment from zero-config-binding-faithfulness.md
to the required <PR_NUMBER>-zero-config-binding-faithfulness.md format,
replacing <PR_NUMBER> with the actual pull request number and preserving the
existing contents.
In `@crates/perry/src/commands/compile/collect_modules.rs`:
- Around line 1224-1242: Add PERRY_REQUIRE_FAITHFUL_BINDINGS to the
BUILD_CACHE_ENV_VARS list in build_cache.rs so cache keys include the
strict-binding mode. Preserve the existing collect_modules refusal behavior and
include the variable alongside the other PERRY_* build-affecting environment
settings.
- Around line 1214-1223: Update the well-known binding lookup in the bare-import
handling around parse_package_specifier to try the full import.source specifier
first, then fall back to the parsed bare package name. Ensure the resulting
binding is used for the existing node_builtin, is_faithful, transparency-note,
and PERRY_REQUIRE_FAITHFUL_BINDINGS logic, including subpath keys such as
mysql2/promise.
In `@crates/perry/src/commands/compile/host_config.rs`:
- Around line 682-706: Track whether PERRY_ALLOW_PERRY_FEATURES=0 explicitly
activated the fail-closed override while processing the existing environment
handling. Update the universal-allow injection after the auto-compile default so
it does not add "*" to ctx.allow_compile_packages when that override is active,
while preserving current wildcard behavior otherwise.
In `@crates/perry/src/commands/compile/well_known.rs`:
- Around line 88-102: Update WellKnownBinding::is_faithful() to resolve alias_of
and inherit the target binding’s compat status, so aliases such as
mysql2/promise and fetch are faithful when their targets are Full. Preserve the
current conservative behavior for non-alias rows and unresolved or non-Full
targets, using the existing well-known binding lookup mechanism.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5421f9ea-c034-40aa-a6a4-959619429e18
📒 Files selected for processing (8)
changelog.d/zero-config-binding-faithfulness.mdcrates/perry/src/commands/compile/collect_modules.rscrates/perry/src/commands/compile/host_config.rscrates/perry/src/commands/compile/run_pipeline.rscrates/perry/src/commands/compile/types.rscrates/perry/src/commands/compile/well_known.rscrates/perry/well_known_bindings.tomldocs/src/native-libraries/zero-config-and-faithfulness.md
| @@ -0,0 +1,52 @@ | |||
| ### Added | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Rename the fragment to the required <PR>-<slug>.md format.
This file is changelog.d/zero-config-binding-faithfulness.md, missing the PR-number prefix. As per coding guidelines, changelog.d/*.md fragments must follow changelog.d/<PR>-<slug>.md: "Add a PR-keyed changelog.d/<PR>-<slug>.md fragment without a version header for every change; never append to CHANGELOG.md." Based on learnings, the changeset-gate CI job enforces the <PR>-<slug>.md naming, so this filename would likely fail that check.
Rename the file, e.g. changelog.d/<PR_NUMBER>-zero-config-binding-faithfulness.md.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@changelog.d/zero-config-binding-faithfulness.md` at line 1, Rename the
changelog fragment from zero-config-binding-faithfulness.md to the required
<PR_NUMBER>-zero-config-binding-faithfulness.md format, replacing <PR_NUMBER>
with the actual pull request number and preserving the existing contents.
Sources: Coding guidelines, Learnings
| ### Changed | ||
|
|
||
| - **Auto-compile is now the default — no `perry.compilePackages` allowlist | ||
| required.** A project that does not pin a `perry.compilePackages` value gets | ||
| its whole reachable `node_modules` dependency graph compiled automatically. | ||
| Perry is a compiler, not a supply-chain gate: faithfully compiling code that | ||
| is already installed is not perry's decision to block, and "this package is | ||
| not on a list" is no longer a hard error. A dependency that genuinely cannot | ||
| be compiled surfaces as an ordinary **compile error** with a diagnostic, not | ||
| a policy refusal. Native-shimmed packages still resolve to their bundled | ||
| bindings (the wildcard expansion skips them), so bindings keep winning. | ||
|
|
||
| Supply-chain hygiene (pinning, lockfiles, review, dedicated tooling) is the | ||
| user's responsibility. Opt out to the old "listed packages only" behavior | ||
| with an explicit `perry.compilePackages` array, or `compilePackages: false` / | ||
| `[]` to compile nothing and re-arm the V8-free gate. | ||
|
|
||
| ### Added | ||
|
|
||
| - **`perry.compilePackages: "auto"` (and `perry.allow.compilePackages: true`).** | ||
| Explicit spelling of the new default — sugar for the universal `["*"]` | ||
| wildcard (#3527), useful to document intent or to re-enable auto-compile | ||
| inside a config that would otherwise constrain it. `"auto"` / `"all"` / | ||
| `true` are accepted; an explicit array is unchanged. A universal-trust value | ||
| auto-satisfies `perry.allow.compilePackages`, and native-shimmed packages | ||
| stay on their bindings exactly as with a literal `"*"`. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Merge the duplicate ### Added heading.
The file has ### Added (line 1), ### Changed (line 27), then a second ### Added (line 44). Static analysis (markdownlint) flags this as "Multiple headings with the same content." Move the perry.compilePackages: "auto" bullet into the first ### Added section, or reorder so all ### Added bullets are grouped together, ahead of ### Changed.
🧰 Tools
🪛 markdownlint-cli2 (0.23.1)
[warning] 44-44: Multiple headings with the same content
(MD024, no-duplicate-heading)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@changelog.d/zero-config-binding-faithfulness.md` around lines 27 - 52, Remove
the duplicate ### Added heading in the changelog by moving the
perry.compilePackages: "auto" bullet into the existing first ### Added section,
keeping all Added entries grouped before ### Changed.
Source: Linters/SAST tools
| let is_bare = !import.source.starts_with('.') && !import.source.starts_with('/'); | ||
| if is_bare { | ||
| let (pkg_name, _) = parse_package_specifier(&import.source); | ||
| if let Some(binding) = super::well_known::lookup_well_known(&pkg_name) { | ||
| // Node builtins (net/http/zlib/events/…) are always native | ||
| // and have no meaningful npm copy to shadow — skip them. | ||
| if !binding.node_builtin | ||
| && !binding.is_faithful() | ||
| && node_modules_copy_on_disk(&pkg_name, entry_path, &canonical) | ||
| { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n '^\[bindings\.[^]]*/[^]]*\]' crates/perry/well_known_bindings.toml -A6Repository: PerryTS/perry
Length of output: 930
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target lines =="
sed -n '1190,1245p' crates/perry/src/commands/compile/collect_modules.rs
echo
echo "== well_known relevant =="
sed -n '1,90p' crates/perry/src/commands/compile/well_known.rs
echo
echo "== parse_package_specifier usages/definition =="
rg -n "parse_package_specifier|struct WellKnownBinding|impl WellKnownBinding|pub fn lookup_well_known" crates/perry/src/commands/compile/well_known.rs crates/perry/src/commands/compile -g '*.rs'
echo
echo "== TOML parsed table names with slashes =="
python3 - <<'PY'
import tomllib
from pathlib import Path
data = tomllib.loads(Path('crates/perry/well_known_bindings.toml').read_text())
for k,v in data.items():
if k.startswith('bindings.') and '/' in k:
print(f"{k}\tother={v.get('alias-of')} partial={v.get('partial')} node_builtin={v.get('node_builtin')}")
PYRepository: PerryTS/perry
Length of output: 9854
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== parse_package_specifier implementation =="
sed -n '400,460p' crates/perry/src/commands/compile/resolve.rs
echo
echo "== well_known implementation =="
sed -n '130,190p' crates/perry/src/commands/compile/well_known.rs
echo
echo "== bindings sections with slashes from source =="
sed -n '420,445p' crates/perry/well_known_bindings.toml
sed -n '545,575p' crates/perry/well_known_bindings.toml
echo
echo "== parse specifiers =="
python3 - <<'PY'
from pathlib import Path
text = Path('crates/perry/src/commands/compile/resolve.rs').read_text()
start = text.index('pub(super) fn parse_package_specifier')
end = text.find('\n', text.index('{', start) + len('{')) + 1
body = text[start:end]
print(body[:1200])
PYRepository: PerryTS/perry
Length of output: 7607
Also match well-known bindings by full subpath specifier before applying bare-package rules.
Registered well-known bindings can use subpath keys like "mysql2/promise", but this block only calls lookup_well_known() with the package name returned by parse_package_specifier, and parse_package_specifier("mysql2/promise") returns the bare name "mysql2". A subpath-keyed partial binding will therefore skip the transparency note and the PERRY_REQUIRE_FAITHFUL_BINDINGS refusal when an installed node_modules copy exists.
Lookup the full import source first, then fall back to the bare package name.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/perry/src/commands/compile/collect_modules.rs` around lines 1214 -
1223, Update the well-known binding lookup in the bare-import handling around
parse_package_specifier to try the full import.source specifier first, then fall
back to the parsed bare package name. Ensure the resulting binding is used for
the existing node_builtin, is_faithful, transparency-note, and
PERRY_REQUIRE_FAITHFUL_BINDINGS logic, including subpath keys such as
mysql2/promise.
| if std::env::var_os("PERRY_REQUIRE_FAITHFUL_BINDINGS").is_some() { | ||
| anyhow::bail!( | ||
| "`{pkg}` resolves to the bundled native binding \ | ||
| `{krate}`, which is a PARTIAL drop-in (not the full \ | ||
| npm API), but a `node_modules/{pkg}` copy is installed \ | ||
| and `PERRY_REQUIRE_FAITHFUL_BINDINGS` is set. Refusing \ | ||
| to auto-prefer the partial binding.\n\ | ||
| \n\ | ||
| Either add `{pkg}` to `perry.compilePackages` (+ \ | ||
| `perry.allow.compilePackages`) to AOT-compile the real \ | ||
| JavaScript, or unset PERRY_REQUIRE_FAITHFUL_BINDINGS to \ | ||
| accept the partial binding.\n\ | ||
| \n\ | ||
| (imported from {importer})", | ||
| pkg = pkg_name, | ||
| krate = binding.krate, | ||
| importer = canonical.display(), | ||
| ); | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect BuildCacheProbe's fingerprint inputs for PERRY_REQUIRE_FAITHFUL_BINDINGS coverage.
fd -a build_cache.rs crates/perry/src/commands/compile | xargs -I{} rg -n -C6 'fn probe\(|fingerprint|PERRY_REQUIRE_FAITHFUL_BINDINGS|env::var' {}Repository: PerryTS/perry
Length of output: 8941
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
fd -a . crates/perry/src/commands/compile | sed -n '1,120p'
echo "== BUILD_CACHE_ENV_VARS current_perry_fingerprint current_env references =="
rg -n -C4 'BUILD_CACHE_ENV_VARS|current_env|current_perry_fingerprint|PERRY_REQUIRE_FAITHFUL_BINDINGS|PERRY_DEBUG_SYMBOLS|PERRY_TARGET_CPU|apply_pkg_and_toml_config|run_pipeline' crates/perry/src/commands/compile
echo "== run_pipeline relevant area =="
fd -a run_pipeline.rs crates/perry/src/commands/compile | xargs -I{} sed -n '1,260p' {}
echo "== build_cache relevant area =="
fd -a build_cache.rs crates/perry/src/commands/compile | xargs -I{} sed -n '450,560p' {}Repository: PerryTS/perry
Length of output: 50369
Add PERRY_REQUIRE_FAITHFUL_BINDINGS to BUILD_CACHE_ENV_VARS.
build_cache.rs includes PERRY_DEBUG_SYMBOLS and PERRY_TARGET_CPU in the build cache env list, but not PERRY_REQUIRE_FAITHFUL_BINDINGS, while the strict partial-binding refusal reads that variable only during collect_modules. A cache hit can therefore return a build from a non-strict run instead of running the refusal path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/perry/src/commands/compile/collect_modules.rs` around lines 1224 -
1242, Add PERRY_REQUIRE_FAITHFUL_BINDINGS to the BUILD_CACHE_ENV_VARS list in
build_cache.rs so cache keys include the strict-binding mode. Preserve the
existing collect_modules refusal behavior and include the variable alongside the
other PERRY_* build-affecting environment settings.
| // Auto-compile default (owner policy): a project that does not pin a | ||
| // `perry.compilePackages` value gets its whole reachable node_modules | ||
| // graph compiled — perry is a compiler, not a supply-chain gate, so | ||
| // "unlisted" is not a hard error, it just compiles (and a dependency that | ||
| // genuinely can't compile surfaces as an ordinary compile error). This is | ||
| // exactly `compilePackages: "auto"`: inject the universal `"*"`, which the | ||
| // expansion just below turns into concrete installed names (native-shimmed | ||
| // packages are skipped, so bundled bindings still win). Opt out with an | ||
| // explicit list, or `compilePackages: false` / `[]` to compile nothing and | ||
| // restore the V8-free gate's "listed only" behavior. | ||
| if !compile_packages_explicit { | ||
| ctx.compile_packages.insert("*".to_string()); | ||
| } | ||
| // Universal trust ⇒ universal allow. Whenever routing includes the `"*"` | ||
| // wildcard (from this auto default, the `"auto"` scalar, or a literal | ||
| // `["*"]`), the host is trusting its whole graph, so the #497 two-key | ||
| // allowlist below must not then block it. Supply-chain review is the | ||
| // user's responsibility (lockfiles, pinning, external tooling), not a | ||
| // hand-maintained perry allowlist. | ||
| if ctx.compile_packages.iter().any(|p| p == "*") | ||
| && !ctx.allow_compile_packages.iter().any(|p| p == "*") | ||
| { | ||
| ctx.allow_compile_packages.push("*".to_string()); | ||
| } | ||
|
|
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
PERRY_ALLOW_PERRY_FEATURES=0 no longer enforces its fail-closed guarantee for the new auto-compile default.
The pre-existing PERRY_ALLOW_PERRY_FEATURES=0 branch clears ctx.allow_compile_packages and is documented as a "fail-closed CI gate" that "enforces the refusal even when package.json opted in." This new "Universal trust ⇒ universal allow" block runs after that env-var handling and unconditionally re-adds "*" to ctx.allow_compile_packages whenever ctx.compile_packages contains "*".
Because the auto-compile default now inserts "*" into ctx.compile_packages for any project that does not set an explicit perry.compilePackages value (the common case), setting PERRY_ALLOW_PERRY_FEATURES=0 no longer prevents the allowlist from being auto-satisfied. The same applies to an explicit compilePackages: "auto" / true / ["*"]. A CI job relying on this env var to force a hard refusal on compiling arbitrary node_modules code into the binary will silently get a passing, fully-compiled build instead.
Track whether the fail-closed override was explicitly requested and skip the universal-allow injection in that case.
🔒 Proposed fix to respect the fail-closed override
+ let mut compile_packages_force_denied = false;
match std::env::var("PERRY_ALLOW_PERRY_FEATURES") {
Ok(v) if v == "1" || v.eq_ignore_ascii_case("true") => {
if !ctx.allow_native_library.iter().any(|s| s == "*") {
ctx.allow_native_library.push("*".to_string());
}
if !ctx.allow_compile_packages.iter().any(|s| s == "*") {
ctx.allow_compile_packages.push("*".to_string());
}
}
Ok(v) if v == "0" || v.eq_ignore_ascii_case("false") => {
ctx.allow_native_library.clear();
ctx.allow_compile_packages.clear();
+ compile_packages_force_denied = true;
}
_ => {}
}
...
if ctx.compile_packages.iter().any(|p| p == "*")
&& !ctx.allow_compile_packages.iter().any(|p| p == "*")
+ && !compile_packages_force_denied
{
ctx.allow_compile_packages.push("*".to_string());
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Auto-compile default (owner policy): a project that does not pin a | |
| // `perry.compilePackages` value gets its whole reachable node_modules | |
| // graph compiled — perry is a compiler, not a supply-chain gate, so | |
| // "unlisted" is not a hard error, it just compiles (and a dependency that | |
| // genuinely can't compile surfaces as an ordinary compile error). This is | |
| // exactly `compilePackages: "auto"`: inject the universal `"*"`, which the | |
| // expansion just below turns into concrete installed names (native-shimmed | |
| // packages are skipped, so bundled bindings still win). Opt out with an | |
| // explicit list, or `compilePackages: false` / `[]` to compile nothing and | |
| // restore the V8-free gate's "listed only" behavior. | |
| if !compile_packages_explicit { | |
| ctx.compile_packages.insert("*".to_string()); | |
| } | |
| // Universal trust ⇒ universal allow. Whenever routing includes the `"*"` | |
| // wildcard (from this auto default, the `"auto"` scalar, or a literal | |
| // `["*"]`), the host is trusting its whole graph, so the #497 two-key | |
| // allowlist below must not then block it. Supply-chain review is the | |
| // user's responsibility (lockfiles, pinning, external tooling), not a | |
| // hand-maintained perry allowlist. | |
| if ctx.compile_packages.iter().any(|p| p == "*") | |
| && !ctx.allow_compile_packages.iter().any(|p| p == "*") | |
| { | |
| ctx.allow_compile_packages.push("*".to_string()); | |
| } | |
| // Auto-compile default (owner policy): a project that does not pin a | |
| // `perry.compilePackages` value gets its whole reachable node_modules | |
| // graph compiled — perry is a compiler, not a supply-chain gate, so | |
| // "unlisted" is not a hard error, it just compiles (and a dependency that | |
| // genuinely can't compile surfaces as an ordinary compile error). This is | |
| // exactly `compilePackages: "auto"`: inject the universal `"*"`, which the | |
| // expansion just below turns into concrete installed names (native-shimmed | |
| // packages are skipped, so bundled bindings still win). Opt out with an | |
| // explicit list, or `compilePackages: false` / `[]` to compile nothing and | |
| // restore the V8-free gate's "listed only" behavior. | |
| if !compile_packages_explicit { | |
| ctx.compile_packages.insert("*".to_string()); | |
| } | |
| // Universal trust ⇒ universal allow. Whenever routing includes the `"*"` | |
| // wildcard (from this auto default, the `"auto"` scalar, or a literal | |
| // `["*"]`), the host is trusting its whole graph, so the `#497` two-key | |
| // allowlist below must not then block it. Supply-chain review is the | |
| // user's responsibility (lockfiles, pinning, external tooling), not a | |
| // hand-maintained perry allowlist. | |
| if ctx.compile_packages.iter().any(|p| p == "*") | |
| && !ctx.allow_compile_packages.iter().any(|p| p == "*") | |
| && !compile_packages_force_denied | |
| { | |
| ctx.allow_compile_packages.push("*".to_string()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/perry/src/commands/compile/host_config.rs` around lines 682 - 706,
Track whether PERRY_ALLOW_PERRY_FEATURES=0 explicitly activated the fail-closed
override while processing the existing environment handling. Update the
universal-allow injection after the auto-compile default so it does not add "*"
to ctx.allow_compile_packages when that override is active, while preserving
current wildcard behavior otherwise.
| /// How faithful this wrapper is to the npm package's public API. | ||
| /// Governs whether auto-preference over an on-disk `node_modules` | ||
| /// copy is safe-by-construction. Absent in the toml → `Partial`. | ||
| pub compat: BindingCompat, | ||
| } | ||
|
|
||
| impl WellKnownBinding { | ||
| /// Whether this binding is an audited complete drop-in — safe to | ||
| /// auto-prefer over a user's `node_modules` copy without a caveat. | ||
| pub fn is_faithful(&self) -> bool { | ||
| // Aliases inherit the faithfulness of their target (resolved by | ||
| // the caller when needed); the row's own `compat` still applies | ||
| // as a conservative floor. | ||
| matches!(self.compat, BindingCompat::Full) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check alias entries and whether they set their own `compat`.
rg -n -B3 -A10 'alias-of' crates/perry/well_known_bindings.tomlRepository: PerryTS/perry
Length of output: 1135
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== well_known.rs relevant section =="
sed -n '1,180p' crates/perry/src/commands/compile/well_known.rs
echo
echo "== well_known_bindings entries around mysql2/fetch aliases =="
sed -n '420,490p' crates/perry/well_known_bindings.toml
echo
echo "== collect_modules relevant section =="
sed -n '1180,1245p' crates/perry/src/commands/compile/collect_modules.rs
echo
echo "== all alias-of occurrences in well_known_bindings =="
python3 - <<'PY'
from pathlib import Path
p=Path('crates/perry/well_known_bindings.toml')
text=p.read_text()
lines=text.splitlines()
cur=None
compat_vals=[]
for i,l in enumerate(lines,1):
if l.startswith('[') and l.endswith(']'):
cur=l.strip('[]')
if 'alias-of' in l:
print(f"alias row {i}: {cur}")
# print up/down context until next table or 6 lines
start=max(1,i-4); end=min(len(lines),i+8)
for n in range(start,end+1):
print(f"{n}: {lines[n-1]}")
print("---")
print("binding compat defaults/present scan:")
# simple structural scan
PY
rg -n '^\[bindings\.' crates/perry/well_known_bindings.toml | sed -n 'x;p' | rg -n -B0 -A0 'fetch\]|mysql2\]'
rg -n 'alias-of|compat|name:|crate:|crate =|lib =|upstream|compat =' crates/perry/well_known_bindings.toml | sed -n '/fetch\]/,+15p;/mysql2\]/,+15p'
sed -n '/^\[bindings\.fetch\]/,/^\[bindings\.ws\]/p' crates/perry/well_known_bindings.toml | head -n 80
sed -n '/^\[bindings\.mysql2-upstream-mysql\]/,/^\[bindings\.nodemailer\]/p' crates/perry/well_known_bindings.toml | head -n 80Repository: PerryTS/perry
Length of output: 15552
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== BindingCompat occurrences in crates/perry/src =="
rg -n 'BindingCompat|compat|faithful|perry\.compilePackages|PERRY_REQUIRE_FAITHFUL_BINDINGS|=full|="full"|- full|full|partial' crates/perry/src
rg -n 'BindingCompat|compat|is_faithful|alias_of|well_known_bindings' crates/perry/src/crates/perry -g '*.rs' 2>/dev/null || true
echo
echo "== manifest/docs references to compat/faithful =="
rg -n -i 'compat|faithful|partial drop|just works|PERRY_REQUIRE_FAITHFUL_BINDINGS' crates/perry docs 2>/dev/null || trueRepository: PerryTS/perry
Length of output: 50370
Inherit compat through alias_of in is_faithful().
mysql2/promise and fetch are alias rows without compat, so they default to Partial even if their targets are Full. Since collect_modules.rs only checks binding.is_faithful() directly, resolve alias_of as part of faithfulness so aliases are not incorrectly marked partial.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/perry/src/commands/compile/well_known.rs` around lines 88 - 102,
Update WellKnownBinding::is_faithful() to resolve alias_of and inherit the
target binding’s compat status, so aliases such as mysql2/promise and fetch are
faithful when their targets are Full. Preserve the current conservative behavior
for non-alias rows and unresolved or non-Full targets, using the existing
well-known binding lookup mechanism.
What & why
Moves perry toward "it just works" for dependents (Socket Firewall is the driving case) — no hand-maintained
perry.compilePackageslist and nonode_modulesoffloading needed.Landed
Auto-compile is the default. A project that does not pin a
perry.compilePackagesvalue gets its whole reachablenode_modulesgraph compiled automatically (host_config injects the universal"*"and auto-satisfies theallow.compilePackagestwo-key check). Perry is a compiler, not a supply-chain gate — "this package is not on a list" is no longer a hard error; a dependency that genuinely can't compile surfaces as an ordinary compile error. Native-shimmed packages still resolve to their bundled bindings (the Compile a standard Express app: full blocker inventory (codegen emits undefined @perry_closure_* globals → linked-but-crashing binary) #3527 wildcard expansion skips them). Opt out with an explicit array, orcompilePackages: false/[]to compile nothing and re-arm the V8-free gate.compilePackages: "auto"/allow.compilePackages: true— explicit spelling of that default (sugar for["*"]).Faithfulness marker. Well-known bindings gain
compat = "full" | "partial"(absent ⇒partial, conservative). Auto-preferring apartialbinding over an installednode_modulescopy now prints a one-line transparency note;PERRY_REQUIRE_FAITHFUL_BINDINGS=1turns that into a refusal. Default off ⇒ existing builds byte-identical. Audited:dotenv/nanoid/slugify/uuid→full;undici/node-forge/lru-cache→partial(documented subsets).Design note + rationale (incl. the resolved auto-compile-default policy and the firewall cleanup this unblocks):
docs/src/native-libraries/zero-config-and-faithfulness.md.Context / finding
undici/node-forge/iovalkey/dotenvwere added toNATIVE_MODULESin #7032/#7033, so on currentmainall four already resolve to their bindings even with anode_modulescopy present — the "binding gets shadowed" problem is already gone. This PR adds the safety/transparency layer around that (thecompatmarker) and the auto-compile default that lets a dependent drop itscompilePackageslist entirely.Verification
perrybin unit tests pass; newwell_known::compat_*+ audit tests added.partialnote fires (andPERRY_REQUIRE_FAITHFUL_BINDINGS=1refuses) forundici/node-forge/iovalkey;fulldotenvstays silent;compilePackages: []restores the gate.Notes
lru-cacheispartialon this base; its faithful port (fix(lru-cache): faithful JS-value keys/values, GC rooting, TTL + updateAgeOnGet #7136) isn't inmainyet — once it lands, its covered option surface can move tofull.perry-codegenmanifest_consistencyhas 3 failures on cleanmain(an.upstreamsubtable scan quirk +iovalkeymissing an API_MANIFEST entry).Summary by CodeRabbit
New Features
compilePackagesoptions:true,"auto","all", arrays, or explicit opt-outs.PERRY_REQUIRE_FAITHFUL_BINDINGS=1to reject partial bindings.Transparency
Documentation