Skip to content

refactor(stdlib): remove nanoid native binding - #10693

Closed
proggeramlug wants to merge 2 commits into
mainfrom
wip/remove-nanoid-binding
Closed

proggeramlug wants to merge 2 commits into
mainfrom
wip/remove-nanoid-binding

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Removes the native nanoid binding so import { nanoid, customAlphabet } from "nanoid" (no perry.compilePackages entry) resolves to the real npm
package, compiled from source, per the owner's decision to stop shipping
hand-written Rust reimplementations of npm packages.

The bug

customAlphabet(alphabet, size) is documented to return a generator
function
— call it repeatedly to mint ids. Native customAlphabet instead
returns the generated id string directly (the FFI function
js_nanoid_custom's own doc comment admits it: "For simplicity, we combine
this into one call: nanoid.custom(alphabet, size)"
), so the only documented
usage — const gen = customAlphabet(...); gen(); — crashes with
TypeError: value is not a function.

What was found and removed

Per #10678 (duplicate extern "C" exports across perry-ext-*/perry-stdlib
pairs), this binding existed twice:

  • crates/perry-ext-nanoid/ — the governance-tracked binding crate, wired
    into well_known_bindings.toml's [bindings.nanoid].
  • crates/perry-stdlib/src/nanoid.rs (62 lines) — a second, independent
    implementation behind the bundled-nanoid feature (default-on via the
    ids umbrella), exporting the same js_nanoid/js_nanoid_sized/
    js_nanoid_custom symbols.

