(sigil env lock)
(sigil env lock) - Evaluation-free env.lock parsing and reification plans.
This module is intentionally a leaf over core data/filesystem/store APIs. It must never import the environment authoring engine, resolver, evaluator, channel, registry, or authoring-library loader.
Exports
locked-packageprocedureConstruct a locked-package struct.
locked-package?procedureTest if a value is a locked-package struct.
locked-package-nameprocedureGet the name field of a locked-package struct.
locked-package-versionprocedureGet the version field of a locked-package struct.
locked-package-recipeprocedureGet the recipe field of a locked-package struct.
locked-package-outputprocedureGet the output field of a locked-package struct.
locked-package-package-nameprocedureGet the package-name field of a locked-package struct.
locked-package-package-identityprocedureGet the package-identity field of a locked-package struct.
%locked-authoring-library--typevariableGet the package-identity field of a locked-package struct.
locked-authoring-libraryprocedureConstruct a locked-authoring-library struct.
locked-authoring-library?procedureTest if a value is a locked-authoring-library struct.
locked-authoring-library-nameprocedureGet the name field of a locked-authoring-library struct.
locked-authoring-library-versionprocedureGet the version field of a locked-authoring-library struct.
locked-authoring-library-sourceprocedureGet the source field of a locked-authoring-library struct.
%locked-authoring-channel--typevariableGet the source field of a locked-authoring-library struct.
locked-authoring-channelprocedureConstruct a locked-authoring-channel struct.
locked-authoring-channel?procedureTest if a value is a locked-authoring-channel struct.
locked-authoring-channel-nameprocedureGet the name field of a locked-authoring-channel struct.
locked-authoring-channel-originprocedureGet the origin field of a locked-authoring-channel struct.
Get the requested-ref field of a locked-authoring-channel struct.
locked-authoring-channel-revisionprocedureGet the revision field of a locked-authoring-channel struct.
locked-authoring-channel-sourceprocedureGet the source field of a locked-authoring-channel struct.
Get the snapshot-path field of a locked-authoring-channel struct.
locked-authoring-channel-snapshotprocedureGet the snapshot field of a locked-authoring-channel struct.
locked-authoring-channel-catalogueprocedureGet the catalogue field of a locked-authoring-channel struct.
Get the catalogue field of a locked-authoring-channel struct.
locked-authoring-load-pathprocedureConstruct a locked-authoring-load-path struct.
locked-authoring-load-path?procedureTest if a value is a locked-authoring-load-path struct.
locked-authoring-load-path-pathprocedureGet the path field of a locked-authoring-load-path struct.
locked-authoring-load-path-sourceprocedureGet the source field of a locked-authoring-load-path struct.
%locked-sealed-recipe--typevariableGet the source field of a locked-authoring-load-path struct.
locked-sealed-recipeprocedureConstruct a locked-sealed-recipe struct.
locked-sealed-recipe?procedureTest if a value is a locked-sealed-recipe struct.
locked-sealed-recipe-nameprocedureGet the name field of a locked-sealed-recipe struct.
locked-sealed-recipe-recipeprocedureGet the recipe field of a locked-sealed-recipe struct.
locked-sealed-recipe-bytesprocedureGet the bytes field of a locked-sealed-recipe struct.
locked-sealed-recipe-derived-fromprocedureGet the derived-from field of a locked-sealed-recipe struct.
locked-sealed-recipe-provenanceprocedureGet the provenance field of a locked-sealed-recipe struct.
%locked-runtime-member--typevariableGet the provenance field of a locked-sealed-recipe struct.
locked-runtime-memberprocedureConstruct a locked-runtime-member struct.
locked-runtime-member?procedureTest if a value is a locked-runtime-member struct.
locked-runtime-member-nameprocedureGet the name field of a locked-runtime-member struct.
locked-runtime-member-versionprocedureGet the version field of a locked-runtime-member struct.
locked-runtime-member-outputprocedureGet the output field of a locked-runtime-member struct.
locked-runtime-member-search-pathsprocedureGet the search-paths field of a locked-runtime-member struct.
%reification-plan--typevariableGet the search-paths field of a locked-runtime-member struct.
reification-planprocedureConstruct a reification-plan struct.
reification-plan?procedureTest if a value is a reification-plan struct.
reification-plan-versionprocedureGet the version field of a reification-plan struct.
reification-plan-snapshotprocedureGet the snapshot field of a reification-plan struct.
reification-plan-toolchain-idprocedureGet the toolchain-id field of a reification-plan struct.
reification-plan-toolchain-versionprocedureGet the toolchain-version field of a reification-plan struct.
reification-plan-toolchain-outputprocedureGet the toolchain-output field of a reification-plan struct.
reification-plan-packagesprocedureGet the packages field of a reification-plan struct.
reification-plan-closureprocedureGet the closure field of a reification-plan struct.
reification-plan-runtimeprocedureGet the runtime field of a reification-plan struct.
reification-plan-base-search-pathsprocedureGet the base-search-paths field of a reification-plan struct.
reification-plan-systemprocedureGet the system field of a reification-plan struct.
reification-plan-rootsprocedureGet the roots field of a reification-plan struct.
reification-plan-priorityprocedureGet the priority field of a reification-plan struct.
Get the authoring-provenance field of a reification-plan struct.
reification-plan-authoring-channelsprocedureGet the authoring-channels field of a reification-plan struct.
Get the authoring-load-paths field of a reification-plan struct.
reification-plan-authoring-librariesprocedureGet the authoring-libraries field of a reification-plan struct.
reification-plan-sealed-recipesprocedureGet the sealed-recipes field of a reification-plan struct.
env-variable-name-char?procedureA (VAR SUBDIR) export pair, shared by base-search-paths and by a runtime member's own row. SUBDIR is joined onto a store path, so it must be relative and must not climb out -- the same shape-check discipline as valid-profile-name?, for the same reason: a lock is a user-editable file and this value becomes a filesystem path.
THE VARIABLE NAME IS VALIDATED HERE BECAUSE THE EMITTER CANNOT DEFEND ITSELF. print-exports renders export NAME='VALUE'${NAME:+":$NAME"}, and POSIX gives no syntax for quoting the NAME half -- quoting it would change what gets assigned rather than escape it. So a name like X; rm -rf ~; Y is not a mangled variable, it is a SECOND COMMAND, the moment the output is sourced -- which is the documented use of sigil env exports. There is no downstream repair available for it: validation at this boundary is not the cheaper of two options for THIS half, it is the only one.
SCOPED DELIBERATELY TO THE NAME. An earlier draft of this comment said "do not harden the emitter and believe this class is closed", which over-generalised a true statement about the NAME into a false one about the pair. The VALUE half sits in the same emitted line, carries the same lock-controlled data, and DOES have a canonical downstream repair (' -> '\''). Saying otherwise steered the next reader away from the second vector, which was live at the time and is handled below. If you are hardening the emitter, harden the VALUE; the NAME still cannot be.
Before v2 these five names were hardcoded constants and no lock content could reach that emitter. base-search-paths is what made a variable name attacker-controllable, so the check arrives with the field that opened the hole. Gate: test/integration/test-env-search-path-injection.sh drives the EMITTER, because the reader-side assertion in test-env-surface.sh does not cover it and passed throughout.
safe-subdir-char?procedureAND THE SUBDIR NEEDS THE SAME TREATMENT, FOR THE OPPOSITE REASON.
The subdir is emitted INSIDE the single quotes: export NAME='<store>/<SUBDIR>:<store>/<SUBDIR>'. Being quoted is not protection on its own -- quoting only contains a value if the QUOTE CHARACTER ITSELF cannot appear in it. A subdir of bin'; rm -rf ~; :' closes the string and appends a command, once per closure member.
RECORDED BECAUSE THE MISTAKE REPEATED ITSELF HERE: the first version of this fix validated the VARIABLE (whose destination is obviously syntax) and left the SUBDIR alone on the reasoning that it is quoted -- which is the SAME asymmetry that caused the original defect, where the author validated the subdir as a PATH and left the variable alone. Both halves of this pair reach a shell; neither is safe by position.
The comment above once claimed the subdir already got "the same shape-check discipline as valid-profile-name?". It did not: valid-profile-name? rejects control characters, DEL, ', " and \ and the subdir check applied none of them. We cannot reuse that predicate wholesale because it also rejects /, which a subdir needs (share/man), so the character rule is restated here rather than referenced.
env-lock-render-verifiedprocedureRender a plan AND prove the bytes parse back before anyone writes them.
env-lock->string stays a pure renderer: tests legitimately serialize deliberately-invalid plans to exercise the reader's refusals, and a renderer that refuses cannot build those fixtures.
The guard belongs where the RISK is -- immediately before a file replaces a user's lock. Without it, a plan missing a required v2 field writes a lock THIS SAME BUILD REFUSES TO READ, after atomic-write has already destroyed the previous one. That is not hypothetical: this change's own headline bug was a second plan constructor silently dropping a field, and the mitigation written beside it -- "prove it survives by reading the written lock back" -- was a discipline note rather than code. This is that note, executed.
It validates the BYTES, not the plan: validate-plan requires canonical order, which only the serialized form has (the in-memory closure is accumulated unsorted and the writer sorts as it emits), so validating the plan directly would refuse every legitimate lock.
env-lock->stringprocedureVALIDATE BEFORE WRITING, not only when reading.
validate-plan used to run at the reader only, so a plan missing a required v2 field wrote a file THIS SAME BUILD REFUSES TO READ -- after atomic-write had already replaced the user's lock. That is not theoretical: this change's own headline bug was a second plan constructor silently dropping a field, and the mitigation written beside it ("prove it survives by reading the written lock back") is a discipline note, not code. One call turns the whole class into a refusal before any bytes hit the disk.
*env-lock-full-verification*variableFULL VERIFICATION: ignore the verification cache and re-hash every output against the lock. The cache lives in the store, which its owner can write, so anything running as that user can rewrite a record to match altered bytes (review 2026-09-23, reproduced). The cached check catches accidental and naive change; sigil env verify --full is the one that trusts nothing but the lock's hashes. The re-hash also rewrites the record, so a forged record is corrected, not just ignored.
env-seconds-sinceprocedure"12.3 s" since START (a current-jiffy value). Integer arithmetic only, so the text never depends on how a float prints.
*env-rehash-announced*variableThe first re-hash in a process announces the phase; each re-hash then reports its item and time. Cache hits say nothing, so an ordinary sigil env shell entry (0.7 s) stays silent and only a slow first entry -- or verify --full -- talks.
env-lock-reify-runtimeprocedureReturns (member . realpath) pairs so a caller can compose a member's own declared exports as well as the base set -- the pairs are on the member, not derivable from the path.
VERIFIES THE FULL CLOSURE, not just the members it returns. Composing commands would otherwise verify a strict SUBSET of what reify, apply and fetch verify, so sigil env shell would succeed on a store whose build-input outputs are missing or corrupt where it previously exited
- Narrowing verification as a side effect of composing fewer paths is not a trade anyone chose; it reuses env-lock-reify so the two cannot drift.
env-lock-full-verification!variable(No description)
env-progress!variable(No description)
env-progress-quiet!variable(No description)
env-progress-quiet?variable(No description)
read-env-lockvariable(No description)
read-env-lock-stringvariable(No description)
env-lock-semantic-diffvariable(No description)
env-lock-output-infovariable(No description)
env-lock-reifyvariable(No description)
env-lock-fetchvariable(No description)
env-lock-fetch-casvariable(No description)
lock-abi-versionvariable(No description)
lock-readable-versionsvariable(No description)
runtime-rows-min-versionvariable(No description)