Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .context/decisions/archive-member-executables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Executable archive members are declared beside the extraction

**Decided 2026-11-04, for the next npm minor release after 1.4.0.**

`assetArchives[].executables` lists paths relative to that entry's destination after stripping.
An absent or empty list declares none. Each must be a distinct regular member of that archive,
reached without a link and still present after pruning and compressed-source removal. Diagnostics
name the field and offending path. Paths follow the forward-slash payload convention and additionally
reject dot segments and colons so alias spellings and Windows stream names cannot hide duplicates.
Repeated resulting payload paths across entries are refused, including after base/target joining.

The validated paths join the existing executable asset/local-file rule. Modes remain synthesized:
0755 on POSIX, 0644 for every Windows file entry, with identical declaration validation on Windows.
This also admits an archive member as a native execution binary.

**Rejected:** a top-level executable-path list, which disconnects origin validation from the
extraction that must supply the file. **Rejected:** inheriting upstream permission bits, which
would let extraction and host modes decide a reproducibility promise.

**Compatibility:** schema version 3 stays unchanged. Only optional build input grows; the signed
release, box manifest, digest, kind strings, signature and frozen golden documents do not change.
Older builders reject a scroll using the field through the archive item's closed properties.
Older consumers already understand the resulting ZIP modes. This is an npm minor, not a wire break.

Authoring commands preserve whole nested objects. There is no archive-specific `add` command;
`edit scroll` intentionally excludes collections, `new scroll` creates no archive entries, and
`refresh` changes pins only. Edit the archive JSON directly. Base/target merging joins entries
whole, so declarations travel with their entry and need no separate merge rule.
32 changes: 32 additions & 0 deletions .context/decisions/environment-executable-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Executables the solved environment installs are declared in `executablePaths`

**Decided 2026-11-04, for the same npm minor release as archive-member executables.**

The runtime rule marks only the interpreter and the scripts directory. A package that keeps a
helper binary in its own directory inside the prefix — a compiler Triton launches, a server
PyTorch spawns — has always shipped as 0644, and once the self-test ran with archive modes it
failed at build time with nothing an author could declare. `executablePaths` closes that gap.

It is a top-level list of literal payload paths under the runtime's environment root (`venv/` for
every runtime). The reader refuses a path outside that root, a path an asset or local file brings
in, and a path under any `assetArchives` destination, so each executable keeps one declaration on
the entry that supplies it. Spelling follows `assetArchives[].executables`: forward slashes, no dot
segments, colons or trailing slash, distinct within a whole scroll; base/target joining appends
base-first and drops repeats, like `prunePaths`. After unpacking and pruning, the build requires
each path to be a regular file reached without a link, then joins it to the same mode rule as every
other declaration. Windows validates identically and keeps extension-based executability.

Packing materialises directory links in the prefix, so a path that went through one on the build
machine is a real path in the payload; a file link inside the prefix stays a link on POSIX targets
and is refused there, while Windows receives the file.

**Rejected:** globs, which would make the declaration describe whatever the solve happened to
produce rather than what the author reviewed; a literal path that moves with a new `python3.x`
directory fails the build by name instead. **Rejected:** widening the runtime rule to a package
directory, which would mark data files executable. **Rejected:** accepting any payload path, which
would give archive members and assets a second, competing declaration — the reason
[`archive-member-executables.md`](archive-member-executables.md) rejected a top-level list.

**Compatibility:** optional build input in schema version 3, like `assetArchives[].executables`.
Signed documents, the box manifest, the payload digest and ZIP interpretation do not change;
older builders reject the unknown top-level property; consumers need no upgrade.
32 changes: 32 additions & 0 deletions .context/decisions/self-test-archive-modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Self-tests use the modes the consumer will receive

**Decided 2026-11-04, alongside executable archive members.**

Before optional Apple signing and the build self-test, `applyArchiveFileModes()` applies the
archive writer's own `archiveFileMode` rule to every collected regular payload file. It never
chmods a link. Runtime entry points and script directories, executable assets/local files and
declared archive members receive 0755; every other POSIX regular file receives 0644. Windows
skips chmod and retains its existing extension-based semantics. Parity sees the same tree.
The entry-point executable guard now runs before the self-test. Self-test failures retain the
original error and explain which declaration can give a helper its missing executable bit.

Modes are reapplied after the probes and the final digest write, so generated output has the
same final mode in the build directory and the ZIP. The writer still synthesizes modes itself;
normalization is a check of what will ship, never permission to trust the build filesystem.
This makes two cheap metadata walks rather than reading or copying a second multi-GB payload.

**Rejected:** archive-then-extract validation (candidate b). It reproduces more than modes but
costs another full write, read and temporary payload, and changes pipeline order. The defect is
the permission rule, which is already centralized. Consumer conformance and the packed-package
end-to-end check still exercise real extraction. **Rejected:** chmodding only declared files,
which would leave undeclared upstream executables runnable during self-testing.

This deliberately changes build behavior: boxes that passed only because their build directory
inherited upstream executable bits now fail where installed consumers already failed. Rebuilding
with the appropriate declaration repairs them without changing the signed format. No dependency
versions or runtime rules are silently broadened to conceal a failure.

Evidence: the new real-subprocess regression first failed because the undeclared build wrongly
succeeded; it passes after normalization by observing an OS permission failure, before archiving.
The declared counterpart runs. A separate test proves that a link under the runtime scripts
directory cannot chmod its data-file target back to executable.
Loading
Loading