Roadmap
Where gripsack is going. The north star: a misconfiguration in any file gripsack touches should produce the same quality of error as a typo in a module.
Shipped
- The core flow:
apply/plan/generations/rollback— generations on disk, atomic flip, no-op satisfaction - Run-level rollback: an apply that fails mid-graph restores every touched destination to the previous generation — no half-applied deployments. Post-activation adapter failures never auto-rollback (a service that failed to start must not bounce your configs back)
grip check— eval + sema + linters, exit code = validity, zero side effects; the CI gate for your dotfiles repo- Fetchers:
github_release(with resolution +{version}substitution),git,brew(bottles, with the pour),pixi, bare binaries /.tar.xz/.zip gripfetch-*plugin fetchers — NDJSON over stdio, every byte hash-verified by the core- Plugin protocol hardening: the locked pin travels in every request, provenance lands in the run log, stderr drains concurrently, and a 600s deadline means no hangs
grip update [MODULE]— the flake cycle, per-module--repobootstrap (path or git URL), self-provisioning runtimes — bundled pixi and a pinned, sha256-verified Deno for eval, per platformtree(...)— directory-shaped config deploys- Read-only store payloads —
chmod a-wat publish: an app that rewrites anownedconfig through its symlink gets EACCES instead of silently corrupting the store - Foreign-path refusal: gripsack never touches a path it didn't deploy
unless you say
--take-over - Prune-on-undeclare: remove a module from your repo and its deployed files go with it
- E110 (missing source) + E111 (duplicate destination) — the plan-time gates for the two classic dotfile accidents
- Per-host selection via auto-detected facts (os, arch, libc) and declared tags
verifychecks on deployed files- Parallel scheduler — N = cores, per-step resource flocks
- Lifecycle locks:
apply/gc/update/rollbackserialize, never interleave gc(+--dry-run) andwhy-owns- Exported env:
profile.shwritten at activation, plus[eval] envbuild-time injection - Activation adapters (SystemdUser, fonts via
fc-cache, and desktop-entry viaupdate-desktop-database) - 23 config linters —
griplint-*for the tools your dotfiles actually configure (helix, yazi, starship, zed, claude-code, …), as data packs incrates/griplint, with a weekly upstream-watch that files freshness issues; see linters - Corporate proxy support, trusting the system CA roots,
NO_PROXYhonored - Per-platform release matrix (linux + macOS, x86_64 + aarch64 — no
Windows; WSL is the story), a homebrew cask, and a multi-platform
install.sh, with a brew
version=tripwire - Run logs with causal spans; the debug and adopt skills
merge+templateownership modes — a managed block inside foreign files (.bashrc), and payloads rendered from{{ vars }}at deploy timegrip init— scaffold an env repo from the embedded template, package.json + tsconfig included (the IDE story)- The griplint engine in-crate — all 23 linters as embedded data packs running in-process (the golden corpus replays byte-exact); no venv, no provisioning, no lifecycle for first-party linters
- Plugin lifecycle:
package = "owner/repo@tag"provisions fetcher and linter binaries — sha256-verified, receipted, store-resolved - Enterprise-grade GitHub releases: authenticated API downloads,
host-scoped tokens,
version=pins, bare-hostbase_url [throttle]token buckets + thecapabilitiesop — rate budgets live in fetchers;[throttle]in env.toml outranks them, and buckets persist across runs- Constrained evaluation (plan 0013) —
one frontend: TypeScript under a pinned, hash-verified Deno with
deny-by-default capabilities (no env, no network, no subprocesses,
read-only within the repo). Facts are injected by the core, not
self-detected; host effects are declared probes the core binds in a
two-stage eval, shown in
grip plan's host-inputs header. Host entrypoints aredefineEnvfunctions — modules are pure values. The first eval of an unfamiliar repo is an explicit trust decision (grip trust,GRIPSACK_TRUST_ALLfor CI). The Python frontend, bun, and uv are retired; the parity corpus became a golden IR snapshot corpus. - Content-addressed store identity (plan 0014) —
the hybrid: fetch-only and config-only modules name their store path
by content hash (self-verifying; a mirror swap with identical bytes
re-proves once and dedups to the same path), builds stay
input-addressed so
grip plankeeps plan-time naming.store verifyis host-independent — the manifest carries the expectation. grip adopt(plan 0015) — the adoption flow as a first-class command: point it at~/.config/helix, it explains what it sees, asks the ownership question with the semantics laid out (never guesses — the safe default istracked_copy), generates the module, shows the plan, and touches nothing until you confirm. The apply absorbs exactly the adopted destinations (scoped take-over) and records prior state — rollback, or undeclaring the module, restores your original files, bytes and permission bits. A fresh machine gets an empty generation 0, so adoption is always reversible.- Structured
runsteps (0007's middle rung): argv/env/cwd as data, no shell interpretation, declared outputs checked — and the{arch.x64}placeholder for node-style asset names - Pin integrity hardening — a fetched apply can no longer drop
pin metadata from the lockfile; the repo overlay hash (
repo256) moves the pin when a config tree gains a file; store publishes survive a tmpfs/tmp(EXDEV copy fallback); deploys refuse destinations resolving into the env repo; git payloads hash the checkout, never the clone; and rollback restores the expanded install keys a generation actually deployed
Next
Order is priority: adoption UX first, reliability of the core loop next, ecosystems last.
- Crash recovery for mutable destinations — the generation flip is atomic, but the tracked_copy/merge/template writes before it are not: kill -9 or power loss mid-apply leaves touched destinations with no generation record. A durable apply journal (begin → backups → intents → fsync → commit) with startup reconciliation would make the mutable half crash-recoverable, not just failure-compensated.
- Resolver executables (0013 D8) — custom registries become
gripresolve-*plugins on the same NDJSON envelope as fetchers: spawned with a scrubbed, declared-env-only environment (credentials never touch eval), network intent declared and shown in plan.grip update --dry-runfolds in here — "resolve, don't write" is the natural read mode of an explicit resolve phase. - Rollback adapters — user-initiated
grip rollbackre-runs post-link/post-activate adapters (the unified engine covers destinations; adapters still don't re-run on rollback). - ~~Content-addressed store paths, deeper~~ — shipped as the 0014 hybrid (above). A content-addressed fetch cache fell out as designed: presence of the content path IS the cache hit.
- Module env inheritance for dependents — a dependent sees the env its dependencies export (build-time today).
- Secrets model — references to external secret managers (age/sops/1Password), decrypted at activation; values never in the store, manifests, plans, or logs (0001 §7's seed, made public).
- More probe kinds —
executableandfile_existsshipped;probe.command("wg", ["show"])(a declared, core-run subprocess at bind time) is the next rung. - More reference fetchers —
pip(corporate PyPI mirrors) and the internal-registry patterns, out-of-tree likegripfetch-apt.
North star
- The LSP: sema passes + config parser + validator plugins all emit the same span-labeled diagnostics; the editor shim maps them in. Editing your kitty config in VSCode and getting an error squiggle at the exact line — that moment.
- Fetcher registry:
gripfetch-*plugins for the long tail, undergripsack-devand beyond.