One subtlety: customAlphabet is declared to codegen
(runtime_decls/stdlib_ffi/data_stores.rs's js_nanoid_custom) but has
no call-site wiring anywhere in perry-codegen — no NativeModSig row,
no lower_call special case. Only plain nanoid(size) had a dispatch row
(native_table/utils_crypto.rs, routing to js_nanoid_sized). This is
consistent with the bug report: customAlphabet isn't a first-class
compiled call at all.

Removed both crates, the 1-entry NativeModSig dispatch row
(nanoid.nanoidjs_nanoid_sized), the 2 js_nanoid* FFI declarations in
runtime_decls/stdlib_ffi/data_stores.rs, the well_known_bindings.toml
entry, the "nanoid" NATIVE_MODULES entry and its manifest row, the
bundled-nanoid stdlib feature, and the 2 Android stub exports.

The ids umbrella is now empty. It was retargeted to
["bundled-nanoid"] when #10701 removed uuid (train 225); removing
bundled-nanoid leaves it with no members, so it is kept as ids = []
an intentionally harmless no-op that preserves --features ids for
existing callers rather than breaking them. The three comments that still
described the two-member split (perry-stdlib/Cargo.toml,
perry-stdlib/src/lib.rs, stdlib_features.rs) are rewritten to say so.
perry-stdlib's uuid crate dependency is untouched: #10701 already
made it non-optional because crypto/random.rs calls it unconditionally.

Why shipped_unproven_bindings_are_partial is deleted, not emptied

This test asserted that the hand-written wrapper bindings we shipped without
proving upstream parity stay marked compat = "partial". Its subject
population was exactly those wrappers: uuid (removed in #10701), dotenv
(#10691) and nanoid (this PR). With the last one gone the loop has no
names left to iterate.

Leaving it as for name in [] { ... } would be worse than removing it. It
would compile, run, assert nothing and report green forever — a test whose
subject never executes, which is failure mode #4 in CLAUDE.md's "four ways a
gate can be unable to fail". A future reader would reasonably believe the
posture was still covered.

Coverage is not lost. shipped_subset_bindings_are_partial is a separate
test and still asserts the same property for the four documented-subset
bindings that remain: undici, node-forge, lru-cache and qs. If a new
unproven wrapper is ever shipped, the right move is a new test with a real
subject, not the resurrection of an empty loop.

Already-known, not touched

test-files/test_parity_nanoid.ts (the exact customAlphabet(...)();
reproduction) is already excluded from the parity gate
test-parity/known_failures.json classifies it ci-env: Node's own oracle
run fails with ERR_MODULE_NOT_FOUND in CI because nanoid was never added
to the repo's root package.json/package-lock.json, so npm ci never
installs it (confirmed: no nanoid entry in the root package.json).
Before this PR, Perry's side of this fixture worked anyway because the
bare-specifier import bypassed node_modules entirely via NATIVE_MODULES.
After this PR, the fixture needs a real installed nanoid on both
sides, which the repo doesn't provision — provisioning it (adding a real npm
dependency to the root package.json) is out of scope for a binding-removal
PR. Not a regression: the test was already excluded before this PR for an
unrelated, already-tracked reason (#8271), and remains excluded after it.

Acceptance test: customAlphabet used as a generator, no compilePackages entry

{ "dependencies": { "nanoid": "^5.0.7" } }

Installed nanoid 5.1.16 — the version this ^5.0.7 range resolves to. Note the registry entry this PR deletes pinned upstream version = "6.0.0", a different major; checking against that one would have proved nothing about the package users actually get.

import { nanoid, customAlphabet } from "nanoid";
const gen = customAlphabet("0123456789", 6);
console.log(typeof gen === "function"); // was the crash: gen was a string
const id = gen();

Built on perrymaster (--profile perry-dev). Ran the binary and diffed
against node --experimental-strip-types (Node 26.5.1): byte-for-byte
identical
customAlphabet(...) now returns an actual function, and
calling it repeatedly succeeds, matching Node exactly.

Verification

Re-measured on the rebase onto v0.5.1606 (train 227), macOS arm64,
--release. Every absolute count below was re-derived from its own
script against the resolved tree
, never carried across the rebase:

  • cargo build --release -p perry -p perry-runtime-static -p perry-stdlib-static in one invocation: clean. .a mtimes moved
    (libperry_runtime.a 00:08:49, libperry_stdlib.a 00:10:59, both after
    the last source edit at 00:06:32), and nm shows zero js_nanoid*
    symbols
    in either archive or in the acceptance binary.
  • cargo test --release -p perry --bin perry -- well_known: 25/25
    passing
    (was 27 before train 227; 227's dotenv removal deleted
    dotenv_is_registered, and this PR deletes
    shipped_unproven_bindings_are_partial).
  • python3 scripts/workspace_architecture.py --check: OK —
    77 → 76 members, externalize 28 → 27, keep 44. Worth flagging: git
    auto-merged workspace-architecture.json with no conflict and left
    the stale 77/28, because the counts live on different JSON lines from
    the crate entry the removal deletes. The script is what caught it.
  • python3 scripts/native_result_ledger.py: OK, 376 rows / 326
    providers unchanged
    — confirmed by running it, not assumed (nanoid's
    one dispatch row was NR_STR, not a ledger-tracked kind).
  • python3 scripts/unrooted_local_shape.py --check: OK, baseline 578
    unchanged (nanoid never had an entry).
  • python3 scripts/string_payload_access_inventory.py: OK,
    perry-stdlib inline-offset 37 unchanged (nanoid.rs had no sites).
  • python3 scripts/binding_governance.py --check: OK (34 extension
    crates). node scripts/binding_pins.mjs --check: OK (32 pinned).
  • API docs regenerated by running the built binary's
    --print-api-manifest, not by editing the headers:
    perry.d.ts 2065 entries/131 modules → 2064/130,
    reference.md 3007/133 → 3006/132. Both header lines conflicted
    with a value that was wrong on both sides.
  • Cargo.lock regenerated with cargo metadata, never hand-merged;
    the diff is exactly the three nanoid stanzas.
  • cargo fmt --all -- --check: clean. This is the gate that matters here —
    two conflict sites (entries/part_1.rs, native_table/utils_crypto.rs)
    have adjacent blocks ending in an identical ),/},, which git folds
    into the common region; an earlier resolution of this branch left a
    dangling method_sig( with no conflict markers and only cargo fmt
    rejected it.
  • scripts/run_lint_gates.sh (SKIP_COMPILE_GATES=1): 78 of 79 ok.
    The single failure is benchmarks/ci_public_baseline_check.py, which is
    known-red on main; this PR touches neither file in its HARNESS_PATHS
    (public-baseline-config.json, honest_bench/results/expected.json),
    so it is pre-existing.
  • Real nanoid round-trip: see above, byte-for-byte identical to Node.

Not run / out of scope

  • Compile tier of run_lint_gates.sh (known-red on Linux per this
    campaign's contract).
  • Full gap suite (host stalls under auto-optimize per contract); ran the
    targeted registry/consistency tests plus the direct acceptance test
    instead.
  • No version bump / CLAUDE.md edit — per this campaign's convention, the
    maintainer bumps at merge time.

Summary by CodeRabbit

  • Bug Fixes

    • nanoid now compiles from the upstream npm package, matching Node.js behavior.
    • customAlphabet now works as a callable generator function.
  • Documentation

    • Updated API references and generated declarations for the current nanoid integration.
  • Removals

    • Removed the bundled native nanoid implementation and runtime integration.
    • Removed nanoid from native binding and workspace inventories.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6f4e75c7-2d8f-4ff4-9780-324d9117cf5b

📥 Commits

Reviewing files that changed from the base of the PR and between 8cf9cb1 and 9a400cc.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • Cargo.toml
  • crates/perry-api-manifest/src/entries.rs
  • crates/perry-api-manifest/src/entries/part_1.rs
  • crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs
  • crates/perry-stdlib/Cargo.toml
  • crates/perry-stdlib/src/lib.rs
  • crates/perry-ui-android/src/stdlib_stubs.rs
  • crates/perry/src/commands/compile/well_known.rs
  • crates/perry/well_known_bindings.toml
  • docs/api/perry.d.ts
  • docs/src/api/reference.md
  • docs/src/native-libraries/governance.md
  • workspace-architecture.json
💤 Files with no reviewable changes (8)
  • Cargo.toml
  • crates/perry/well_known_bindings.toml
  • crates/perry-api-manifest/src/entries.rs
  • crates/perry-codegen/src/lower_call/native_table/utils_crypto.rs
  • crates/perry-ui-android/src/stdlib_stubs.rs
  • docs/src/native-libraries/governance.md
  • crates/perry-api-manifest/src/entries/part_1.rs
  • crates/perry/src/commands/compile/well_known.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/src/api/reference.md

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The native NanoID binding and duplicate runtime implementations were removed. Manifest, code-generation, feature, registry, workspace, Android stub, and documentation references were updated. NanoID imports now compile from npm package source.

Changes

NanoID binding removal

Layer / File(s) Summary
Remove NanoID implementations
Cargo.toml, crates/perry-ext-nanoid/*, crates/perry-stdlib/*, crates/perry-ui-android/src/stdlib_stubs.rs
The native extension crate, standard-library implementation, optional dependency, feature wiring, exports, Android stubs, and workspace references were removed.
Remove NanoID dispatch wiring
crates/perry-api-manifest/*, crates/perry-codegen/*, crates/perry/src/commands/*, crates/perry/well_known_bindings.toml
Native module registration, call dispatch, FFI declarations, feature mapping, binding metadata, and compatibility checks no longer reference NanoID.
Update generated metadata and documentation
docs/api/perry.d.ts, docs/src/api/reference.md, docs/src/native-libraries/governance.md, workspace-architecture.json, changelog.d/*
Generated API declarations, API reference totals and entries, governance inventory, workspace architecture counts, and the changelog now describe the removal.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: removing the native nanoid binding.
Description check ✅ Passed The description is detailed and covers the change, rationale, related issues, acceptance test, verification results, and out-of-scope items. It does not use every template heading, but the required in…
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 4 files. (3 skipped: 3 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

proggeramlug pushed a commit that referenced this pull request Sep 19, 2026
Removes the native uuid binding so `import { v4, parse, stringify, NIL }
from "uuid"` (no perry.compilePackages entry) resolves to the real npm
package compiled from source, per the owner's decision to stop shipping
hand-written Rust reimplementations of npm packages.

The native binding is missing `parse`/`stringify` entirely (a
`parse`/`stringify` roundtrip throws: `bytes` comes back `undefined`),
and `NIL` reads as `undefined` (js_uuid_nil exists in the deleted source
but was never wired into either NativeModSig dispatch table or the API
manifest, so property access on the uuid module namespace fell through
to undefined). v1/v3/v4/v5/validate/version were correct in both.

Per #10678 (duplicate extern "C" exports across perry-ext-*/perry-stdlib
pairs), this binding existed twice: crates/perry-ext-uuid/ (the
governance-tracked binding crate) and crates/perry-stdlib/src/uuid.rs (a
second, independent implementation behind the now-removed bundled-uuid
feature). Both are deleted, along with the 7-entry NativeModSig dispatch
block in native_table/utils_crypto.rs, the well_known_bindings.toml
entry, the "uuid" NATIVE_MODULES entry and its 7 manifest rows, and the
6 Android stub exports.

crypto/random.rs entanglement: crypto.randomUUID()/randomUUID({v7:true})
and nodemailer's message-id generation both call the `uuid` Cargo crate
(uuid::Uuid::new_v4()/now_v7()) directly and unconditionally — neither
site has a cfg(feature) gate. The `uuid` crate dependency in
perry-stdlib/Cargo.toml was therefore never actually optional in
practice even though it was declared `optional = true` behind the
bundled-uuid npm-binding feature; removing that feature without also
dropping `optional = true` would have broken the build the moment
bundled-uuid stopped being enabled. Made `uuid` a required (non-optional)
dependency and retargeted the `ids` feature umbrella to
`["bundled-nanoid"]`. crypto.randomUUID()/randomUUID({v7:true}) and
node:crypto's randomBytes are unaffected — verified below.

Retargeted the one dts-shape regression test that used uuid.v4() as its
zero-arg-module-function fixture (perry-api-manifest's
dts_uuid_v4_has_no_args) to perry/gc.minor(), which is unrelated to any
binding-removal churn.

Built on perrymaster (--profile perry-dev). A real `npm install uuid`
project (no perry.compilePackages entry) exercising v1/v4/v5/v3,
validate/version, a parse/stringify roundtrip, and NIL, diffed against
`node --experimental-strip-types` (Node 26.5.1): byte-for-byte identical,
including the deterministic v5/v3 (name+namespace) values. Confirmed
against a pristine origin/main (8df83f8) baseline build that the same
program crashes there: `NIL: undefined`, `parse instanceof Uint8Array:
false`, then `TypeError: Cannot read properties of undefined (reading
'length')` on the roundtrip — reproducing the reported bug exactly.
crypto.randomUUID() and node:crypto's randomBytes/randomUUID were
re-checked against the fix and still work (both call the `uuid` crate
directly, unaffected by the binding removal).

- cargo build --profile perry-dev -p perry -p perry-runtime-static -p
  perry-stdlib-static: clean; confirmed .a mtimes moved.
- cargo check --workspace --all-targets (host-compatible exclusion set
  via workspace_architecture.py --print-excluded-scope) under
  -D warnings: clean.
- cargo test -p perry-api-manifest: 39+4 passing (after retargeting the
  uuid-fixture test).
- cargo test -p perry-codegen --test manifest_consistency: 5/5 passing.
- cargo test -p perry --bin perry -- well_known: 27/27 passing.
- cargo test -p perry-hir: full suite passing (test_lower_native_module_
  registration uses "uuid" only as synthetic example data for a generic
  register/lookup mechanism — unaffected by the registry removal).
- python3 scripts/binding_governance.py --check: OK (39 extension
  crates).
- node scripts/binding_pins.mjs --check: OK (37 pinned, lock-step
  holds).
- python3 scripts/workspace_architecture.py --check: OK.
- python3 scripts/native_result_ledger.py: OK, 371 rows/322 providers
  unchanged (none of uuid's dispatch rows used a ledger-tracked NR_*
  kind).
- python3 scripts/string_payload_access_inventory.py --write-baseline:
  perry-stdlib inline-offset 40 -> 38 (uuid.rs's own 2 sites).
- Regenerated docs/api/perry.d.ts + docs/src/api/reference.md
  (perry --print-api-manifest) and docs/src/native-libraries/
  governance.md (binding_governance.py --table) from the fixed manifest.
- cargo fmt --all -- --check: clean.
- scripts/run_lint_gates.sh (SKIP_COMPILE_GATES=1): 76 of 77 passed; the
  one failure (Public benchmark evidence freshness) is the pre-existing,
  known-red-on-every-PR gate per this campaign's contract.

- Compile tier of run_lint_gates.sh (known-red on Linux per this
  campaign's contract).
- Full gap suite (host stalls under auto-optimize per contract).
- test-files/test_parity_uuid.ts is already excluded from the parity
  gate (test-parity/known_failures.json, "ci-env": Node's own oracle run
  fails ERR_MODULE_NOT_FOUND because uuid was never added to the repo's
  root package.json/package-lock.json — the same pre-existing gap
  documented for nanoid, #8271). Its `@covers` comment now points at a
  deleted file (crates/perry-stdlib/src/uuid.rs); leaving it untouched,
  matching how the sibling nanoid PR (#10693) left its own equivalent
  parity fixture alone.
- No version bump / CLAUDE.md edit — per this campaign's convention, the
  maintainer bumps at merge time.
- crates/perry-ui-android/src/stdlib_stubs.rs was edited to remove the
  matching 6 js_uuid_* stub exports (following the established pattern
  from the sibling removals) but not build-verified — this host has no
  Android NDK and the package is excluded from the host-compatible
  check scope.
proggeramlug pushed a commit that referenced this pull request Sep 19, 2026
Removes the native uuid binding so `import { v4, parse, stringify, NIL }
from "uuid"` (no perry.compilePackages entry) resolves to the real npm
package compiled from source, per the owner's decision to stop shipping
hand-written Rust reimplementations of npm packages.

The native binding is missing `parse`/`stringify` entirely (a
`parse`/`stringify` roundtrip throws: `bytes` comes back `undefined`),
and `NIL` reads as `undefined` (js_uuid_nil exists in the deleted source
but was never wired into either NativeModSig dispatch table or the API
manifest, so property access on the uuid module namespace fell through
to undefined). v1/v3/v4/v5/validate/version were correct in both.

Per #10678 (duplicate extern "C" exports across perry-ext-*/perry-stdlib
pairs), this binding existed twice: crates/perry-ext-uuid/ (the
governance-tracked binding crate) and crates/perry-stdlib/src/uuid.rs (a
second, independent implementation behind the now-removed bundled-uuid
feature). Both are deleted, along with the 7-entry NativeModSig dispatch
block in native_table/utils_crypto.rs, the well_known_bindings.toml
entry, the "uuid" NATIVE_MODULES entry and its 7 manifest rows, and the
6 Android stub exports.

crypto/random.rs entanglement: crypto.randomUUID()/randomUUID({v7:true})
and nodemailer's message-id generation both call the `uuid` Cargo crate
(uuid::Uuid::new_v4()/now_v7()) directly and unconditionally — neither
site has a cfg(feature) gate. The `uuid` crate dependency in
perry-stdlib/Cargo.toml was therefore never actually optional in
practice even though it was declared `optional = true` behind the
bundled-uuid npm-binding feature; removing that feature without also
dropping `optional = true` would have broken the build the moment
bundled-uuid stopped being enabled. Made `uuid` a required (non-optional)
dependency and retargeted the `ids` feature umbrella to
`["bundled-nanoid"]`. crypto.randomUUID()/randomUUID({v7:true}) and
node:crypto's randomBytes are unaffected — verified below.

Retargeted the one dts-shape regression test that used uuid.v4() as its
zero-arg-module-function fixture (perry-api-manifest's
dts_uuid_v4_has_no_args) to perry/gc.minor(), which is unrelated to any
binding-removal churn.

Built on perrymaster (--profile perry-dev). A real `npm install uuid`
project (no perry.compilePackages entry) exercising v1/v4/v5/v3,
validate/version, a parse/stringify roundtrip, and NIL, diffed against
`node --experimental-strip-types` (Node 26.5.1): byte-for-byte identical,
including the deterministic v5/v3 (name+namespace) values. Confirmed
against a pristine origin/main (8df83f8) baseline build that the same
program crashes there: `NIL: undefined`, `parse instanceof Uint8Array:
false`, then `TypeError: Cannot read properties of undefined (reading
'length')` on the roundtrip — reproducing the reported bug exactly.
crypto.randomUUID() and node:crypto's randomBytes/randomUUID were
re-checked against the fix and still work (both call the `uuid` crate
directly, unaffected by the binding removal).

- cargo build --profile perry-dev -p perry -p perry-runtime-static -p
  perry-stdlib-static: clean; confirmed .a mtimes moved.
- cargo check --workspace --all-targets (host-compatible exclusion set
  via workspace_architecture.py --print-excluded-scope) under
  -D warnings: clean.
- cargo test -p perry-api-manifest: 39+4 passing (after retargeting the
  uuid-fixture test).
- cargo test -p perry-codegen --test manifest_consistency: 5/5 passing.
- cargo test -p perry --bin perry -- well_known: 27/27 passing.
- cargo test -p perry-hir: full suite passing (test_lower_native_module_
  registration uses "uuid" only as synthetic example data for a generic
  register/lookup mechanism — unaffected by the registry removal).
- python3 scripts/binding_governance.py --check: OK (39 extension
  crates).
- node scripts/binding_pins.mjs --check: OK (37 pinned, lock-step
  holds).
- python3 scripts/workspace_architecture.py --check: OK.
- python3 scripts/native_result_ledger.py: OK, 371 rows/322 providers
  unchanged (none of uuid's dispatch rows used a ledger-tracked NR_*
  kind).
- python3 scripts/string_payload_access_inventory.py --write-baseline:
  perry-stdlib inline-offset 40 -> 38 (uuid.rs's own 2 sites).
- Regenerated docs/api/perry.d.ts + docs/src/api/reference.md
  (perry --print-api-manifest) and docs/src/native-libraries/
  governance.md (binding_governance.py --table) from the fixed manifest.
- cargo fmt --all -- --check: clean.
- scripts/run_lint_gates.sh (SKIP_COMPILE_GATES=1): 76 of 77 passed; the
  one failure (Public benchmark evidence freshness) is the pre-existing,
  known-red-on-every-PR gate per this campaign's contract.

- Compile tier of run_lint_gates.sh (known-red on Linux per this
  campaign's contract).
- Full gap suite (host stalls under auto-optimize per contract).
- test-files/test_parity_uuid.ts is already excluded from the parity
  gate (test-parity/known_failures.json, "ci-env": Node's own oracle run
  fails ERR_MODULE_NOT_FOUND because uuid was never added to the repo's
  root package.json/package-lock.json — the same pre-existing gap
  documented for nanoid, #8271). Its `@covers` comment now points at a
  deleted file (crates/perry-stdlib/src/uuid.rs); leaving it untouched,
  matching how the sibling nanoid PR (#10693) left its own equivalent
  parity fixture alone.
- No version bump / CLAUDE.md edit — per this campaign's convention, the
  maintainer bumps at merge time.
- crates/perry-ui-android/src/stdlib_stubs.rs was edited to remove the
  matching 6 js_uuid_* stub exports (following the established pattern
  from the sibling removals) but not build-verified — this host has no
  Android NDK and the package is excluded from the host-compatible
  check scope.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Heads-up before this is queued: this PR's recorded workspace baseline is stale and will fail workspace_architecture.py --check on rebase.

It records workspace_members 82 / externalize 32 / keep 45. Main (023dc0b653) is at 80 / 31 / 44, so a removal landing on it must produce 79 / 30 / 44 — not 82. Five sibling removal PRs carry the identical 82/32/45, which is also mutually impossible: six different crates cannot all produce the same transition.

Full table and reasoning in #10739. The short version, for whoever rebases this:

  • Recompute from the resolved tree and let workspace_architecture.py --check --print-summary reproduce the number independently. Do not derive it from 83 by arithmetic, and do not copy a sibling's figure or one quoted in a comment — they all go stale as the queue advances.
  • The same hazard applies to scripts/native_result_ledger.tsv, scripts/string_payload_access_baseline.txt and the governance/pins tables. Regenerate with their own scripts rather than resolving by hand.
  • MERGEABLE will not catch this. The counts sit on different JSON lines from the deleted crate entry, so git auto-merges both sides without a conflict — chore(bindings): remove axios native binding, compile real axios from source #10679 rebased onto current main today, came out MERGEABLE, and still carried 82/32/45.

Also relevant to the acceptance run whenever it happens: #10735 is live on main — require.main === module is true in every compiled CommonJS module, so any dependency with a CLI entry guard runs its CLI branch when merely imported. A fix is in flight. If acceptance fails in a way that looks like the package misbehaving at import time, test a dependency-free fixture that never mentions the package before attributing it to this removal.

proggeramlug pushed a commit that referenced this pull request Sep 19, 2026
Removes the native uuid binding so `import { v4, parse, stringify, NIL }
from "uuid"` (no perry.compilePackages entry) resolves to the real npm
package compiled from source, per the owner's decision to stop shipping
hand-written Rust reimplementations of npm packages.

The native binding is missing `parse`/`stringify` entirely (a
`parse`/`stringify` roundtrip throws: `bytes` comes back `undefined`),
and `NIL` reads as `undefined` (js_uuid_nil exists in the deleted source
but was never wired into either NativeModSig dispatch table or the API
manifest, so property access on the uuid module namespace fell through
to undefined). v1/v3/v4/v5/validate/version were correct in both.

Per #10678 (duplicate extern "C" exports across perry-ext-*/perry-stdlib
pairs), this binding existed twice: crates/perry-ext-uuid/ (the
governance-tracked binding crate) and crates/perry-stdlib/src/uuid.rs (a
second, independent implementation behind the now-removed bundled-uuid
feature). Both are deleted, along with the 7-entry NativeModSig dispatch
block in native_table/utils_crypto.rs, the well_known_bindings.toml
entry, the "uuid" NATIVE_MODULES entry and its 7 manifest rows, and the
6 Android stub exports.

crypto/random.rs entanglement: crypto.randomUUID()/randomUUID({v7:true})
and nodemailer's message-id generation both call the `uuid` Cargo crate
(uuid::Uuid::new_v4()/now_v7()) directly and unconditionally — neither
site has a cfg(feature) gate. The `uuid` crate dependency in
perry-stdlib/Cargo.toml was therefore never actually optional in
practice even though it was declared `optional = true` behind the
bundled-uuid npm-binding feature; removing that feature without also
dropping `optional = true` would have broken the build the moment
bundled-uuid stopped being enabled. Made `uuid` a required (non-optional)
dependency and retargeted the `ids` feature umbrella to
`["bundled-nanoid"]`. crypto.randomUUID()/randomUUID({v7:true}) and
node:crypto's randomBytes are unaffected — verified below.

Retargeted the one dts-shape regression test that used uuid.v4() as its
zero-arg-module-function fixture (perry-api-manifest's
dts_uuid_v4_has_no_args) to perry/gc.minor(), which is unrelated to any
binding-removal churn.

Built on perrymaster (--profile perry-dev). A real `npm install uuid`
project (no perry.compilePackages entry) exercising v1/v4/v5/v3,
validate/version, a parse/stringify roundtrip, and NIL, diffed against
`node --experimental-strip-types` (Node 26.5.1): byte-for-byte identical,
including the deterministic v5/v3 (name+namespace) values. Confirmed
against a pristine origin/main (8df83f8) baseline build that the same
program crashes there: `NIL: undefined`, `parse instanceof Uint8Array:
false`, then `TypeError: Cannot read properties of undefined (reading
'length')` on the roundtrip — reproducing the reported bug exactly.
crypto.randomUUID() and node:crypto's randomBytes/randomUUID were
re-checked against the fix and still work (both call the `uuid` crate
directly, unaffected by the binding removal).

- cargo build --profile perry-dev -p perry -p perry-runtime-static -p
  perry-stdlib-static: clean; confirmed .a mtimes moved.
- cargo check --workspace --all-targets (host-compatible exclusion set
  via workspace_architecture.py --print-excluded-scope) under
  -D warnings: clean.
- cargo test -p perry-api-manifest: 39+4 passing (after retargeting the
  uuid-fixture test).
- cargo test -p perry-codegen --test manifest_consistency: 5/5 passing.
- cargo test -p perry --bin perry -- well_known: 27/27 passing.
- cargo test -p perry-hir: full suite passing (test_lower_native_module_
  registration uses "uuid" only as synthetic example data for a generic
  register/lookup mechanism — unaffected by the registry removal).
- python3 scripts/binding_governance.py --check: OK (39 extension
  crates).
- node scripts/binding_pins.mjs --check: OK (37 pinned, lock-step
  holds).
- python3 scripts/workspace_architecture.py --check: OK.
- python3 scripts/native_result_ledger.py: OK, 371 rows/322 providers
  unchanged (none of uuid's dispatch rows used a ledger-tracked NR_*
  kind).
- python3 scripts/string_payload_access_inventory.py --write-baseline:
  perry-stdlib inline-offset 40 -> 38 (uuid.rs's own 2 sites).
- Regenerated docs/api/perry.d.ts + docs/src/api/reference.md
  (perry --print-api-manifest) and docs/src/native-libraries/
  governance.md (binding_governance.py --table) from the fixed manifest.
- cargo fmt --all -- --check: clean.
- scripts/run_lint_gates.sh (SKIP_COMPILE_GATES=1): 76 of 77 passed; the
  one failure (Public benchmark evidence freshness) is the pre-existing,
  known-red-on-every-PR gate per this campaign's contract.

- Compile tier of run_lint_gates.sh (known-red on Linux per this
  campaign's contract).
- Full gap suite (host stalls under auto-optimize per contract).
- test-files/test_parity_uuid.ts is already excluded from the parity
  gate (test-parity/known_failures.json, "ci-env": Node's own oracle run
  fails ERR_MODULE_NOT_FOUND because uuid was never added to the repo's
  root package.json/package-lock.json — the same pre-existing gap
  documented for nanoid, #8271). Its `@covers` comment now points at a
  deleted file (crates/perry-stdlib/src/uuid.rs); leaving it untouched,
  matching how the sibling nanoid PR (#10693) left its own equivalent
  parity fixture alone.
- No version bump / CLAUDE.md edit — per this campaign's convention, the
  maintainer bumps at merge time.
- crates/perry-ui-android/src/stdlib_stubs.rs was edited to remove the
  matching 6 js_uuid_* stub exports (following the established pattern
  from the sibling removals) but not build-verified — this host has no
  Android NDK and the package is excluded from the host-compatible
  check scope.
@proggeramlug
proggeramlug force-pushed the wip/remove-nanoid-binding branch from 7adf46f to 8cf9cb1 Compare September 19, 2026 21:52
Ralph Küpper added 2 commits September 20, 2026 00:18
customAlphabet(alphabet, size) is documented to return a generator
function; native customAlphabet instead returns the generated id string
directly (js_nanoid_custom's own doc comment: "For simplicity, we combine
this into one call"), so the only documented usage --
const gen = customAlphabet(...); gen(); -- crashes with
TypeError: value is not a function.

Per #10678 (duplicate extern "C" exports across perry-ext-*/perry-stdlib
pairs), this binding existed twice: crates/perry-ext-nanoid/ (governance-
tracked) and crates/perry-stdlib/src/nanoid.rs (a second, independent
implementation behind the default-on bundled-nanoid feature, exporting the
same js_nanoid/js_nanoid_sized/js_nanoid_custom symbols).

customAlphabet is declared to codegen (data_stores.rs's js_nanoid_custom)
but has no call-site wiring anywhere in perry-codegen -- no NativeModSig
row, no lower_call special case. Only plain nanoid(size) had a dispatch row
(native_table/utils_crypto.rs, routing to js_nanoid_sized), consistent with
customAlphabet not being a first-class compiled call at all.

Removed both crates, the 1-entry NativeModSig dispatch row, the 2
js_nanoid* FFI declarations, the well_known_bindings.toml entry, the
"nanoid" NATIVE_MODULES entry + manifest row, the bundled-nanoid stdlib
feature, and 2 Android stub exports.

DELETED the shipped_unproven_bindings_are_partial test rather than emptying
it. Its subject population was exactly the hand-written wrappers shipped
without proven upstream parity: uuid (#10701), dotenv (#10691, landed in
train 227) and nanoid (here). With the last one gone the array would read
`for name in []` -- a test that compiles, runs, asserts nothing and reports
green forever, which is failure mode #4 in CLAUDE.md's "four ways a gate can
be unable to fail". Coverage is not lost: shipped_subset_bindings_are_partial
is a separate test and still asserts the same property for undici,
node-forge, lru-cache and qs. Verified the deletion orphans nothing --
the test module is `use super::*`, lookup_well_known has 9 other callers,
BindingCompat::Partial has 5 other uses, and nothing in the tree keys on
the test's name.

The "ids" umbrella is now EMPTY. It was retargeted to ["bundled-nanoid"]
when #10701 removed uuid (train 225); removing bundled-nanoid leaves it
with no members, so it is kept as `ids = []` -- an intentionally harmless
no-op that preserves `--features ids` for existing callers rather than
breaking them. The stale comments that described the two-member split
(perry-stdlib/Cargo.toml, perry-stdlib/src/lib.rs, stdlib_features.rs) are
rewritten to say so. perry-stdlib's `uuid` crate dependency is untouched:
#10701 already made it non-optional because crypto/random.rs calls it
unconditionally.

test-files/test_parity_nanoid.ts (the exact customAlphabet(...)();
reproduction) is already excluded from the parity gate --
known_failures.json classifies it "ci-env": Node's own oracle fails with
ERR_MODULE_NOT_FOUND in CI because nanoid was never added to the repo's
root package.json, so npm ci never installs it. Not touched -- provisioning
a real npm dependency in the root package.json is out of scope for a
binding-removal PR, and the test remains excluded before and after this
change for the same underlying reason.

Every absolute count re-derived from its own script against the resolved
tree, never carried across the rebase and never hand-merged:
workspace-architecture.json 77 -> 76 members, externalize 28 -> 27, keep 44
(scripts/workspace_architecture.py; git auto-merged this file with NO
conflict and left the stale 77/28, which the script caught);
docs/api/perry.d.ts 2065 entries/131 modules -> 2064/130 and
docs/src/api/reference.md 3007/133 -> 3006/132, both regenerated by running
the built binary's --print-api-manifest rather than editing the headers;
Cargo.lock regenerated with `cargo metadata`. native_result_ledger
(376 rows/326 providers), unrooted_local_shape (578) and
string_payload_access (perry-stdlib inline-offset 37) confirmed unchanged
by running them, not by assuming.

Rebased onto v0.5.1606 (train 227). Conflicts were all of the form "main
deleted dotenv, this branch deleted nanoid, in one hunk"; every one was
resolved to main's current content minus nanoid's own entries, which is
neither side.
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Landed in merge train 228 (#10764), released as v0.5.1607 — main is now 2f9dc8e692.

Closing rather than merging is how trains work here: both PRs were cherry-picked onto one tree, validated together, and landed under the train's own commit, so GitHub cannot mark this one merged even though your change is on main.

Counts re-derived on the assembled tree, not carried: workspace 76/27/44, ledger 376/326, unrooted 578. Worth recording that workspace-architecture.json came through the rebase as M rather than UU — git merged it with zero conflict markers while preserving a stale value, and only workspace_architecture.py --check caught it.

Validation: all nine cheap gates, cargo check --workspace --all-targets under -D warnings, the release build of all five pinned artifacts, every unit suite, and a 6-area gap sweep with zero unexplained regressions, each area asserted to have run a non-zero number of tests, at PERRY_RUN_TIMEOUT=30.

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