Changelog
User-visible changes per release. Design archaeology lives in
plan/; this file is for "what's new for me".
[0.17.11] — 2026-08-31
Fixed
grip adopton a freshgrip initrepo generated a host file that didn't eval. The host updater matched themodules: [example inside the template's header comment and inserted the new module entry there; the dangling comma swallowed the import below ("Import is not allowed here") and adopt's self-check refused its own output. The updater targets the real array now, with a regression test against the shipped template.- The demo tapes run again: fixtures moved off the retired Python
frontend, the rollback tape passes
--host, and the demo workflow extracts the release tarball before installing it. Demos now re-render on every CLI change (path triggers on), including the new adopt demo.
[0.17.10] — 2026-08-31
Regression fixes for 0.17.9, from the same migration report.
Fixed
git()fetches are deterministic. The payload hash covered the whole clone including.git— whose index caches working-tree mtimes — so the same rev hashed differently on every fetch and every cold-store apply failed its pin check. The checkout alone is the payload now;.gitnever reaches the store.- A failed apply no longer leaves placeholder-literal links. The
generation manifest recorded the RAW install key, so the mid-graph
rollback restored destinations to paths like
ripgrep-{version}-{target}/rg. The manifest records the expanded key, deploy refuses a key that still contains a placeholder after expansion (invariant violation, not a path), and restore never writes a dangling symlink. grip self-updateworks on shared egress. The unauthenticated GitHub API rate-limits by source IP; when it fails, self-update falls back to the web tier (releases.atom→ plain download URLs) — the same path plain release downloads already take.
[0.17.9] — 2026-08-31
Hardening from a real two-host migration report.
Fixed
- A fetched apply no longer drops pin metadata from the lockfile.
Re-fetching against a locked pin rewrote the entry with only the
content hash, losing
version,url, andapi_url— the next warm-store deploy then failed with an unexpanded{version}in install keys, and a cold store had to re-resolve through the registry API (breaking private GitHub Enterprise mirrors behind shared egress). - Store publish falls back to a copy across filesystems. Staging
lives in
$TMPDIR; on containers that's routinely a tmpfs while the store sits on the overlay, and the hard rename died with a bareCross-device link. Publish now copies when rename returns EXDEV, and store io errors name both paths. - Deploy refuses destinations that resolve inside the env repo. A symlinked destination directory (e.g. a leftover from another provisioner) used to land the write inside the checkout — the module overwrote its own source.
- A config tree that gains a file re-pins instead of going stale.
The lockfile records the repo overlay hash (
repo256) alongside the tree hash: presence checks compare it,grip updatereports the move as a bump, and deploy no longer falls back to linking the repo checkout for sources the store never published (0014 §4). grip updateresolve errors name the module, not the registry string; pinned git modules reportskipped (pinned by rev)instead of "resolution not supported yet".- helix linter pack knows
[editor.inline-diagnostics](24.07+).
Added
- Probe docs say to gate on a stable system path, never the tool's own installed presence (which oscillates).
[0.17.8] — 2026-08-30
Dead-IR audit: nothing in the schema may exist without an executor.
Added
runsteps are executable (0007 §3's middle rung): structured argv/env/cwd as data, no shell interpretation, declared outputs checked after the run. Previously declared-but-refused — the phantom-class the cargo_install removal started.{arch.x64}placeholder (node-style: x64 / arm64) for upstreams whose assets use the node naming family.- Step-form intents execute through the activation adapters — the
adapters now read the expanded steps (the single source of truth),
so declarative
activatefields and class-style intents take the same path, exactly once.
[0.17.7] — 2026-08-30
Removed
cargo_installandmakebuild kinds — declared but never executable, which is a phantom contract (valid IR the core refuses at apply). The IR now carries only what it executes:custom_shellplus an ephemeral toolchain module covers the same ground;cargo install --lockedin a custom step with declared outputs is the documented rust-build form. Reusable build logic belongs to builder plugins when reality demands it (0001 §3.1 amended).
[0.17.6] — 2026-08-30
Added
- npm dependencies in env repos: module code can import packages
from the repo's own
package.json+node_modules(BYONM) — installed by you, evaluated read-only under the same sandbox as module code (no env, no network, no subprocesses, no filesystem outside the repo). A dependency needing an effect fails loudly at eval; that effect belongs in a probe or a fetcher. grip initscaffolds the IDE story:package.json(with@gripsack/corepinned to a compatible major.minor — types for your editor and the deliberate pin),tsconfig.json,node_modules/in.gitignore, and a freshgit init, cargo-init style.
Fixed
- The eval spawn applies the pin map via
--import-mapinstead of relying on deno.json discovery — a discovered deno.json project would have blocked BYONM forever (and the embedded frontend now carries itspackage.json, which is what flips BYONM on).
[0.17.5] — 2026-08-30
Fixed
- E115 path validation now covers explicit-steps modules
(
installStep/configStepentries) and verify paths — the declarative-only pass could be routed around bysteps = [...].
[0.17.4] — 2026-08-30
The beautiful-errors sweep (0004 §3 pushed through the stack).
Added
- E114 — unknown placeholder: a mistyped
{sytem}in a fetch, install, or verify string now failsgrip checkwith a span at the module and an edit-distance suggestion ("did you mean '{system}'?"), instead of a 404 at fetch time.
Fixed
- Apply-time failures are coded, span-labeled diagnostics: a fetch or
build step failing now renders
error[E301]/E302pointing at the module line ("raised here"), replacing bareerror:lines. - Resolution errors name the gripsack module, not the registry string ("step resolve failed in fish", not "in fish-shell/fish-shell").
- Probe diagnostic codes (E112/E113) moved into the central codes module — the placeholder code is E114, no collision.
Added (path validation — 0016 §D4)
- E115 — path shape: source and destination paths validate at
check time with spans — payload-relative sources reject absolute
forms,
.././empty segments, trailing slashes, backslashes; destinations reject..escapes and bare~//. Placeholders validate as opaque single-segment atoms (their values are single-segment by construction). - Tar traversal is a loud error: hostile entries (absolute paths,
..escapes) fail extraction naming the entry — the tar crate's unpack_in skips them silently otherwise, stranding a partial payload (zip extraction was already sanitizing).
[0.17.3] — 2026-08-30
Platform facts, floating git, and a hardened store (plan 0016).
Added
- Platform placeholders in fetch specs:
{system}(flake-stylex86_64-linux),{target}(rust triple, musl on linux),{arch},{arch.go}(goreleaseramd64),{os}— expanded by the core from the machine's facts in asset patterns, tarball URLs, and install and verify keys. One module now serves every platform; per-host locks keep every machine honest. - Floating git fetcher:
git(url)without a rev resolves the remote's default-branch HEAD at lock time, pins the sha into the lockfile, andgrip updatemoves it — the same float-and-pin semantics every other fetcher already had. Inline revs still pin. - Read-only store payloads: files publish with write bits dropped
— an app rewriting an
ownedconfig through its symlink gets EACCES instead of silently corrupting the store. Directories stay writable, so repair/gc/rollback are unaffected. - Linter packs: yazi theme sections ([app], [indicator], [cmp],
[spot], [icon], [flavor], …), helix
completion-timeout+[editor.whitespace]+[editor.lsp], starship$schema, atuin[daemon]keys — all sourced from current upstream docs.
Fixed
- Linter diagnostics for unknown sub-tables named the section and pointed at the right header line (they said "unknown key" at line 1).
- Version-skew warnings (W10) no longer fire on current versions —
host lockfiles pin tag-style versions (
v18.20.1) while packs carried bare prefixes (18.); packs now carry both forms. - atuin
search.shellsaccepts the documented string default (was a live false-positive A04).
[0.17.2] — 2026-08-29
Dogfood fixes — every one of them caught by running gripsack against a real, full-sized dotfiles env.
Fixed
store verifyno longer false-positives onmergeandtemplatemodules: the manifest records the deploy-output hash (trimmed block, rendered bytes), and verify now recomputes exactly that instead of comparing the raw store file (which could never match).- Package harvest copies symlinks as symlinks instead of following
them — a symlink to a directory (conda's
lib/terminfo → share/terminfo) crashed pixi-module fetches with a pathless io error. Note: pixi payload identity changes with this fix; agrip update <module>re-pins deliberately. - API auth tokens now survive same-host redirects — a transferred
GitHub repo (
owner/x→new-owner/x) previously re-requested anonymously into the rate-limited pool and 403'd. Cross-host redirects still strip credentials (the no-leak rule stands). - Fetch/copy io errors carry the offending path instead of a bare "the source path is neither a regular file…".
[0.17.1] — 2026-08-29
The adopt audit (plan 0015 §7): ask, don't guess; say exactly what you wrote.
Changed
grip adoptno longer guesses ownership. The hardcoded app-behavior tables are deleted — they presented folk knowledge as detection. Adopt now asks with the semantics laid out (arrow-key select):owned(read-only link, repo is the only editor),tracked_copy(real file, drift kept — the safe default),merge(one managed block in a shared file). Non-interactive runs taketracked_copywith a loud note;--modeis the preseed.- Adopt's plan labels destinations it will absorb as
"will be adopted (prior recorded)" instead of demanding
--take-over.
Fixed
- Adopt no longer follows directory symlinks inside the adopted tree — a link could pull an arbitrary directory tree into your repo. Such links (and broken ones) are skipped and reported.
- Adopt refuses paths outside
$HOME(plan compliance) and refuses to overwrite existingmodules/<name>.ts/configs/<name>/— the never-clobber rule covers the repo too. - Eval-failure and abort messages list exactly what was written and how to abandon it (the old message claimed the repo was untouched).
- Host-entrypoint edits are a pure, unit-tested function; a
single-line
modules: [a]insertion produced[ab, ]before. - Large adoptions (>25MB) warn and name the largest entries.
Refactored
commands/adopt.rs(500 lines) split intoadopt/{mod,inspect,generate,prompt}.rs— pure functions at the edges, side effects named. The ownership menu uses dialoguer.
[0.17.0] — 2026-08-28
Constrained evaluation (plan 0013). Breaking: the Python frontend, bun, and uv are removed.
Added
- Sandboxed eval: the TypeScript frontend runs under a pinned,
hash-verified Deno (2.9.6) with deny-by-default capabilities — no
environment variables, no network, no subprocesses, read-only within
the repo. First eval downloads the runtime once;
GRIPSACK_DENOoverrides. Eval platforms: glibc Linux and macOS (Deno ships no musl build;grip doctorsays so plainly there). - Injected facts: os/arch/libc/hostname are detected in the Rust core and passed to eval in a JSON inputs document — no more frontend-side self-detection.
- Probes:
ctx.probe.executable("nvidia-smi")/ctx.probe.file_exists(...)are symbolic requests the core binds in a two-stage eval (fixpoint-capped, E112 on instability), recorded in the run log and summarized ingrip plan's host-inputs header. - Trust gate: the first eval of an unfamiliar repo prompts before
running its code, naming the exact sandbox capabilities.
grip trust list/add/removemanages the store;GRIPSACK_TRUST_ALL=1is the CI bypass. defineEnv: host entrypoints are functions —export default defineEnv((ctx) => ({ tags, modules }) ).module()is a pure constructor; falsy module entries drop out. Side-effect registration is gone.- The dual-frontend parity corpus is replaced by a golden IR snapshot corpus (fixture envs → IR, byte-exact modulo spans).
- Content-addressed store identity (plan 0014):
fetch-only and config-only modules name their store path by the
canonical hash of their content; builds stay input-addressed (their
output can't be named before it exists — plan-time naming is what
keeps
grip plancomplete). A mirror swap or URL edit with identical bytes re-proves once, then dedups to the same path: no store churn, no new generation. Editing an install mapping no longer refetches. The lockfile records both hashes:sha256(transport integrity of the download) andtree256(store identity). grip store verifyis now correct and host-independent: the whole-tree check previously compared a tree hash against the transport hash (could never match) under a hostname-keyed lock lookup (usually skipped) — the generation manifest now carriestree256, and a tampered fetched payload fails verify anywhere.grip adopt <path>(plan 0015) — the adoption flow as a first-class command: inspects the path, recommends an ownership mode with the reason stated, generates the payload + module + host entry, shows the plan, and touches nothing until confirmed. The apply uses scoped take-over (absorbs exactly the adopted destinations; unrelated drift is never clobbered) and records prior state for every taken-over destination. Rollback — and undeclaring the module — restores your original files, bytes and permission bits, drift-guarded against your post-adopt edits. On a fresh machine, adopt records an empty generation 0 first, so adoption is always reversible.- Prior blobs live content-addressed under
$GRIPSACK_HOME/prior/;gccollects them with the same reachability rule as store paths. - Provisioning is serialized across concurrent
gripruns — two racing applies could corrupt each other's Deno download or embedded frontend materialization (os error 26/2 under concurrency). - Symlink deploys report "unchanged" when the link already points at the right store path — a re-proved fetch no longer looks like a redeploy.
Migration note: content-addressed paths differ in shape, so the
first apply after upgrading re-stages fetch/config modules once
(identical bytes, new names); grip gc collects the old paths.
Removed
- The Python frontend (
pip install gripsack), the embedded zero-provisioning path,GRIPSACK_PYTHON,[eval] deps, and uv provisioning.frontend = "python"in env.toml is an E400 with a migration hint. - bun provisioning and
GRIPSACK_BUN.
[0.16.4] — 2026-08-28
Fixed
grip self-updatefinds the binary inside the real release layout (gripsack-<version>-<triple>/grip, not a bare root) — 0.16.3's self-update reported "no grip binary" against actual releases; the e2e fixture now nests identically so the gate catches the shape. (0.16.3 self-updaters: runcurl -fsSL https://gripsack.dev/install.sh | shonce.)
[0.16.3] — 2026-08-28
Added
grip self-update— a package manager that can update itself. Tarball/install.sh installs fetch the newestcore-vrelease, verify the mandatory sha256 sidecar, and atomically swap the running binary (takes effect next launch). brew/cargo/mise installs get their manager's command instead.--checkreports only.- This changelog, mirrored at gripsack.dev/docs/changelog.
[0.16.2] — 2026-08-28
Added
- Zero-provisioning bootstrap: the Python frontend is embedded in
the binary. A config-only repo applies with zero network and zero
provisioning, first apply included —
grip+ anypython3is the whole requirement. Repos declaring[eval] depsor wheel linters still provision a pinned venv on demand.
Fixed
- Repo-ref linters (
owner/repo@tag) no longer force frontend provisioning; they resolve from the plugin store, not pip. grip doctorrecognizes the embedded frontend.- The crates.io publish loop publishes
griplintbeforegripsack-lint(retries absorb index lag, not ordering).
[0.16.1] — 2026-08-27
Added
grip store verify [--repair]— re-hashes every deployed entry against the manifest and every store payload against the lockfile's pins;--repairremoves corrupt paths so the next apply re-fetches.- The dual-frontend golden corpus: one full-surface fixture env evaluated through both frontends, IR diffed modulo spans — running in the required CI gate.
Fixed
- Four parity bugs the corpus caught on debut: TypeScript dropped
brew(version=), env contributions were missing from the TS spec, host arch drifted (x64vsx86_64), and a missing--hostfile silently yielded empty tags instead of erroring. A fifth (musl libc detection) fell to the docker gate. - Rollback of template/merge entries restores rendered bytes from the manifest's recorded vars; a foreign file's non-managed content survives.
keep_generationsresolves from the repo, never cwd-sniffing.
[0.16.0] — 2026-08-27
Added
- One deploy engine for apply and rollback: destinations are restored through the same code path either way.
grip plandiffs against the live generation — modified, pruned, and take-over rows, not just the new manifest.
Fixed
- Identity projection: spans and absolute paths no longer leak into the store-path hash, so two machines with identical repos get identical paths.
- IR rejects unknown fields; GC fails closed on corrupt manifests;
tracked_copydrift is e2e-pinned.
[0.15.5] — 2026-08-26
Added
- Activation adapters:
fonts(fontconfig cache) anddesktop_entry(desktop database) intents run post-link/post-activate.
[0.15.0] — 2026-08-25
Added
- The griplint engine moved in-crate: all 22 config linters run in-process from embedded data packs — no venv, no provisioning for first-party linters.
- Plugin lifecycle:
package = "owner/repo@tag"provisions fetcher and linter binaries from releases, sha256-verified and receipted. - TypeScript frontend evaluates through a pinned, provisioned bun.
Earlier releases (≤ 0.14.x) predate this file — see the release history.