Tillandsias

Tillandsia is the largest genus in the bromeliad family, with well over six hundred species.

A small private cloud on your own computer. Its workspaces are thrown away and rebuilt; the work you push out of them is kept.

tillandsias.org · v56.10.20.1 · website last updated 56.10.20

A small cloud on your own computer,
built to be thrown away and rebuilt.

Your own hardware. Free software. No account and no subscription. Below is what it is and how it works, told five times over — pick the version that fits the person reading.

The simplest way of putting it that is still true.

Tillandsias, explained like you are five

A real tillandsia needs no soil and no pot — it drinks from the air, and borrows nothing.

A tiny cloud on your own table

When you open a website, you borrow a computer in a huge building far away — the cloud. Tillandsias builds a tiny cloud inside your own computer, so you can work without sending your things to its makers. A service you choose to use can still receive what you send it.24

It is a doll house. Your computer is the table. Tillandsias puts a clean pretend-computer on it, and inside that, little rooms, one program each.

Anyone may have it, read it and change it, for free.14

Nobody goes outside except the doorman

The rooms have no doors outside. They share one private hallway, and exactly one room — the doorman — may step out to the internet. Everyone else asks it.2

Robot helpers may only play inside the doll house.11 If one makes a mess in its room, the room can be thrown away — but anything it sends out of the doll house is real, so look at what it sends.26

Broken things get thrown away, not glued

the usual way this way it breakspoke at it still oddpoke again drifts somewhere nobody can reproduce it breaksthrow it away build from the recipe identical every time, on every machine
Nothing at runtime is repaired. It is thrown away and built again from the recipe — which is why it comes back identical.

If a room breaks, nobody glues it. It goes in the bin and Tillandsias builds a fresh one from the same recipe. That is on purpose: gluing one layer tends to break the layer underneath next.3

Your own project folder lives outside the doll house, and sweeping a room never touches it.12

Getting work out of the doll house has its own rule. The doll house has a post box, and it does not say done until the shelf far away really has your parcel. If it cannot reach the shelf, it says so, instead of saying done and quietly keeping the parcel in a drawer.26

Getting better, honestly

Every time the makers fix something, a machine checks that nothing that used to work got broken.23

Less broken every time does not mean perfect one day. It only means creeping toward some resting place, which may not be zero. They say so themselves.4

Things that are still broken

known limitation The doorman has a secret key. The key itself is locked so only you can read it,15 and it lives in a folder inside your own home folder.1720 But that folder is made the ordinary way, without first being made private.25

what happens next Making the folder private before the key goes in is the written next job. It is not done yet.18

How we know — 14 sources

Each small number in the text opens the exact lines of the Tillandsias source that back that sentence, as they stand in release v56.10.20.1, the one the install commands give you. Hover a number to read the quoted lines without leaving the page. A source marked with its own release points at a newer one. The links were moved to release v56.10.20.1 automatically; a person last checked the claims themselves against v56.10.9.1.

  1. 2Only the doorman may step outside openspec/specs/enclave-network/spec.md#L10-L12Only the proxy is dual-homed for external access; all other members communicate exclusively through the enclave.
  2. 3Throw away and rebuild, never patch by hand, and why methodology/philosophy.yaml#L84-L99Therefore the DEFAULT response to a borked host/VM/guest/podman/stack layer is to DESTROY and RECREATE it, never to hand-patch it forward.
  3. 4Getting better every time means nothing gets worse and what is left shrinks, not that it ends up perfect methodology/math-foundations.yaml#L108-L120Without that metric proof, "monotonic convergence" means ordered non-regression plus finite residual descent, not metric contraction.
  4. 11Robot helpers run only inside the doll house openspec/specs/forge-as-only-runtime/spec.md#L18-L19Every coding agent, maintenance shell, and runtime utility executes inside the project's forge container; there is no host-side execution surface.
  5. 12Sweeping a room never touches your project folder openspec/specs/app-lifecycle/spec.md#L79-L81the container is removed, project-specific cache data is deleted, but the project source directory in `~/src` is never touched
  6. 14The licence: anyone may have it, read it and change it LICENSE#L1-L16the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program
  7. 15The key is locked so only its owner can read it crates/tillandsias-headless/src/main.rs#L4143-L4143Clamp the CA private key to owner-only access (0600) — 755-qcxh.
  8. 17The key's folder sits inside the program's own folder crates/tillandsias-core/src/ca_path.rs#L36-L50format!("{}/ca", ca_template())
  9. 18The written next job: make the folder private before any key is written plan/index.yaml#L26013-L26013deliverable: CA material is created in a directory that is private by construction (XDG_RUNTIME_DIR, or DirBuilder::mode(0o700) before any key is written), and scripts/clamp-ca-material.sh gains a retirement condition or is deleted
  10. 20The program's own folder is inside your home folder images/default/ca-path.txt#L83-L83${HOME}/.local/state/tillandsias
  11. 23After every fix, a test that used to pass may not start failing build.sh#L3109-L3111a failure not named in scripts/test-known-red.txt is a new regression, and a listed test that PASSED is a stale entry that must be deleted
  12. 24A service you choose receives what you send it PRIVACY.md#L60-L63If you instead configure a remote provider, the content you send is transmitted to that provider under their terms.
  13. 25The key's folder is made the ordinary way, with no private setting crates/tillandsias-headless/src/main.rs#L4211-L4211std::fs::create_dir_all(&certs_dir)
  14. 26The post box says done only once the faraway copy really has the work images/git/pre-receive-hook.sh#L6-L8Validates ledger YAML, then synchronously relays the proposed ref transaction upstream before accepting it locally. A client success therefore means the configured upstream has durably accepted the same atomic ref set.

Straight answers to what you are actually wondering: privacy, cost, and what breaks.Picks up where “like I’m 5” left off.

Tillandsias, explained simply

A tillandsia is an epiphyte, not a parasite: it rests on its tree and takes nothing from it.

What it is for, in one paragraph

The reason a grown-up wants one is narrower than "the cloud, but at home". Software you did not write — increasingly, AI assistants that write and run code for you — has to work somewhere. The choice is between your actual machine and a sealed room containing only the project folder you deliberately opened.1 Tillandsias is that room, plus the plumbing to prepare it and start your tools.

the boundary the specification draws must hold must NOT hold positive cases negative controls
A test that only ever passes proves nothing. Each boundary is pinned from both sides.

Is my stuff private, and who could see it?

The people who make Tillandsias operate no servers your copy talks to: no account, no sign-up, no usage tracking, no crash reporting, no identifier attached to you or your machine.2 Your logs stay on your own disk where you can read them, and leave only if you send them somewhere yourself. Any login you hand over goes into a secret store on your own machine — the program's own vault, whose key sits in your operating system's keychain, or on some computers in a local file — never on anyone else's server.316

This does not depend on trusting anyone's intentions: there is no server of theirs to send anything to.2 It covers their side only. A service you sign into yourself still sees what you tell it.18

Does it cost money? Does it need the internet?

It costs nothing: free software under a licence that lets anyone read, run, modify and pass it on.4 You have already paid for the only hardware involved.

The internet is needed only for ordinary, visible reasons: fetching the program, fetching updates, reaching a service you chose.17 Local work can continue without sending it to an AI provider: language models can run on your own machine.18 That does not make every action offline-ready. Fetching a missing tool or model needs a connection, and publishing your work to GitHub fails when GitHub cannot be reached.31

What happens when I turn it off? Can it break my computer?

Turning it off is the expected motion, not an interruption. Anything you saved is a real file on your real disk, untouched by the rebuild. Even the internal passwords the pieces use to talk to each other are replaced at every start, so yesterday's messy shutdown cannot jam today's start.5

Installing a new release is a bigger change than turning it off: the Linux, Mac and Windows installers all reset the program's own working state and set it up again.363738 On every platform this reset is the gentle kind: it rebuilds the program's parts but deletes none of your stored data, so your downloads survive, and so do your sign-ins as long as your keychain can still unlock them.3942 The installers do not ask first, and they keep the details in a setup log rather than on your screen.363740 On a Mac the only reset that deletes the secret store is a separate, explicitly approved one that no installer runs.41

Publishing your work follows the same idea. Inside the sealed room you push to a copy that lives on your own machine, not to the internet. That copy passes your work on and waits: it reports success only once the copy outside has really accepted the same changes, so a success you can see is one that survives the room being thrown away.32 It is also the only part that holds your GitHub login — it uses it when it publishes, and never hands it to the room where your tools run.33

As for damage: a misbehaving tool inside the sealed room sees only the folder you gave it.1 Wiping the installation and rebuilding is documented and supported, not a last resort.6 The cost you do pay is ordinary and reversible. It is a real virtual machine, so it holds real memory while running and gives it back when it stops. The system image it downloaded stays on your disk so the next start is quick; uninstalling clears it, though on a Mac you have to ask for that explicitly, and the uninstaller makes you type a confirmation first.203043

Will an update break what already works?

far close successive releases — each one re-raises the bar the floor — possibly not zero zero would mean “finished”. No proof it is reachable.
Schematic: if comparable residuals never rise, they approach a floor, and nobody has proven that floor is zero.

Each change to Tillandsias has to pass its tests, and a machine refuses a change that makes a test fail that was not already known to fail.23

Two honest limits come with that. Passing tests are evidence, not proof: they show no problem was found, not that none exists.8 And "never worse" means settling toward some floor, not that the floor is zero problems.9

Where it falls short today

known limitation On a Mac, the app has not yet passed Apple's inspection, so a copy downloaded with a web browser is blocked the first time you open it. The recommended one-line install avoids that only because it does not go through a browser.10

what happens next The fix needs a paid Apple developer membership, which is still pending.1011 The build is already able to do the inspection step once it has those credentials.24

known limitation The Windows downloads are not signed yet.27 One kind of Windows installer package cannot be installed at all without a signature, so it is left out of a release until signing is in place.1226

what happens next The signing service has been chosen but is not set up yet.13

known limitation The Linux program shows a version number but not the exact source it was built from, so two builds with the same number cannot be told apart.3435

None of these touch the privacy answer above.

what happens next It is recorded as an open problem in this release.35

How we know — 34 sources

Each small number in the text opens the exact lines of the Tillandsias source that back that sentence, as they stand in release v56.10.20.1, the one the install commands give you. Hover a number to read the quoted lines without leaving the page. A source marked with its own release points at a newer one. The links were moved to release v56.10.20.1 automatically; a person last checked the claims themselves against v56.10.9.1.

  1. 1What a workspace can reach, and why that isolation exists to protect you from the tools PRIVACY.md#L32-L40Each workspace sees the project you opened, not your whole filesystem. That isolation exists to protect you from the tools, not to hide anything from you.
  2. 2"Tillandsias collects nothing" — no account, no telemetry, no analytics, no server PRIVACY.md#L5-L17**Tillandsias collects nothing.** There is no account to create, no telemetry, no analytics, and no server operated by us that your installation talks to.
  3. 3Credentials in a local secret store on your machine; logs kept on your own disk PRIVACY.md#L41-L44**Credentials you provide** (for example, a GitHub login you initiate) are stored in a local secret store on your machine.
  4. 4GNU General Public License, version 3 LICENSE#L1-L2GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
  5. 5Internal passwords are removed and reissued at every start, so an unclean shutdown leaves nothing stale openspec/specs/ephemeral-secret-refresh/spec.md#L10-L25The system SHALL check for existing podman secrets before creation. If a secret exists from a prior unclean shutdown, it SHALL be removed and recreated with fresh content.
  6. 6"Designed to be wiped and rebuilt freely" PRIVACY.md#L46-L47You can remove all of it at any time by resetting or uninstalling; the application is designed to be wiped and rebuilt freely.
  7. 8Passing tests are evidence, not proof that the program is correct methodology/philosophy.yaml#L249-L252Traceability, version matching, litmus success, and CRDT metadata convergence are evidence. They are not proof of semantic correctness by themselves.
  8. 9The project does not claim it will reach zero problems methodology/math-foundations.yaml#L107-L120The methodology does not currently claim Banach-style contraction. It can report decreasing residuals, but it has not proven a contraction constant over a complete metric space of project states.
  9. 10The install instructions: a Mac download through a browser is blocked on first launch README.md#L27-L45Tillandsias is signed but not yet notarized (Apple Developer enrollment is pending), and macOS tags anything a *browser* downloads with `com.apple.quarantine` — so the .dmg route hits a Gatekeeper block on first launch, while `curl` does not tag at all and the app opens normally.
  10. 11Apple signing and inspection options, and which steps need a paid membership plan/issues/macos-gatekeeper-signing-options-2026-08-29.md#L39-L48| Developer ID Application certificate | **Yes** — no free-tier equivalent.
  11. 12An unsigned Windows installer package of this kind cannot be installed plan/issues/store-msix-submission-blockers-2026-08-31.md#L26-L40**Consequence for the pending signing decision.** The unsigned MSIX (`0x800B0100`, packet 722-w7a2) blocks the **GitHub-release** channel only. It does not block the Store channel at all.
  12. 13The signing service chosen for the Windows downloads plan/issues/windows-signing-research-2026-08-16.md#L1-L25**SignPath Foundation is the signing path for the GitHub-release channel.** Packet 722-w7a2 is reshaped, not closed: its deliverable changes from "an Azure Trusted Signing account" to the SignPath Foundation chain, with Azure **Artifact Signing** as the recorded fallback.
  13. 16Where no keychain is available, the vault's key falls back to a local file crates/tillandsias-headless/src/vault_bootstrap.rs#L4070-L4072let fallback_file = read_credential_fallback(&format!("fallback_{VAULT_SHAMIR_SHARE_V1}"))
  14. 17Software sources — package repositories and release downloads, reached at your direction PRIVACY.md#L51-L56**Software sources** — package repositories and release downloads (for example GitHub, Linux distribution mirrors, and language package registries) to fetch the software it runs.
  15. 18AI providers only if you configure one; language models can run entirely on your own machine PRIVACY.md#L60-L63**AI providers, only if you configure one.** Tillandsias can run language models entirely on your own machine. If you instead configure a remote provider, the content you send is transmitted to that provider under their terms.
  16. 20The downloaded system image is cached on the host between runs openspec/specs/vm-provisioning-lifecycle/spec.md#L48-L51cached at `~/.local/share/tillandsias/rootfs-fedora-44-<sha256>.tar.xz` (on macOS: `~/Library/Application Support/tillandsias/rootfs-…`; on Windows: `%LOCALAPPDATA%\tillandsias\rootfs-…`).
  17. 23The test verdict: a failure not already on the known-failing list is refused as new build.sh#L3106-L3106THE VERDICT IS A RATCHET, NOT CARGO'S EXIT CODE.
  18. 24The Mac build inspects and seals the app when given Apple's credentials scripts/build-macos-tray.sh#L267-L290say "notarize: submitting (this waits for Apple's verdict)"
  19. 26That installer package is left out of a release when it is unsigned .github/workflows/release.yml#L800-L800::warning::withholding unsigned MSIX from release assets: $($_.Name) (uninstallable without a signature; set TILLANDSIAS_SIGNING_ACCOUNT to publish it)
  20. 27The other Windows downloads are published unsigned .github/workflows/release.yml#L789-L789::warning::TILLANDSIAS_SIGNING_ACCOUNT is unset — publishing UNSIGNED Windows artifacts (plan packet 722-w7a2)
  21. 30Uninstalling removes the cached image, except on macOS where it is preserved unless you ask for a full wipe scripts/uninstall.sh#L238-L238Preserving the VM image in $DATA_DIR (use --wipe to remove it).
  22. 31A publish that cannot reach GitHub fails instead of half-succeeding openspec/specs/git-mirror-service/spec.md#L225-L230the forge's `git push` SHALL return non-zero
  23. 32Success means the faraway copy really accepted the same changes images/git/pre-receive-hook.sh#L6-L8Validates ledger YAML, then synchronously relays the proposed ref transaction upstream before accepting it locally. A client success therefore means the configured upstream has durably accepted the same atomic ref set.
  24. 33The GitHub login is read by the publishing service and never enters a workspace openspec/specs/git-mirror-service/spec.md#L15-L16The git service reads the GitHub token from Vault at push time via Vault CLI; the token never crosses into a forge container.
  25. 34The version shown is the one written into the program when it was built crates/tillandsias-headless/src/main.rs#L167-L167pub(crate) const VERSION: &str = include_str!("../../../VERSION");
  26. 35The open problem: the Linux program records no build commit plan/index.d/20260914t185710z-1188-mm9y-installed-launcher-has-no-provenance-lenovinha.yaml#L27-L43the Linux launcher has no build.rs reading it.
  27. 36The Linux installer runs the reset, quietly, into a setup log scripts/install.sh#L359-L359"$INSTALL_PATH" --reset-state </dev/null >"$SETUP_LOG" 2>&1
  28. 37The Mac installer runs only the gentle reset, which keeps the VM, the secret store, the sign-ins and the downloads scripts/install-macos.sh#L286-L288It is the SOFT reset, always (host-state-lifecycle): it keeps the VM, the
  29. 38The Windows installer runs the reset without asking scripts/install-windows.ps1#L1051-L1051--reset-state < NUL
  30. 39The Linux reset rebuilds the program's parts and deletes no stored data crates/tillandsias-headless/src/main.rs#L11554-L11570A SOFT reset destroys DERIVED state only and deletes NO store under any
  31. 40The Windows installer's rule: the gentle reset, with no prompt scripts/install-windows.ps1#L1036-L1037So SOFT reset is the default only and forever.
  32. 41On a Mac only the explicitly approved hard reset destroys the secret store, and it announces that first crates/tillandsias-macos-tray/src/reset_state.rs#L305-L324destroyed.push(cache_root.join("vault-data").display().to_string());
  33. 42The project's rule: every platform's reset must keep your data openspec/specs/host-state-lifecycle/spec.md#L180-L182`--reset-state` on every platform SHALL be a SOFT reset: it destroys every item of derived state and SHALL NOT remove, move or rewrite any item of operator data.
  34. 43The uninstaller asks for a typed confirmation and refuses without a terminal scripts/uninstall.sh#L184-L185Type \"delete\" to uninstall, or press Enter to cancel:

The anatomy: what runs where, what survives a teardown, and where the sharp edges are.Assumes the two levels before it.

Tillandsias, for a power user

Its roots only grip; the leaves do the drinking — a plant that runs rootless.

What you get the moment a forge opens

tillandsias --headless /path/to/project --claude starts a container with your project in it and an agent at the prompt. Nothing below is configured by you or by the agent: the context file the agent reads first lists the plumbing under the heading "all transparent — zero configuration needed".16 Everything on this page describes the stable release, v56.10.20.1 — the one the install commands give you.

The forge container

A forge is a Fedora Minimal image in two layers: a heavy base carrying the toolchains — gcc, make, cmake, Rust with cargo and rust-analyzer, Go, Node, Java, Python, the debuggers — and a thin runtime layer with the entrypoints, cheatsheets and the agent's configuration.1718 The tag is a content hash of its sources, so an unchanged rebuild is a no-op.19 The coding agents are not baked in: every launch refreshes them into a per-project cache volume that outlives the container, rolling back to the last known-good version when upstream ships a broken one.2021 Cheatsheets, /tmp and the runtime directory sit on RAM-backed tmpfs with hard caps.22 The default clone-only lane now puts the source tree in RAM too — a tmpfs-backed volume the launcher creates for the lane, sized from the mirror’s pack; the opt-in host-mounted checkout keeps its host mount.30149 Allowlisted commands — tmux and lazygit among them — are shims that install userspace Homebrew on first use.23 On start the forge writes a context file into the checkout naming what is present, what is absent, and what needs no configuring.24

checked Works out of the box. The tool cache is a named volume, never a host path, so first-run installs survive the container's removal without opening a host-home surface.21

known limitation The storage spec names four RAM-backed roots and a host RAM gate with a matching container memory ceiling; the default lane now mounts all four roots, but the host RAM gate and the matching container memory ceiling remain library code nothing calls.2526 The Homebrew prefix is backed by no volume, so every on-demand tool is re-installed at every launch and its bootstrap needs live egress each time.27 And the context file tells the agent each such install verifies a signed attestation per bottle; the shim states that attestation is off, because fetching it would need a GitHub credential no forge may hold — what remains is integrity from one publisher.2829

what happens next The RAM gate and memory ceiling are a planned fix that has not been started: run the host RAM check before launch and set matching memory limits.26 The recorded remedy for the prefix asks for a decision between persisting it and baking the tools into the image.27 For the context file's wording there is a recorded remedy too: fetch and pin the attestation bundles when the image is built, so a lane can verify one with no GitHub credential present at all.145

The HTTPS proxy and its cache

Every container is born with its proxy variables set and the proxy's certificate composed into the system trust bundle before any network client starts; no per-client CA variable is set, and a regression test refuses a launcher that tries.34353637 The allowlist of developer hosts — registries, GitHub, cloud SDKs, model providers — is generous enough that npm, cargo, pip and flutter work unconfigured.38 It decrypts exactly one of them, GitHub's release-asset CDN, so a large asset is fetched once for the whole machine.3940 Everything else passes through encrypted, so the proxy never sees, and never caches, crates.io, npm, PyPI or api.github.com.41 What makes the second cargo build fast is not the proxy but a per-project cache volume for cargo, npm, pip and go that outlives the forge.42 A blocked host gets no 403: the connection is reset, and only the proxy log names the domain.43

checked The trust wiring is pinned by a test: the forge launcher mounts the single runtime CA input read-only and sets none of the per-client CA variables.37

known limitation The cache-hit claim for the one decrypted host has never been shown live. The fix for a rule order that made decryption unreachable shipped the config and static tests, and records that no real miss-then-hit trace was obtained.44

what happens next The next step is written down: on a Podman host, request one release-asset URL twice through the strict proxy and record a miss followed by a hit with equal checksums.44

Local inference

The launcher prepares a shared model server at http://inference:11434, started on launch if it is not already running and shared across projects.4546146 The first run pulls the engine and one small tool-capable chat model into a host directory that outlives the containers.47489 Readiness is best-effort: a failed readiness wait warns and the forge launches anyway; errors while preparing the service can still fail launch.46 The context file tells the agent, in a closed vocabulary, what the host's accelerators are and whether the endpoint is ready — ready means at least one model is cached, never "starting up".4950 Larger tiers are opt-in behind an environment variable; the CPU floor is unconditional by spec.5152

checked A failed model-readiness wait is non-fatal, so it does not hold up the agent session.46 The CPU floor is the required fallback, rather than evidence that every fresh host has successfully loaded a model.52

checked The first-run engine download now pins an Ollama release and checks an architecture-specific SHA-256 before execution; a missing checksum tool or mismatched digest stops it.4853

known limitation Two limits remain. GPUs reach the container only with a vendor stack — NVIDIA with a CDI spec, AMD when ROCm reports the card — and NPUs are named on every platform and used on none: no device argument for one exists in the launcher, and the spec itself says the engine cannot use them.54555657 The container's health check tests that the runner binary exists, not that a model loads.58

what happens next NPUs have two planned fixes, neither started — device passthrough for the container, and a host-native sidecar for Metal and the NPUs a Linux guest cannot see.5960

what happens next For the health check, no fix is planned yet. The entrypoint does run the real load check once at startup.58

The git mirror and push relay

For an agent, a refinement is a controlled data path: it receives selected source, rules, task context and tool results; it proposes a patch; local checks and review decide whether that patch becomes a commit. Git retains reachable commits, while the current tree is a snapshot that may replace or delete files. Calling the repository a "set of vectors" is therefore a useful description of the model's selected context, not Git's storage format and not a guarantee that every current file survives.616266

Several tasks can run this loop at once. Each needs its own declared target and measurement; the shared repository is the coordination point, not evidence that their measurements are already comparable. The relay makes an accepted push atomic,6263 which preserves the record of what was accepted, not the truth of the patch.

Why this is built rather than borrowed. The mirror has to satisfy two requirements at the same time. The credential for the upstream must stay on the server side, so a workspace can publish its own work without ever holding a token that could publish anything else — the git service reads that token when it pushes and it never crosses into a workspace container.152 And the push must be synchronously durable: the hook relays the proposed ref transaction upstream before accepting it locally, so a client's success means the upstream has durably accepted the same atomic ref set.153 The live relay performs that as a single atomic push.154

Tools in general offer one of those or the other. A managed push mirror holds the credential for you and copies asynchronously, reporting success before the copy lands — which is precisely the false success that strands an agent's work. A caching or redirecting git proxy relays synchronously but forwards the client's credentials, which is the isolation being bought in the first place. Needing both at once is the unusual requirement, and it is what the pre-receive relay exists to provide.

That no off-the-shelf component satisfies both requirements together is the project's survey, not a benchmark: read it as the reason the decision was taken, not as proof that no such tool exists.

Rolling the decision back is deliberately cheap: the relay is a bare repository plus a pre-receive hook, so undoing it means pointing the daemon back at the bare repository.

Git needs nothing from you inside a forge. A read-only global gitconfig redirects the project's GitHub URL to a bare mirror in its own named volume, so git remote -v still says github.com while clone, fetch and push hit the enclave.616 A push is not acknowledged until the mirror's hook has relayed exactly those refs upstream with one atomic push.6263 The token authorising that relay is read from the vault inside the git service at push time and never enters the forge.6465 Deletions and branch rewinds land nowhere;66 a project with no upstream keeps its pushes in the mirror.67

note The pairing is the argument, not novelty. The pattern of relaying a push

onward from a hook is documented elsewhere; what is specific here is refusing to

report success before the upstream has it, while the credential stays outside the

workspace.

checked Works out of the box once GitHub Login has run on the host; without a stored token the relay refuses the push and says so.68

known limitation The push path inside the enclave is anonymous: any peer that can reach the mirror may create branches or fast-forward them and have the privileged relay carry those updates upstream. Being on an internal-only network is placement, not client authentication.69 The authenticated SSH lane that would replace it is built but dark behind an environment variable that defaults to off.70 Offline, a push simply fails — nothing is queued.71

what happens next Turning the SSH lane on by default is planned and waits on a decision about the transport.72 For the offline push, no fix is planned yet.

The vault

Secrets live in a Vault container at vault:8200, reached over TLS with the enclave certificate already trusted.7475 A lane with a credentialed provider is handed a single-purpose token on tmpfs at /run/secrets/vault-token whose policy reaches only that provider's paths; the generic forge policy cannot read the GitHub token, which the git mirror, the tray and the login flow can.76777879 Across a stop, Vault unseals itself from one Shamir share held in your host keychain, with no passphrase prompt; the share reaches the container as a file on tmpfs for the length of the launch, never as a podman secret, because podman keeps its secrets in plaintext on durable disk.8

checked The token is injected at container start with nothing for the agent to do,76 and a test pins that every credentialed lane mounts its scoped lease while a credential-free lane mounts none.147

known limitation The context file tells the agent the vault is at http://vault:8200. The shipped listener is TLS-only, so that address answers an HTTP error; only the in-image helper carries the right default.807581 The token defaults to a one-hour life and nothing in the lane renews it.82

what happens next No fix is planned yet.

The experts and the project index

The agent finds four tool servers already registered, for Claude Code and OpenCode alike: git tools, a project index, the host-browser bridge, and a plan expert.8384 The index answers project type, status, commands and layout for any checkout, citing what it indexed; the plan expert returns cited answers or a typed refusal, never a guess, and answers only on a checkout carrying Tillandsias' own plan crate — elsewhere it reports itself degraded, by design.858687 Both start in the background after the clone and never gate the session.88 The status now distinguishes the running expert’s capabilities from what a relaunch would build: waiting for a build, needing a relaunch, and losing capabilities on relaunch have different machine-readable verdicts.150 A ready index is separate from a usable answer engine. Deterministic project questions can work without local inference; synthesis still returns a typed refusal when the synthesis capability is absent, even if inference itself is ready.151 The "on-demand" spec describes tray-side spawning and health checks that do not exist; the harness launches each server itself.89

checked Works out of the box on any project, with no registration step.85

known limitation The context file tells the Claude lane about a conversational Local Experts mode, but only the OpenCode configuration carries an agent that reaches the grounded endpoint behind it.9091 And the pre-expert-binary trap is half closed: a forge seeded from a checkout without the expert sources now reports itself degraded instead of ready, but the fresh-forge criterion — an in-forge probe grading full marks with no manual step — is recorded open.92

what happens next No fix is planned yet for the Claude-lane mismatch. The fresh-forge check waits on a decision about how releases are branched.92

Sibling web containers

Ask the agent to publish the project and it does not start a server in the forge. It asks the host, over a socket that exists only for that lane, to launch a sibling web container beside it: read-only, capability-dropped, joined to the enclave network and given a router route.939495 The sibling sees the running forge's own worktree, resolved from the lane's live container and its RAM-backed source volume rather than from a path on the host, and it is given a selected public document tree rather than the repository: var/html, public or dist if one exists, otherwise the root index.html and the plainly static files and asset directories beside it; a Wrangler configuration at the project root selects a second, Wrangler-based runtime instead.160 The URL comes back as https://www.<project>.localhost:<port>, on a loopback TLS port the router listens on, with a leaf certificate issued for that name.96 The project is attributed from the socket the request arrived on, never from the request.95 From inside the forge the proxy forwards .localhost requests to the router;97 this page has not traced the HTTPS preview port through that rule.

checked A sibling never mounts .git, .env or the forge root, so the repository's history cannot be read through the router, as it could on the earlier publish path.16098 Publishing reads the running forge, so it no longer needs the project to live under ~/src on the host;160 the tray's project list still looks only there.100 And a sibling is not left running when its forge dies: a watcher stops it and removes its route once that forge is gone, and the next launch of the same lane clears a departed one; the stack-wide sweep still matches no sibling, so this cleanup is the preview's own.16199 All of this is read in the code, not shown live.

known limitation The feature's own spec still calls itself a draft whose verification is design-only, with executable coverage and live evidence pending.162 The only end-to-end test of publishing is still on the list of tests that no suite has ever run.104 Only www is routed, not the apex.96 An ordinary forge launch ensures four images and the web images are not among them;102 when a publish needs an image that is missing, this release builds it on first use instead of refusing, reports the build step while it runs, and remembers a failed build by its inputs so the next attempt waits for a retry rather than repeating the failure.164103

what happens next Planned: prove the preview works live, then make its spec binding.163

what happens next For the apex route, no fix is planned yet.

Chromium

Two browser images are built at initialisation: a minimal headless core, and a framework layer on top of it with GUI Chromium, Node and Playwright.101105106 The runtime launches the framework image as a read-only, capability-dropped, incognito app window for the OpenCode-web and observatory modes.107 The browser tool the agent sees — open, screenshot, click, type, close — opens the same kind of container: every window is the framework image started by the host, there is no host-browser path any more, and a lane with no container launcher is told so.109110 A window reaches the host's display over Wayland only, never X11; with no display, or on request, it runs headless.108165 Arbitrary JavaScript evaluation is refused except in a development-mode window the same lane opened, which gets full browser control.167

known limitation The browser spec still says evaluation is disabled outright in this version, while the code enables it for development-mode windows; the two have not been reconciled.166167 A legacy Chrome installer script remains in the tree with no caller.111 The browser script now defaults to the enclave network,115 and the tool's allowlist accepts dotted project names while checking the project label exactly,113 but proxy-only egress has not been demonstrated for every browser path.114

what happens next Planned and not started: remove the orphaned Chrome installer with its test and spec.112

what happens next For the spec drift and the remaining browser egress paths, no fix is planned yet.

The anatomy

The region is one Fedora guest — a VM, or a WSL2 distro on Windows — running a rootless Podman stack. Among the members:

  • a forge, the container your coding agent runs in, one per project;
  • a git mirror, a bare repo on the internal network;
  • a vault holding your tokens;
  • a forward proxy, the single outbound door;
  • a router, a Caddy reverse proxy giving web services stable names;
  • an inference container, the shared model server.

The roster is longer — a Nix cache, a catalog service, the observatory's web container, an SSH-lane sidecar — and most of those do not run in an ordinary session.1 Membership is not a hand-kept list: the spec names the attach sites by function symbol, and a guard compares the attach sites it finds in the source with that list, refusing the build if either side drifts. It reads code, not a live network.132

your computer · your disk · your electricity a clean Linux machine (made for you on Mac/Windows) the enclave — a private network with no way out appapp appagent the one guarded door out — everything else is refused
A region is layers, not a machine. Each ring only trusts what is inside it.

One rule carries the isolation: the containers share a Podman network created with --internal — no gateway, no route out.1 Only the proxy is dual-homed onto an egress network, and the launcher creates both networks on the first launch and reuses them after.131

The router publishes to one loopback address — port 80 by default, with fallbacks, or the port you pass with --port — and maps <service>.<project>.localhost onto internal ports. Nothing binds 0.0.0.0.2116

Starting one is a curl on Linux, an installer script on macOS, a tray executable on Windows. Then tillandsias --headless /path/to/project --claude.

checked Isolation is structural — one --internal network, exactly one dual-homed container — not a firewall ruleset you maintain. The launcher the tray runs passes the flag,131 and the membership guard runs in the build gate.32148

known limitation One thing the spec asks for is missing; another is enforced more narrowly than it reads. The spec requires the network to be removed at application exit when empty, and nothing implements that — the only removal is the destructive reset.133 And the membership guard, which does run, counts only builder and launcher functions by name, so an attach site written any other way is invisible to it.32

what happens next No fix is planned yet.

checked The stack script, the tray launcher, the per-project runner and the proxy diagnostic all create the network with --internal, and the stack script refuses to reuse a network that lacks it.311731120121 A build check finds each tracked shell network-create command and checks it; its Rust check covers less, one file at a time.118

What the walls are made of

Agent tooling exists only inside the forge image, never on your host $PATH; a missing image makes the launcher refuse rather than fall back to a host binary.4122 Mounts are enumerated — your project, a read-only CA certificate, the tmpfs roots, a per-launch temp dir, the persistent tool-cache volumes and a Tillandsias-written read-only gitconfig — so $HOME, ~/.config and your keychain are simply not addressable from inside, and a regression test asserts no user home is ever mounted.5

the usual way this way it breakspoke at it still oddpoke again drifts somewhere nobody can reproduce it breaksthrow it away build from the recipe identical every time, on every machine
Nothing at runtime is repaired. It is thrown away and built again from the recipe — which is why it comes back identical.

The forge does not touch your working tree by default: it clones fresh from the enclave mirror, and pushes travel back through the mirror on their way upstream.123124 An opt-in escape hatch bind-mounts your real checkout read-write and prints a reduced-isolation warning.125 Hence the blunt operating rule: a finding you did not push is a finding you destroyed.7

What survives, exactly

Idempotence is what makes destroy-and-recreate a repair procedure rather than a loss. Every layer comes up clean from scratch, so the boundary worth memorising is not "will it break" but "what is on which side of the wipe":

  • Survives a stop: the bare mirror in its per-project volume, with everything you pushed to it.6
  • Survives a stop: vault data, auto-unsealed from a single Shamir share held in your host keychain, with no passphrase prompt. The spec requires the share to reach the vault only through a file on tmpfs that is removed once the launch settles;8 the shipped guest also keeps a fallback copy of the share on its own disk when no keyring is reachable, and inside a VM guest that file is written on every initialisation.126127 The lifecycle spec says survival of the store requires an unlocking keyring and allows no persisted fallback share, so that code path is ahead of the spec in the other direction: the fallback writer is still present at this release.158
  • Survives even podman system reset, on Linux: the model cache, because it is a host bind mount and not a named volume. The spec spells that distinction out, having once cost someone a wrong answer.9 The spec now moves that directory to ~/.tillandsias/downloads/models, with a one-time migration from the old cache path; the launcher at this release still resolves the model directory under the cache root, so the move is specified and not yet in the code.9159
  • Does not survive --reset-guest: it removes every Tillandsias container, volume (the per-project git mirrors among them) and secret and the enclave and egress networks, then re-initialises. On Linux at this release it keeps the model cache and no longer deletes the Vault store, which is a host directory; whether your sign-ins then survive depends on the keyring still holding the unseal share. Push first.10128129 --reset-state goes further: it also destroys every Podman image, and still keeps the models and the keyring entries.156

checked On Linux the reset boundary is documented per artifact and matches the code for what a reset removes and keeps.128129 The one divergence is the model directory's path, which the spec has moved and the launcher has not.9159

known limitation The spec also promises that your host working copy is fast-forwarded after every successful push. No code implements it at this release: the file the spec names does not exist, and the tray-managed host checkout it fed was removed. After a forge push, your host checkout moves only when you pull.130143

what happens next No fix is planned yet.

known limitation On macOS the "cache survives" line is not yet proven. The VM at this release already boots with a second shared directory for the model cache, and the guest's first boot mounts it, so the durable path exists.131132 What remains open: guests provisioned before that change are not migrated, because the mount is written on first boot only, and survival across a VM rebuild has not been demonstrated end to end.11133 The shipped uninstaller now preserves the VM unless asked to wipe, and asks for a typed confirmation before it removes anything.134135157 A partially restored cache is worse than none — the inference service answers a version check and then fails every request.11

what happens next Planned and not started: prove survival end to end, and migrate the guests provisioned before the shared directory existed. How much a lost cache really costs to re-download has not been measured reliably yet.133136

known limitation On Windows, wiping the guest used to leave the old unseal share in Credential Manager, and the tray pushed that dead key into the fresh guest, permanently breaking GitHub login. The wipe paths now clear the stale share; a host that already holds one still delivers it, and the guest cannot tell the host it was rejected.12137

what happens next The reconcile half is blocked: the delivery reply carries no accept-or-reject signal, so a delivered share that fails to authenticate cannot yet lose to the guest's own secret.138 Until then, remove the stale credential by hand.

known limitation A host's container engine used to carry a global configuration line routing all registry traffic through the enclave proxy unconditionally, so with the stack down podman build and podman pull died on a raw DNS error that names nothing. Initialising a host now removes that line and hands the proxy setting to each container instead; a host never re-initialised still carries it, and a build that needs the absent proxy still fails without naming it.14014114213

what happens next The global-config half is done: re-running initialisation fixes the file.141 The rest is a planned fix nobody has started: a build must either not depend on a service that may be absent, or fail with a verdict that names the proxy.13

How Tillandsias itself is tested

the boundary the specification draws must hold must NOT hold positive cases negative controls
A test that only ever passes proves nothing. Each boundary is pinned from both sides.

The checking the earlier levels describe happens in a local gate, run on the developers' own machines: specs declare intent, litmus tests turn each requirement into an executable check, traces show the check ran, and the gate refuses a push that breaks the chain.

Read a passing gate with care. Eleven litmus files have never been run by any suite; they sit on a list that may only shrink, so that switching the gate on did not turn them all red at once.14

known limitation No server checks a change when it is pushed or proposed. The cloud runs one job, the release build, because the signing keys exist only there; everything else is checked only by the local gate.15

what happens next This is a deliberate choice to save cost, not a gap waiting for a fix.15

How we know — 162 sources

Each small number in the text opens the exact lines of the Tillandsias source that back that sentence, as they stand in release v56.10.20.1, the one the install commands give you. Hover a number to read the quoted lines without leaving the page. A source marked with its own release points at a newer one. The links were moved to release v56.10.20.1 automatically; a person last checked the claims themselves against v56.10.9.1.

  1. 1Enclave network requirements — `--internal` creation, reuse, cleanup on exit, proxy-only egress, and the symbol-anchored membership list the guard enforces openspec/specs/enclave-network/spec.md#L21-L77A new enclave member MUST be added to this list in the same commit that attaches it; `scripts/lua/check-enclave-membership-documented.lua` refuses the divergence in both directions.
  2. 2Loopback-only publish and the host-port fallback chain openspec/specs/subdomain-routing-via-reverse-proxy/spec.md#L3-L43What did NOT change, and what the requirement is actually protecting, is the loopback-only invariant: the publish is `127.0.0.1:{host_port}:8080` in every branch.
  3. 3The stack orchestration path now creates an internal network scripts/orchestrate-enclave.sh#L90-L103--internal
  4. 4Forge-as-only-runtime: the agent binaries must resolve inside a fresh forge, and the host must not need them openspec/specs/forge-as-only-runtime/spec.md#L48-L68`command -v claude codex opencode bash` MUST print four valid paths from inside a freshly built forge container.
  5. 5Regression test asserting no user home is mounted into a forge, and the mounts it deliberately allows crates/tillandsias-headless/src/main.rs#L25931-L25931must not mount a host .config dir into the forge; got source in: {arg}
  6. 6Bare mirror per project in a named volume; forge pushes persist there openspec/specs/git-mirror-service/spec.md#L19-L39The system SHALL create and maintain a bare mirror repository for each project at `/srv/git/<project>` inside the git service container, backed by the named Podman volume `tillandsias-mirror-<project>`.
  7. 7The ephemerality rule, stated for agents AGENTS.md#L61-L61**IN A FORGE, A FINDING YOU DID NOT PUSH IS A FINDING YOU DESTROYED.**
  8. 8Auto-unseal from the host native keychain, no passphrase prompt; the share is a tmpfs file for the launch, never a podman secret openspec/specs/tillandsias-vault/spec.md#L108-L135The unseal key SHALL NOT be a podman secret: podman's file
  9. 9Model cache is a host bind mount, NOT a named volume — and why that decides what `podman system reset` destroys; the spec now names `~/.tillandsias/downloads/models/` openspec/specs/inference-container/spec.md#L30-L32Models SHALL be stored in a HOST DIRECTORY (a bind mount, NOT a named Podman volume)
  10. 10What `--reset-guest` wipes and what it keeps crates/tillandsias-headless/src/main.rs#L1830-L1830keeps the model cache) and re-initialize.
  11. 11macOS model cache and engine payload inside the VM disk image; the destroyers, the outage a lost engine caused, and the ~2.47 GB figure as filed (planned, not started) plan/index.yaml#L27667-L27667every VM-directory deletion forces a ~2.47 GB re-download — the macOS twin of 518
  12. 12Windows Credential Manager retains a stale unseal share across a guest wipe; the wipe half landed, the reconcile half still open (planned, not started) plan/index.yaml#L26930-L26930every Windows reset path (--reset-guest, installer -Purge, the e2e smoke) wipes the guest vault but never clears vault-shamir-share-v1 from Credential Manager
  13. 13Global container config hardcodes the enclave proxy, so builds fail on DNS when the stack is down; the open deliverable is a verdict naming the proxy (planned, not started) plan/index.yaml#L37615-L37615~/.config/containers/containers.conf hardcodes http_proxy=http://proxy:3128 unconditionally, so when the enclave is down EVERY image build fails on DNS — and with it the release gate
  14. 14The ratchet list of litmus files that have never run, and why they are exempt openspec/litmus-tests/unbound-grandfathered.txt#L1-L25THE ONLY LEGITIMATE EDIT TO THIS FILE IS A DELETION
  15. 15The cloud runs one job, the release; every other check runs locally methodology/ci.yaml#L286-L324GitHub Actions runs EXACTLY ONE workflow: the release. Nothing else may consume cloud minutes — no push CI, no PR CI, no scheduled jobs, no cache warming, no webhooks. Every other gate runs locally.
  16. 16The startup context's infrastructure section: everything transparent, nothing to configure images/default/lib-common.sh#L5143-L5172You never need to configure git remotes, tokens, SSH keys, proxy settings, or CA certs.
  17. 17The heavy base layer: Fedora Minimal with the toolchains in one package set images/default/Containerfile.base#L5-L35rust cargo clippy rustfmt rust-analyzer cargo-deny
  18. 18The thin runtime layer on top of the base images/default/Containerfile#L1-L6Builds upon the heavy base image to inject configuration,
  19. 19Image identity is a content hash of the sources openspec/specs/default-image/spec.md#L100-L104The default forge image SHALL use a content-hash canonical tag derived from the image source set.
  20. 20Harnesses refresh at every launch into the persistent per-project tool cache, and a freshly installed binary that fails its contracts is replaced by the last good one openspec/specs/default-image/spec.md#L196-L237A freshly installed OpenCode that violates any of those contracts SHALL be rejected and replaced by that last-good binary.
  21. 21The tool cache is a podman named volume, not a host bind-mount, so it cannot become a credential-leak path crates/tillandsias-headless/src/main.rs#L18710-L18710A named volume — not a host bind-mount —
  22. 22The three RAM-backed roots the launcher mounts at this release crates/tillandsias-headless/src/main.rs#L19283-L19283.tmpfs("/tmp:size=256m,mode=1777")
  23. 23The on-demand tool allowlist in full: every listed command becomes a PATH shim that installs on first use images/default/brew-tools-allowlist.txt#L1-L122Commands listed here get a PATH shim in the forge: running the command
  24. 24Where the startup context file is written images/default/lib-common.sh#L4816-L4816local ctx_file="$project_dir/.forge-startup-context.md"
  25. 25The hot/cold spec: exactly four RAM-backed roots, the source tree among them openspec/specs/forge-hot-cold-split/spec.md#L11-L24Only these four path roots are HOT. "Maybe a hot path" is a HARD NO.
  26. 26The planned fix for storage: source tmpfs, host RAM gate and memory ceiling still to wire (planned, not started) plan/index.yaml#L8207-L8207production launch runs check_host_ram, derives compute_memory_ceiling_mb, and emits equal --memory and --memory-swap limits
  27. 27The Homebrew prefix is ephemeral; every launch re-installs on-demand tools (planned, not started) plan/index.yaml#L12407-L12407The brew prefix is ephemeral, so every forge launch re-installs and re-attests every tool — that is the rate-limit multiplier
  28. 28The startup context's claim of a Sigstore attestation per bottle images/default/lib-common.sh#L5244-L5249it verifies a Sigstore attestation per bottle
  29. 29What the shim verifies today: integrity from one publisher, attestation off images/default/brew-shim-exec.sh#L12-L21Sigstore verification is OFF because it requires a GitHub credential to FETCH
  30. 30The source tmpfs budget is calculated from the mirror pack size crates/tillandsias-headless/src/main.rs#L18749-L18749format!("/home/forge/src:size={budget}m,mode=0777")
  31. 31The launcher creates the enclave network with `--internal` on first launch, after ensuring the egress network crates/tillandsias-headless/src/main.rs#L3764-L3767command.args([ "network", "create", "--internal",
  32. 32The membership guard (now a Lua decider): every attach site's enclosing function must be in the spec and vice versa; it counts only builder and launcher functions scripts/lua/check-enclave-membership-documented.lua#L60-L81local BUILD_LAUNCH = [[^(build|launch)_]]
  33. 33The only network removal in the launcher: the destructive reset crates/tillandsias-headless/src/main.rs#L11829-L11829command.args(["network", "rm", "-f", &name]);
  34. 34Proxy variables injected into every enclave container from one source crates/tillandsias-headless/src/main.rs#L2140-L2140"http_proxy=http://proxy:3128".into(),
  35. 35The forge composes vendor roots plus the proxy CA into the system-default bundle before any network client starts images/default/lib-common.sh#L12-L23Compose the per-install CA into that target atomically, before any network
  36. 36No per-client CA variables; trust flows through the distribution's standard path openspec/specs/transparent-https-caching/spec.md#L57-L62launchers and entrypoints SHALL NOT select CA files with `GIT_SSL_CAINFO`, `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE`, or `NODE_EXTRA_CA_CERTS`.
  37. 37A regression test pins the CA mount and the absence of CA overrides crates/tillandsias-headless/src/main.rs#L26127-L26127typed forge launcher must mount the single runtime CA input
  38. 38Package-manager workflows must work with no configuration openspec/specs/proxy-container/spec.md#L157-L157common development workflows (npm install, cargo build, pip install, flutter pub get) work out of the box without configuration.
  39. 39Proxy purpose: allowlisted caching forward proxy, bump only the release-asset CDN, splice all other TLS openspec/specs/proxy-container/spec.md#L8-L14HTTPS interception is deliberately exceptional: the proxy bumps only the exact GitHub release-asset CDN hostname and splices all other TLS traffic end to end.
  40. 40The three ssl_bump rules: peek once, bump one exact host, splice everything else images/proxy/squid.conf#L133-L139ssl_bump peek ssl_bump_step1 ssl_bump bump github_release_assets ssl_bump splice all
  41. 41Spliced HTTPS is never cached by the proxy openspec/specs/proxy-container/spec.md#L44-L49the HTTPS response SHALL NOT be cached (tunneled traffic is opaque to squid)
  42. 42cargo, Go, npm and pip caches all redirected into the per-project cache images/default/lib-common.sh#L2283-L2315export CARGO_HOME="$PROJECT_CACHE/cargo"
  43. 43Blocked hosts get a TCP reset, not a 403 images/proxy/squid.conf#L151-L156deny_info TCP_RESET strict_deny_acl
  44. 44The live miss-then-hit trace for the bumped host was never recorded, and the next action that would record it (planned, not started) plan/index.yaml#L7533-L7576no rebuilt-image parser result or real identical-key MISS-to-HIT trace is claimed
  45. 45The consumer contract: one endpoint at inference:11434, downloads through the proxy openspec/specs/inference-container/spec.md#L13-L14Forge containers SHALL access it via `OLLAMA_HOST=http://inference:11434`. The inference container SHALL use the proxy for model downloads.
  46. 46Inference readiness is best-effort, never a launch gate crates/tillandsias-headless/src/main.rs#L18338-L18338Local inference readiness (order 392) is BEST-EFFORT, not a launch
  47. 47The single default chat model pulled on first run images/inference/entrypoint.sh#L120-L120DEFAULT_MODELS="${TILLANDSIAS_DEFAULT_MODELS:-qwen2.5:0.5b}"
  48. 48The engine download pins a version and architecture-specific digests images/inference/entrypoint.sh#L322-L328OLLAMA_VERSION="v0.34.0"
  49. 49The closed-vocabulary accelerator line every forge receives crates/tillandsias-headless/src/accel_probe.rs#L4553-L4553accel_class=<workstation-gpu|mobile-npu|hybrid-gpu-npu|cpu-only>
  50. 50Ready means the endpoint answers and at least one model is cached; there is no "starting up" images/default/lib-inference-state.sh#L14-L24Reason vocabulary (CLOSED SET — there is deliberately no "starting up"):
  51. 51Larger tier models are opt-in, not default images/inference/entrypoint.sh#L954-L957tier pulls opt-in (set TILLANDSIAS_INFERENCE_TIER_PULLS=1) — skipping runtime tier pulls
  52. 52The CPU floor is unconditional by spec openspec/specs/inference-container/spec.md#L154-L159Tier-S SHALL be available on every host regardless of GPU, NPU, driver, or engine availability, and SHALL NOT be gated on any device probe.
  53. 53A missing verifier or wrong engine digest prevents execution images/inference/entrypoint.sh#L435-L447FATAL: sha256sum unavailable — cannot verify the engine payload, refusing to execute it
  54. 54NVIDIA delivery is gated on a CDI spec; without one the host degrades to CPU with a loud remedy crates/tillandsias-headless/src/main.rs#L6939-L6939a GPU host without CDI degrades to CPU with a LOUD remedy
  55. 55The AMD lane is selected only when ROCm reports a graphics agent crates/tillandsias-headless/src/main.rs#L5886-L5886let rocm = std::process::Command::new("rocminfo")
  56. 56The spec: NPU rows are never attempted on the shipped engine openspec/specs/inference-container/spec.md#L186-L193N rows SHALL NOT be attempted on the `ollama` engine kind. Ollama cannot use any NPU
  57. 57On Windows the NPU cannot be seen from inside the guest at all scripts/windows-host-capability-probe.sh#L12-L14The NPU is not merely undetected in the guest, it is STRUCTURALLY invisible:
  58. 58The health check tests that the runner binary exists; the real load check runs once at startup images/inference/Containerfile#L149-L158A real generate would be the strongest assertion but is far too expensive at a
  59. 59NPU passthrough for the container is planned (planned, not started) plan/index.yaml#L9263-L9263Impl: NPU /dev/accel passthrough plumbing for the inference container (AMD XDNA2 first lane)
  60. 60The host-native sidecar for Metal and the NPUs is planned (planned, not started) plan/index.yaml#L9419-L9419Impl: host-native inference sidecar registry (macOS Metal/MLX, AMD XDNA2 flm/Lemonade, Intel NPU OpenVINO) behind the enclave proxy
  61. 61Agents configure nothing and still see the original GitHub URL openspec/specs/git-mirror-service/spec.md#L470-L476`git push`, `git fetch`, and `git clone` SHALL work with zero agent-side configuration
  62. 62One atomic push of explicit refspecs images/git/relay-refs.sh#L298-L298git push --atomic "$PUSH_URL" "$@"
  63. 63The mirror acknowledges only after upstream durably accepts the ref transaction images/git/pre-receive-hook.sh#L783-L783Push rejected: configured upstream did not durably accept the ref transaction
  64. 64The GitHub token is read from Vault at push time inside the git service and never crosses into a forge openspec/specs/git-mirror-service/spec.md#L14-L16The git service reads the GitHub token from Vault at push time via Vault CLI; the token never crosses into a forge container.
  65. 65The credential helper fetches the token from Vault over git's credential protocol images/git/git-credential-tillandsias.sh#L93TOKEN="$(vault-cli read -field=token secret/github/token 2>/dev/null || true)"
  66. 66Every start re-applies receive hardening: no deletes, no rewinds, object checks openspec/specs/git-mirror-service/spec.md#L98-L106the git service SHALL set `receive.denyNonFastForwards=true`, `receive.denyDeletes=true`, and `receive.fsckObjects=true`.
  67. 67A project with no upstream keeps pushes durably in the local-only mirror images/git/relay-refs.sh#L157-L157No upstream configured; accepting as a durable local-only mirror update
  68. 68Without a stored token the relay refuses and names the remedy images/git/relay-refs.sh#L193-L193HTTPS upstream credential is unavailable; run GitHub Login before pushing
  69. 69The mirror daemon's push path is anonymous inside the enclave, and the repo says so in the same breath as the hardening that limits the damage images/git/entrypoint.sh#L504-L504The daemon remains anonymous inside the enclave: any reachable peer can still
  70. 70The authenticated SSH push lane is dark behind one flag until the default flip crates/tillandsias-headless/src/main.rs#L14008-L14008std::env::var("TILLANDSIAS_MIRROR_SSHD")
  71. 71Offline or credential-less, the forge's push returns non-zero and nothing is partially applied openspec/specs/git-mirror-service/spec.md#L224-L230the forge's `git push` SHALL return non-zero
  72. 72The SSH-lane default flip is planned (planned, not started) plan/index.yaml#L20538-L20538T11+T12 — flip one lane behind a flag once the §4a matrix is green
  73. 74Vault is the default and only Linux secrets backend; GitHub token and short-lived per-container tokens openspec/specs/tillandsias-vault/spec.md#L11-L14Run a HashiCorp Vault container as the default and ONLY Linux secrets backend for Tillandsias.
  74. 75The shipped Vault listener is TLS-only images/vault/vault.hcl#L16-L24tls_cert_file = "/run/secrets/tillandsias-vault-tls-cert"
  75. 76Every OAuth-credentialed agent lane mounts one scoped Vault token crates/tillandsias-headless/src/main.rs#L19344-L19344Every OAuth-credentialed agent lane mounts a scoped Vault token so its
  76. 77Forge containers get no broad Vault token, only a provider-scoped short-lived one openspec/specs/tillandsias-vault/spec.md#L359-L364Forge containers SHALL NOT receive a broad Vault token.
  77. 78The generic forge policy cannot read the GitHub token images/vault/policies/forge.hcl#L1-L8Explicitly NO github or token access — forge containers must remain
  78. 79The tray policy reads the whole secret tree images/vault/policies/tray.hcl#L1-L6path "secret/*" {
  79. 80The startup context advertises http for a TLS-only Vault images/default/lib-common.sh#L5170-L5170http://vault:8200
  80. 81The in-image helper defaults to https and the tmpfs token path images/default/vault-cli.sh#L23-L24VAULT_ADDR="${VAULT_ADDR:-https://vault:8200}"
  81. 82Client tokens default to a one-hour TTL openspec/specs/tillandsias-vault/spec.md#L263-L267Every client token, including tokens minted by Vault Agent, SHALL default to TTL 1h with a maximum TTL of 24h.
  82. 83Four MCP servers registered for Claude Code inside the forge image images/default/config-overlay/claude/mcp.json#L2-L23"command": "/home/forge/.config-overlay/mcp/forge-plan.sh"
  83. 84The same four servers registered for OpenCode images/default/config-overlay/opencode/config.json#L66-L87"command": ["/home/forge/.config-overlay/mcp/forge-plan.sh"],
  84. 85Spec: the generic project expert bootstraps for any project with no registration or configuration openspec/specs/forge-environment-discoverability/spec.md#L171-L173The forge MUST bootstrap a project expert surface for an ARBITRARY mounted project without manual registration, configuration, or repository-type knowledge from the caller.
  85. 86The plan expert's refusal rule: zero citations means unsupported images/default/config-overlay/mcp/forge-plan.sh#L1367An answer with zero citations is returned as confidence=unsupported — the expert refuses rather than guesses.
  86. 87The plan expert is degraded by design on a project without the plan crate images/default/lib-common.sh#L5161-L5161(this project has no plan expert — expected off-tillandsias)
  87. 88Discovery, expert build and the grounded endpoint all backgrounded and fail-soft after the clone images/default/lib-common.sh#L1693-L1699ensure_forge_experts >>/tmp/forge-lifecycle.log 2>&1 || true
  88. 89The on-demand spec obliges the tray to detect and spawn servers openspec/specs/mcp-on-demand/spec.md#L32-L37the tray MUST detect that the filesystem MCP server is not running
  89. 90The startup context every lane reads names Local Experts mode images/default/lib-common.sh#L5180-L5180Local Experts mode (the
  90. 91Only the OpenCode configuration carries an agent bound to the grounded endpoint images/default/config-overlay/opencode/config.json#L18-L30"model": "tillandsias-experts/all",
  91. 92The pre-expert-binary trap: criterion 2 closed, the fresh-forge criterion still open (planned, not started) plan/index.yaml#L6796-L6873criterion 1 (release/branch decision) still OPEN — packet intentionally stays `ready` (partial reduction)
  92. 93The catalog is an allowlist: WEB only, refused host-side otherwise openspec/specs/enclave-service-catalog/spec.md#L22-L26Requests outside the catalog are refused host-side; the guest cannot mint categories.
  93. 94The web image: busybox httpd on 8080, document root /var/www openspec/specs/web-image/spec.md#L24-L25The image MUST serve static files from `/var/www` on port 8080 using busybox httpd with no additional packages or configuration.
  94. 95One socket per lane, bind-mounted into the forge; attribution comes from the listener that accepted the connection, never from the request openspec/specs/mcp-tool-socket/spec.md#L31-L64The project (and instance) a request acts on SHALL be derived from WHICH LISTENER accepted the connection. The tray SHALL NOT read the project from the request body, from the peer process's environment, or from any other peer-supplied source.
  95. 96The live publish path hands back an https URL for the www name on the router's TLS port crates/tillandsias-headless/src/local_web_preview.rs#L1194-L1200"https://www.{}.localhost:{}",
  96. 97From inside the forge, .localhost requests are forwarded by the proxy to the router images/proxy/squid.conf#L183-L188cache_peer_access tillandsias-router allow localhost_subdomain
  97. 98What could be read through the router when the earlier path served the repository root plan/index.yaml#L44421-L44421/.git/config returned 200 through the router (full history + remote URLs reconstructible)
  98. 99The stack sweep's name patterns: a published sibling matches none of them crates/tillandsias-headless/src/main.rs#L8662-L8662name.starts_with("tillandsias-git-")
  99. 100Projects must live under ~/src on the host; discovery hardcodes it (planned, not started) plan/index.yaml#L42106-L42106tray discover_projects hardcodes ~/src and run_opencode_mode starts no lane listener
  100. 101The declarative image set initialisation builds: both Chromium images and the web image among them crates/tillandsias-headless/src/main.rs#L11183-L11185"forge-base", "forge", "web",
  101. 102An ordinary forge launch ensures four images, not the web or browser ones crates/tillandsias-headless/src/main.rs#L33257-L33259== ["router", "git", "inference", "forge"]
  102. 103A failed first-use image build is remembered by its inputs, so the next attempt waits for the retry instead of repeating it crates/tillandsias-browser-mcp/src/launcher.rs#L39-L39longer while a failed build waits for its automatic retry
  103. 104The end-to-end publish litmus has never been run by any suite openspec/litmus-tests/unbound-grandfathered.txt#L21-L21litmus:publish-local-e2e
  104. 105The headless core image and its entrypoint images/chromium/Containerfile.core#L36-L37ENTRYPOINT ["/usr/lib64/chromium-browser/headless_shell", "--headless=new"]
  105. 106The framework image extends the core with GUI Chromium, Node and Playwright images/chromium/Containerfile.framework#L27-L47RUN npm install -g --prefix=/usr \ playwright \
  106. 107The hardened, ephemeral browser container the runtime launches crates/tillandsias-headless/src/main.rs#L16935-L16935Hardened browser boundary: read-only rootfs, CAP_DROP=ALL, no-new-privileges,
  107. 108A browser container reaches the host display over Wayland only; X11 is not offered crates/tillandsias-headless/src/main.rs#L16759-L16761The host graphical session the browser container may reach. WAYLAND ONLY
  108. 109Every browser window is the framework container started by the host; the host-Chromium path is gone crates/tillandsias-browser-mcp/src/launcher.rs#L3-L8a window is ALWAYS the chromium-framework container,
  109. 110A lane with no container launcher is told the browser is unavailable crates/tillandsias-browser-mcp/src/server.rs#L845-L849BROWSER_UNAVAILABLE: no browser container launcher is configured for this lane
  110. 111The only Chromium installer has no consumer scripts/install-chromium.sh#L10-L16INTEGRATION STATUS (2026-08-16 freshness audit, 774-cfw8): NO consumer
  111. 112Give the unused Chrome installer a caller, or remove it (planned, not started) plan/index.yaml#L22074-L22074scripts/install-chromium.sh has ZERO consumers while its header claimed two integrations; a litmus pins a shape nothing ships — wire it or tombstone it
  112. 113The allowlist accepts dotted project labels and compares them exactly crates/tillandsias-browser-mcp/src/allowlist.rs#L283-L290let host_project_label = labels[1..labels.len() - 1].join(".");
  113. 114Safe variant: proxy-only egress is the spec openspec/specs/chromium-safe-variant/spec.md#L16-L19Full network isolation inside the enclave, with allowlist enforcement via the proxy only; no host-gateway internet fallback
  114. 115The browser script defaults to the enclave network scripts/launch-chromium.sh#L103-L108"--network=${TILLANDSIAS_BROWSER_NETWORK:-${TILLANDSIAS_ENCLAVE_NET:-tillandsias-enclave}}"
  115. 116The router's host-port candidates: an explicit --port first, then 80 and the fallbacks crates/tillandsias-headless/src/main.rs#L7862-L7862let mut candidates = vec![80, 8080, 18080, 28080, 38080, 48080, 58080];
  116. 117Stack orchestration script now passes `--internal` and refuses to reuse an unisolated network scripts/orchestrate-enclave.sh#L82-L126Network $ENCLAVE_NET EXISTS BUT IS NOT INTERNAL — it was created without --internal, so every member has NAT egress and the proxy is not the only way out (order 972-a8vh, spec:enclave-network).
  117. 118The gate discovers tracked shell launchers and names its Rust limit scripts/check-enclave-network-internal.sh#L113-L135The shell side IS invocation-scoped: continuations are folded first, and the flag must appear on the same logical line as the create verb.
  118. 120The per-project runner creates an internal network scripts/run-forge-project.sh#L127-L130"$PODMAN_CTL" network create --driver bridge --internal --subnet "$ENCLAVE_SUBNET" "$ENCLAVE_NET" >/dev/null
  119. 121The proxy diagnostic creates an internal network scripts/diagnose-proxy.sh#L94-L97podman network create --driver bridge --internal --subnet "10.0.42.0/24" "$ENCLAVE_NET"
  120. 122A missing forge image makes the launcher refuse, never fall back to a host binary openspec/specs/forge-as-only-runtime/spec.md#L114-L115The tray MUST refuse to launch an agent if the forge image is missing — it MUST NOT silently fall back to a host binary.
  121. 123Clone-only by default: the host checkout is the opt-in path crates/tillandsias-headless/src/main.rs#L19197-L19197Order 437: clone-only by default.
  122. 124Forge containers clone from the mirror and push through it openspec/specs/git-mirror-service/spec.md#L54-L61Forge containers SHALL clone from `git://git-service/<project>`
  123. 125The reduced-isolation warning the host-mount escape hatch prints crates/tillandsias-headless/src/main.rs#L8399-L8399WARNING: WORKSPACE ENCLAVE ISOLATION IS REDUCED
  124. 126Inside a VM guest the share is written to a fallback file on every initialisation crates/tillandsias-headless/src/vault_bootstrap.rs#L3145-L3145Inside the VM there is no OS keychain, so these files are the only durable
  125. 127Where no OS keyring is reachable the share falls back to a file in the cache directory crates/tillandsias-headless/src/vault_bootstrap.rs#L5351-L5351using fallback file (expected in VM guest and headless environments)
  126. 128What the reset wipes on the host side: nothing; the function that used to name the vault data directory returns an empty list crates/tillandsias-headless/src/main.rs#L11476-L11478fn reset_guest_wipe_paths(_cache_dir: &Path) -> Vec<PathBuf> {
  127. 129The pin test: the model cache must never be in the reset wipe set crates/tillandsias-headless/src/main.rs#L35373-L35373the inference model cache must never be in the reset wipe set
  128. 130The host working-copy fast-forward is specified, and specified against a file that does not exist openspec/specs/git-mirror-service/spec.md#L344-L363The tray SHALL trigger a fast-forward attempt on the host working copy at `<watch_path>/<project>` for every successful push to the enclave bare mirror
  129. 131The VM boots with a second shared directory for the model cache crates/tillandsias-vm-layer/src/vz.rs#L3316-L3316tag: "model-cache".to_string(),
  130. 132The guest's first boot persists the model-cache mount crates/tillandsias-vm-layer/src/vz.rs#L1187-L1187model-cache /root/.cache/tillandsias/models virtiofs nofail 0 0
  131. 133The shared directory exists; migration and end-to-end survival are unproven plan/index.yaml#L27804-L27804guest wiring verified by source scan, MIGRATION UNVERIFIED.
  132. 134The shipped uninstaller preserves the VM unless asked to wipe scripts/uninstall.sh#L238-L238Preserving the VM image in $DATA_DIR (use --wipe to remove it).
  133. 135The uninstaller fix the macOS cache work waited on plan/archive/packets-2026-08.yaml#L23430-L23436shipped uninstall.sh deletes the 11.83 GiB VM directory with no --wipe, no root and no prompt
  134. 136The cold measurement and its limit for a populated model cache plan/index.yaml#L27838-L27838Re-fetched from the network: the Fedora Cloud qcow2 only, 514 MB.
  135. 137The wipe half landed; a host already holding a stale share is unaffected plan/index.yaml#L26924-L26924a host that ALREADY holds a stale credential is unaffected
  136. 138The delivery reply carries no accept-or-reject signal (planned, not started) plan/index.yaml#L37937-L37937DeliverCredentials reply says "I received it", never "I accepted it"
  137. 140Initialisation removes the orphaned global proxy block crates/tillandsias-headless/src/main.rs#L10197-L10197Remove the orphaned `[engine] env` proxy block.
  138. 141What initialisation prints when it converges the file crates/tillandsias-headless/src/main.rs#L10358-L10358removed the orphaned [engine] env proxy block from {} (923-rmtw); containers receive proxy env per-container
  139. 142The global-config fix, archived as completed plan/archive/packets-2026-08.yaml#L46813-L46818containers-conf-env-line-is-orphaned-and-never-converges
  140. 143The decision that removed the tray-managed host checkout plan/index.yaml#L16185-L16185It is REMOVED ENTIRELY.
  141. 145Fetch and pin the attestation bundles at image build time so no lane needs a GitHub identity (planned, not started) plan/index.yaml#L12320-L12320a forge lane installs an allowlisted tool with attestation verified and NO GitHub credential present anywhere in the lane
  142. 146The shared inference container is created only when it is not already running, so sibling forges share one crates/tillandsias-headless/src/main.rs#L18384-L18385inference is recreate-if-not-running (a --replace would drop loaded models and interrupt a sibling's inference mid-flight).
  143. 147The test pinning the scoped lease for every credentialed lane, and its absence from the credential-free ones crates/tillandsias-headless/src/main.rs#L30798-L30798Credential-free lanes never mount a provider lease.
  144. 148The check gate invokes the membership guard and fails the build when it refuses build.sh#L3581-L3581if ! _run_lua_decider "scripts/lua/check-enclave-membership-documented.lua" 2>&1; then
  145. 149Only the clone-only lane mounts the RAM-backed source volume; the host-mount arm binds the checkout instead crates/tillandsias-headless/src/main.rs#L19209-L19220forge_ram_workspace_volume(project_name),
  146. 150The context distinguishes waiting, relaunching and relaunch regression images/default/lib-common.sh#L5159-L5168\`relaunch-regresses\` means the running binary has MORE than the checkout and a relaunch would REMOVE capability.
  147. 151Ready inference alone does not provide the project index with a synthesis tier images/default/config-overlay/mcp/project-info.sh#L789-L789no synthesis tier is wired into project_answer yet
  148. 152The upstream token is read by the git service at push time and never enters a workspace container openspec/specs/git-mirror-service/spec.md#L15-L16The git service reads the GitHub token from Vault at push time via Vault CLI; the token never crosses into a forge container.
  149. 153The relay is synchronous: local acceptance follows upstream acceptance images/git/pre-receive-hook.sh#L6-L8Validates ledger YAML, then synchronously relays the proposed ref transaction upstream before accepting it locally. A client success therefore means the configured upstream has durably accepted the same atomic ref set.
  150. 154The live relay pushes the refs as one atomic transaction images/git/relay-refs.sh#L298-L298if OUTPUT="$(GIT_TERMINAL_PROMPT=0 git push --atomic "$PUSH_URL" "$@" 2>&1)"; then
  151. 156What a soft reset destroys and what it keeps crates/tillandsias-headless/src/main.rs#L11639-L11649<cache>/tillandsias/models and every other download in the cache
  152. 157The uninstaller asks for a typed confirmation before removing anything scripts/uninstall.sh#L185-L185Type \"delete\" to uninstall, or press Enter to cancel:
  153. 158The lifecycle spec's amendment: store survival requires an unlocking keyring, no persisted fallback share openspec/specs/host-state-lifecycle/spec.md#L13-L18survival of the Vault store REQUIRES an unlocking keyring — no persisted fallback share
  154. 159The launcher resolves the model directory under the cache root crates/tillandsias-headless/src/main.rs#L6823-L6826&tillandsias_core::cache_root::cache_root().join("models"),
  155. 160The preview never mounts .git, .env or the forge root; it selects a document tree or named assets crates/tillandsias-headless/src/local_web_preview.rs#L1020-L1027Static compatibility selects a public document tree, never mounts .git/.env/the forge root into a sibling.
  156. 161A departed lane's preview and route are removed crates/tillandsias-headless/src/local_web_preview.rs#L2293-L2295Called only after the launcher proves no forge remains for this lane.
  157. 162The preview spec's own status openspec/specs/local-web-preview/spec.md#L7-L10verification: S0 (design; executable coverage and live evidence pending)
  158. 163The plan item to prove the preview live and activate the scoped specs plan/index.d/20261007-local-web-preview-design-yoga.yaml#L79-L79title: Prove live tillandsias.org preview and activate scoped specs
  159. 164Publishing builds a missing image instead of refusing; a test pins that the old refusal is gone crates/tillandsias-headless/src/local_web_preview.rs#L3204-L3211the init refusal names a step nothing runs; publish builds instead
  160. 165Headless when asked, or by default on a host with no display crates/tillandsias-browser-mcp/src/server.rs#L769-L769Order 1712-3fsw: headless when asked, or by default when this host
  161. 166The spec: evaluation is disabled in this version openspec/specs/host-browser-mcp/spec.md#L407-L412### Requirement: browser.eval is disabled by default in v1
  162. 167The code: evaluation is refused outside a development-mode window crates/tillandsias-browser-mcp/src/server.rs#L976-L980EVAL_DISABLED: browser.eval needs a development-mode window; open it with browser.open{url, dev: true}

The architecture interrogated rather than described — boundaries, egress, provenance, and what the tests do not actually test.Assumes the three levels before it.

Tillandsias, for an IT / security reader

Silvery leaf scales open to take water in, then trap air to keep it: every exchange across one surface.

Where the boundary actually is

The anatomy does not answer the security question: when an agent does something you did not intend, how far does it get?

First, a correction. The region was described as one Fedora guest — a VM, or WSL2 on Windows. True on two platforms, false on the third: Linux provisions no VM, and the orchestrator drives rootless containers directly on your host.1 The promise is that this costs nothing but Podman; the price is that the enclave is a hypervisor boundary on macOS and Windows and a namespace boundary on Linux — where an escape lands as your own uid, with your $HOME.2

Inside, every launch is meant to carry four flags — userns mapped to your uid, all capabilities dropped, no-new-privileges, and label-disable, which is to say SELinux labelling off on every container — audited by a policy module that rejects privileged mode, a non-identity userns, or a blanket capability add.498 Why this envelope: keep-id makes an escape land as you rather than root, and a checker that never runs Podman is one small place to ask whether a launch still carries it. The compromise is the fourth flag, bought so bind mounts work on an SELinux-enforcing host.9294 Read the envelope as defaults on a struct, not an invariant — and not quite every container: the vault adds one capability back, omits --rm, and never passes the checker at all.53

checked The threat model is written down, including the parts that make the design look bad — the architecture notes state plainly that the allowlist is generous by design and that an attacker can encode data in DNS queries or headers to an allowed domain.3

The litmus now derives its launch flags from the product's declared envelope.58 Its repaired user-namespace probe checks ownership of a host-owned mounted file, because inspecting the namespace label or comparing the process uid alone could give a misleading answer.100 That is evidence about the declared envelope and Podman on the tested host, not proof that every production launch uses it.

checked Production hardening checks now survive release compilation: the checked serializer returns an error for a stripped envelope, and both attached and delegated forge launch paths refuse it.6555657 The general capability-add escape hatch has also been removed from the typed container builder.27 Before this release the check was only a debug assertion, and the security litmus printed success on both branches.7

known limitation Enforcement is still incomplete across launch paths: proxy, mirror, observatorium and a host-browser launch assemble or serialize arguments without the policy check, and the Podman client accepts raw arguments.6490959697 The typed builder retains private boolean fields, although its public constructor turns all four on and exposes no setters to turn them off.63 A separate test defect remains: the capability probe treats empty output as success without checking whether the inspection ran, so a failed podman exec can still print CAPABILITIES_DROPPED.101

what happens next The checked serializer and forge callers are the enforcement mechanism that exists; extending it to every launch is not implemented.5565 The project's build rejects a litmus test whose success and failure print the same thing,6061 but that check does not catch the empty-output failure above,62 and the project's other test-quality checks are partial too.854

does not hold "The litmus proves every launch is hardened." It launches a probe from declared flags, leaving production call-site coverage to other checks; even its cleanup step reports success unconditionally.5859

known limitation SELinux confinement is disabled on every forge, so on a Fedora or RHEL host the agent's container is less confined than a default one. The project does confine elsewhere — the vault carries a loadable policy module,50 the Windows guest embeds policies for the control daemon and the vault51 — but on a rootless Linux host the vault too runs unconfined.52

what happens next MCS labelling of the forge is recorded as a planned later phase — intent, with no implementation and no litmus behind it.5 For the missing hypervisor on Linux no design tradeoff is recorded, only the blast radius of an escape — your uid, your $HOME2 — mitigated by defence in depth rather than a stronger boundary.

not yet shown Defence in depth is argued layer by layer below; nothing demonstrates that the layers compose to bound an escape.

Egress: intercepted versus passed through

The intended egress boundary is network placement plus proxy environment variables. The proxy configuration supplies hostname rules; it does not establish that the host or container networking stack uses no packet filters. An agent can unset HTTP_PROXY; what stops it is that the namespace has nowhere to send the packet. Then the question everyone asks on hearing "trusted CA in the container":

the boundary the specification draws must hold must NOT hold positive cases negative controls
A test that only ever passes proves nothing. Each boundary is pinned from both sides.

checked This is not a blanket MITM. The proxy peeks at the handshake and then splices — passes through undecrypted — everything but one exact hostname, GitHub's release-asset CDN, bumped so large binaries can be cached. Registries, GitHub's APIs and every auth endpoint stay end-to-end encrypted.9

shown The policy is three lines of a config file you can read, and the bump is scoped to one client-requested server name.9

The cost of that restraint: with one host decrypted the proxy does no payload inspection — hostnames, not content — and its default-deny allowlist admits anyone-can-publish namespaces, so an allowlist hit is no evidence of a benign destination.330 What it buys is a denial that looks like a network fault: a refused runtime request gets a TCP reset, not a page to negotiate with — the config's behaviour; the spec still promises a 403.3738

checked Where it does terminate TLS it still verifies upstream against the system trust store, and the config carries a written rule never to disable peer or domain verification — that would hide a real origin MITM behind a proxy-issued certificate.10

checked The permissive port now binds to loopback inside the proxy container, so an enclave peer cannot reach it by the proxy's network address.31 Its access rules remain permissive for a process inside that container,3233 and the decision to use it for builds or remove it remains open.3536

checked The stable release now puts the CA bundle under per-user persistent state instead of /tmp, so reboot cleanup no longer removes its declared location.284391 On Unix, new key material is clamped to owner-only before publication, and the existing-key path attempts the same repair; the non-Unix helper is a documented no-op.394041 This addresses the code side of a defect whose earlier fix had not reached material already created on hosts.11

known limitation Moving the directory does not make its permissions private by construction: the launcher still creates it with ordinary recursive directory creation.102

what happens next The planned fix is to create the directory privately before any key is written.45 It has not landed.

known limitation The published proxy spec describes something the code does not do: a fresh two-level EC P-256 chain generated per launch, held only on tmpfs, no CA key ever touching disk.12 The shipped launcher generates one self-signed RSA-2048 certificate with a 30-day life, stored in persistent per-user state and reused until refresh is needed.1328102

what happens next No fix is planned yet: the spec has not been marked stale, and nothing reconciles it with the code.

Secrets, and the channel that guards them

The GitHub token never enters the agent container: the mirror holds short-lived role credentials, relays pushes on the agent's behalf, and passes the credential on stdin rather than argv, closing a process-listing leak. Credential material that crosses the host–container boundary at all — the vault's unseal key, per-container tokens — is no longer carried as a podman secret, because podman's file driver, and the copy it keeps for every container, are plaintext on durable disk; each is a file on tmpfs for the length of the launch.120121 The relay can invoke its helper for an HTTPS origin,48 but the helper now checks the credential request's protocol and exact host before returning a token.49

checked The mirror holds the upstream credential and mediates pushes; the agent-facing write path is separate from that credential-bearing relay.144849

does not hold "Exfiltrating a token requires compromising both the mirror and the proxy." The architecture says this,3 but the proxy forwards permitted destinations even though its permissive port now binds to loopback.3031 A compromised mirror could use an allowed destination without compromising the proxy process. Network placement restricts routes; it does not prove that an allowed request cannot carry a secret.

known limitation The write path the agent pushes to is an anonymous git daemon receive-pack listener. The spec is blunt: network placement SHALL NOT be described as client authentication — any process on the enclave network can write to any mirror.14

Why the relay is bespoke, in security terms. The design needs the upstream credential to stay server-side and the push to be synchronously durable, and those two requirements pull against the available tools. A managed push mirror holds the credential and copies asynchronously, so it reports success before the copy lands; a caching or redirecting git proxy relays synchronously but forwards the client's credentials, dissolving the separation this section is about. The hook takes the third option: it relays the proposed ref transaction upstream before accepting it locally, so a client's success means the upstream durably accepted the same atomic ref set.107

That is a security property and not only a convenience. A false success is indistinguishable, to the agent that received it, from work that is safely published — so an asynchronous mirror would put unpublished work behind a green light, on exactly the disposable machine the design expects to be thrown away.

what happens next The spec labels it interim and constrains what may be built on it, but records no dated authenticated replacement.14 That replacement is built and dark: an SSH-CA push lane exists in the mirror image and the launcher, behind an environment variable that defaults off and that nothing in shipped packaging sets.4647 Separately, the mirror's push hook can now refuse a push to the default branch, or to a new branch outside a ref grammar, when a project's branch-discipline seed says to enforce it; that is policy over anonymous writers, not authentication of them, and the seed is read from the repository's own integration branch.117

The host↔guest control channel now uses a Noise handshake by default. Its key is derived through HKDF from the guest binary's SHA-256, separated by build version, wire version and hop.15103 This binds compatibility to known release material; it is not remote attestation that the peer runs unmodified code.

note This says what the relay refuses to report, not that the credential

boundary is complete. The RED above about the anonymous receive-pack listener is

the live gap on the agent-facing side of the same path, and it is not closed by

anything in this paragraph.

A release exposed a second boundary: the tray and guest are different executables, so hashing each process's own binary produced different keys even at the same version. The host now derives its key from the guest digest embedded at build time, while the guest hashes itself.103104 The Windows caller refuses a missing embedded digest and includes tests for matching and mismatched guest digests.105106 A one-process round-trip test could never have detected the original two-binary mismatch; that limitation is now recorded beside the implementation.104

checked An absent secure-wire setting now selects encryption, blank or invalid values are refused, and the Windows tray uses the same parser as the other callers.16666769 A build check refuses any caller that reads the setting on its own.68 The macOS host writes the resulting value into the guest unit.93 Windows and the VM image need no explicit environment line to obtain the new default from the listener.7071

known limitation Encryption is on by default, but caller identity remains weaker than operator authentication: anyone with the published guest bytes and version inputs can derive the same key. The guest's debug path instead uses a public constant.1529103 Explicit off still enables plaintext.16 Neither possession of the digest nor a completed handshake proves the caller is the legitimate orchestrator.

what happens next The maturity ladder still records deletion of the insecure path as a later stage.17 Mixing in a per-boot host secret is approved and deferred; the present derivation does not include it.18103

Supply chain and provenance

checked Release artifacts are signed with Sigstore cosign keyless — no long-lived key, identity asserted by the CI OIDC token, inclusion recorded in a public transparency log — and the Linux and Windows asset sets are checked for transitive coverage as a property over whatever is staged, not over a remembered list.197274

known limitation The signature proves less than it looks like. Verification matches an identity regexp pinning only the repository name — not the owning organization, the workflow, or the ref — so a fork of the same name signing through the same CI provider satisfies it, in the published command and in the shipped verifier alike.1978 Beyond that there is no SBOM, no build-provenance attestation, no SLSA or in-toto predicate, no container-image signing; base images are pinned by mutable tag rather than digest, so the image you build tomorrow is not the one you built today.20

what happens next Recorded only as research — a study of another distribution channel shows such provenance verifies with no verifier identity at all, and recommends shipping bundles plus a pinned root.2180 Nothing has been adopted here.

known limitation The macOS lane is the exception: it signs an allow-list of three named assets and runs no integrity check, so its checksum manifest ships without a bundle73 — the allow-list shape the Windows lane's own comment blames for an installer once shipping bare.74

what happens next No fix is planned yet.

known limitation Nothing a user runs by default checks a signature; only the hand-run verifier does. The install scripts fetch the checksum manifest over the same channel as the binary and on Linux continue without it;757677 no install path verifies a cosign bundle, and the updater's spec asks for no verification.79 The Windows installer verifies a SHA-256 taken from the manifest it fetched beside the zip and nothing else.76

what happens next An open defect asks the Windows installer to verify the manifest's cosign bundle before trusting it and to refuse a bad signature; it is not fixed in this release.115 Another asks that the install entry point either verify its own bundle or state in writing that transport trust is the anchor.116 No fix is planned for the updater.

Where ephemerality stops being a control

Ephemerality resets compute, not identity. Of what survives teardown, the security-relevant item is the Vault unseal share, which lives in the host OS keychain — outside every boundary the enclave draws.22 The reason is fair — no passphrase prompt, never wipe a vault the host can still open — but destroying the region does not destroy the ability to open what it held.

known limitation The application's Linux --reset-state no longer clears host-held Vault credentials. It asks the keyring first, announces what it keeps, and leaves the Vault store and both keyring entries in place under every outcome.81111 That is deliberate: a reset renews compute and keeps identity; the only code that deletes the unseal material is a clearer reserved for an uninstall command that no code calls yet.110 A bare podman system reset still clears only Podman state. We have not re-checked the macOS and Windows resets for this release.

what happens next The clearer's own documentation names the uninstall command as its caller, and that command is not in the binary; until it lands, nothing shipped removes the share on request.110

known limitation The keychain is not the only copy. The vault spec now says the share lives only in an unlocking keyring and that there is no persisted fallback share file,109 yet where no OS keyring is available the launcher still writes the share to a plaintext file in its cache directory,83 and inside a VM guest it writes that file on initialization.84 Only a hand-run clean-room script clears those fallback files;23 the ordinary key lookup can still use a persistent file8586 while the spec says persistent on-disk copies are deleted immediately.87

what happens next Replace the persistent fallback with the ephemeral handoff the spec now describes, or revise the spec to state the actual lifetime; no completed remedy for ordinary operation is recorded here.

Blast radius, autonomy, auditability

An agent's reach: its forge, every mirror on the enclave network, any allowlisted hostname with nothing inspecting the payload. Not your keychain, $HOME, or real checkout — unless you took the host-mount escape hatch.

checked The browser an agent can drive is a container started by the host, never a host browser; a build check refuses any code path that would hand a URL to one.118 Arbitrary JavaScript evaluation is refused unless the same lane opened the window in development mode.24

The project's record of what it checked is committed and candid. It is also written by its own agents and unsigned, with no commit signing in evidence; the local gate binds a one-shot pass token to the tree it checked.99 With nothing in the repository showing server-side validation of a push, that record's provenance rests on the honesty of the process that wrote it.

known limitation The browser spec still says evaluation is disabled outright in this version. The code enables it for development-mode windows, and the spec has not been updated to say what that mode may reach.11924

What the assurance claim actually is

The convergence argument you already have is not a security argument, and the project does not offer it as one. What carries the weight is a pair of invariants — verification claims must be falsifiable, and evidence is not proof25 — plus the methodology's refusal to read its completion score as a probability.26 So: a passing suite is a bounded signal over the defects someone thought to write a litmus test for, and, as the hardening case shows, only over those whose tests distinguish the relevant failure from success. Finite litmus coverage is not proof of absence of defects; the repo says so before you do.

How we know — 111 sources

Each small number in the text opens the exact lines of the Tillandsias source that back that sentence, as they stand in release v56.10.20.1, the one the install commands give you. Hover a number to read the quoted lines without leaving the page. A source marked with its own release points at a newer one. The links were moved to release v56.10.20.1 automatically; a person last checked the claims themselves against v56.10.9.1.

  1. 1Podman the only host dependency on Linux; macOS/Windows provision a VM README.md#L52-L53Podman is the only host dependency on Linux (auto-detected). macOS and Windows provision a lightweight Fedora-based utility VM; no host Podman required.
  2. 2Escape lands as the invoking user's UID on the host, with `$HOME` only openspec/specs/podman-idiomatic-patterns/spec.md#L220-L221- **THEN** the escaped process runs as the invoking user's UID on the host, not as root - **AND** it has access only to `$HOME` and user-owned resources
  3. 3Enclave architecture — attack scenarios and stated limits docs/cheatsheets/enclave-architecture.md#L217-L225The proxy allowlist is generous by design — a determined attacker could encode data in DNS queries or HTTP headers to an allowed domain.
  4. 4The mandatory hardening envelope and the argv policy checker crates/tillandsias-podman/src/policy.rs#L14-L22pub const MANDATORY_HARDENING_FLAGS: [&str; 4] = [ "--userns=keep-id", "--cap-drop=ALL", "--security-opt=no-new-privileges", "--security-opt=label=disable", ];
  5. 5SELinux MCS labelling recorded as a planned future phase openspec/specs/default-image/spec.md#L768-L769future release will apply SELinux MCS labels to the forge domain, adding mandatory access control at the boundary layer.
  6. 6Checked serialization refuses an invalid launch in every build profile crates/tillandsias-podman/src/container_spec.rs#L478-L479crate::policy::validate_launch_argv(&argv)?; Ok(argv)
  7. 7Historical false-pass defect and its corpus-wide guard openspec/litmus-tests/litmus-podman-idiomatic-security-flags.yaml#L7-L13Both branches printed what the harness was looking for, so the greps decided nothing
  8. 8The false-pass scanner: advisory, no caller gates on it, skips over half the corpus, keyed on a non-zero exit scripts/scan-litmus-false-pass.sh#L8-L48Advisory — no caller gates on it.
  9. 9Peek at step 1, bump one exact hostname, splice everything else images/proxy/squid.conf#L122-L139ssl_bump peek ssl_bump_step1 ssl_bump bump github_release_assets ssl_bump splice all
  10. 10Upstream verification is kept, with a written rule against disabling it images/proxy/squid.conf#L88-L97verification MUST remain enabled. Never add DONT_VERIFY_PEER or DONT_VERIFY_DOMAIN: doing so would hide an origin MITM behind a proxy-issued certificate that enclave clients trust.
  11. 11Host-side CA re-clamp, and why a code fix alone did not repair key material already created scripts/clamp-ca-material.sh#L26-L54Two hosts were found with a world-readable CA private key days after the packet closed
  12. 12Spec: per-launch EC P-256 root-and-intermediate chain on tmpfs openspec/specs/proxy-container/spec.md#L63-L74The system SHALL generate a fresh two-level CA chain on every proxy launch. The chain SHALL consist of a self-signed Root CA and an Intermediate CA signed by the root, both using EC P-256 keys. All key material SHALL be stored on tmpfs
  13. 13Launcher generates self-signed RSA-2048 CA with a 30-day life crates/tillandsias-headless/src/main.rs#L4266-L4269"req", "-x509", "-newkey", "rsa:2048",
  14. 14Anonymous receive-pack listener; network placement is not authentication; owners named, no date openspec/specs/git-mirror-service/spec.md#L52-L72Network placement SHALL NOT be described as client authentication: any enclave peer can still create or fast-forward refs and cause the privileged relay to carry them upstream.
  15. 15Noise PSK derived from the binary's own hash crates/tillandsias-secure-channel/src/lib.rs#L112-L114let exe = std::env::current_exe().expect("current_exe for self-hash"); let bytes = std::fs::read(&exe).expect("read self binary for hash"); Sha256::digest(&bytes).to_vec()
  16. 16Absent means encrypted; plaintext requires explicit off crates/tillandsias-control-wire/src/secure_wire_mode.rs#L85-L131Err(std::env::VarError::NotPresent) => Ok(SecureWireMode::On),
  17. 17The maturity ladder from default-OFF to secure-by-default to removing the plaintext path — status active plan/issues/secure-channel-maturity-ladder-2026-07-04.md#L1-L12implement the e2e encrypted socket channel in ALL places, enable it at runtime with a flag, then advance through STABLE VERIFIABLE MATURITY GATES to secure-by-default and finally to removal of the insecure path.
  18. 18Per-boot key hardening — approved, deferred, unimplemented plan/issues/encrypted-channel-perboot-key-hardening-2026-07-01.md#L1-L22This packet is the operator-approved **later hardening**: mix in a **per-boot secret** the host controls, so a leaked release secret alone no longer lets an attacker with a matching-release binary attach across a different VM boot.
  19. 19Keyless signing recorded in the Rekor transparency log, and the verification identity regexp that pins the repo name only .github/workflows/release.yml#L376-L376--certificate-identity-regexp "https://github.com/.*/tillandsias/"
  20. 20Base images pinned by mutable tag images/git/Containerfile#L19-L21FROM docker.io/hashicorp/vault:1.18 AS vault-agent FROM docker.io/library/alpine:3.20
  21. 21Research: such provenance verifies with no verifier identity; it recommends bundles plus a pinned root (not adopted) plan/index.yaml#L12318-L12318verifying a Sigstore/SLSA attestation requires NO identity from the verifier. Reproduced with no token, no gh config, and inside a network namespace with no interface at all: exit 0, real certificate chain, correct signer; one flipped byte gives exit 1.
  22. 22Vault unseal share stored in the host OS keychain openspec/specs/tillandsias-vault/spec.md#L115-L118The unseal key (the single Shamir share generated by Vault during initialization) SHALL be stored directly in the host OS's native secure keychain (Secret Service/KWallet on Linux, Credential Manager on Windows, Keychain on macOS) under the versioned name `vault-shamir-share-v1`.
  23. 23Linux clean-room reset clears host credentials and probes the result scripts/e2e-step2-linux.sh#L15-L38"$SCRIPT_DIR/clear-vault-host-credentials.sh" 2>&1 | tee "$LOG_DIR/02-clear-credentials.log" || true
  24. 24Evaluation is refused outside a development-mode window the lane opened crates/tillandsias-browser-mcp/src/server.rs#L976-L980EVAL_DISABLED: browser.eval needs a development-mode window; open it with browser.open{url, dev: true}
  25. 25Falsifiability and evidence-is-not-proof invariants methodology/philosophy.yaml#L69-L71- verification_claims_must_be_falsifiable: true - convergence_requires_stability: true - evidence_is_not_proof: true
  26. 26Bounded ranking function, explicitly not a probability methodology/math-foundations.yaml#L91-L125CentiColons are a finite bounded ranking function.
  27. 27General capability-add field and builder method removed crates/tillandsias-podman/src/container_spec.rs#L85-L100there is deliberately no `cap_add`. The field existed as an unvalidated pass-through
  28. 28Shared CA path accessor composes the ca leaf under the state root crates/tillandsias-core/src/ca_path.rs#L37-L51format!("{}/ca", ca_template())
  29. 29Debug builds derive the channel key from a fixed, public seed crates/tillandsias-secure-channel/src/lib.rs#L63-L68Fixed, non-secret dev seed used in debug builds. It lets a locally-built host + guest of the *same* tree interoperate without a release build. It is intentionally NOT a secret
  30. 30The allowlist table: package registries and cloud namespaces anyone can publish to docs/cheatsheets/enclave-architecture.md#L144-L156| Package registries | `registry.npmjs.org`, `crates.io`, `pypi.org`, `rubygems.org` | Package installation |
  31. 31The permissive port binds only to loopback in the proxy container images/proxy/squid.conf#L70-L78http_port 127.0.0.1:3129 ssl-bump
  32. 32Permissive port: allow everything, no source restriction images/proxy/squid.conf#L146-L149http_access allow CONNECT SSL_ports build_port http_access allow build_port
  33. 33Spec: on the permissive port domain filtering does not apply openspec/specs/proxy-container/spec.md#L114-L114- **AND** domain filtering SHALL NOT apply
  34. 35The port is kept until a decision is made, not deleted images/proxy/squid.conf#L39-L44That is an operator decision, not a cleanup. Until it is taken, this comment is the honest state.
  35. 36Network audit: the permissive-port decision still open plan/issues/network-architecture-audit-2026-07-09.md#L531-L533**STILL OPEN as a decision**, with the false claim removed and the agreement now enforced
  36. 37Denied runtime traffic gets a TCP reset rather than a 403 images/proxy/squid.conf#L148-L153Send a TCP reset instead of an HTTP 403 error for strictly-denied runtime traffic.
  37. 38The spec still promises an HTTP 403 for denied domains openspec/specs/proxy-container/spec.md#L109-L109- **AND** all other domains SHALL be denied with HTTP 403
  38. 39The owner-only clamp on the CA key, in the stable release's source crates/tillandsias-headless/src/main.rs#L4151-L4153fn enforce_ca_key_mode(key: &Path) -> std::io::Result<()> { use std::os::unix::fs::PermissionsExt; std::fs::set_permissions(key, std::fs::Permissions::from_mode(0o600))
  39. 40On non-unix targets the clamp is a documented no-op crates/tillandsias-headless/src/main.rs#L4171-L4173This is deliberately NOT a security regression on Windows: the key is not protected by mode bits there in the first place, and the enclave's Windows path receives it as a podman secret rather than through this file.
  40. 41Pre-fix keys are healed down to owner-only on every pass crates/tillandsias-headless/src/main.rs#L4355-L4357Heal DOWN to 0600 every call so keys generated before this fix (deliberately world-readable 0644) are repaired without requiring a CA rotation.
  41. 43State root is persistent and explicitly shared between consumers images/default/ca-path.txt#L1-L83${HOME}/.local/state/tillandsias
  42. 45The planned fix: make the CA directory private by construction plan/index.yaml#L26013-L26013CA material is created in a directory that is private by construction
  43. 46The authenticated push lane is gated on an environment variable that defaults to off crates/tillandsias-headless/src/main.rs#L14008-L14010std::env::var("TILLANDSIAS_MIRROR_SSHD") .map(|v| v == "1") .unwrap_or(false)
  44. 47The mirror's sshd starts only behind that flag images/git/entrypoint.sh#L524-L525Behind TILLANDSIAS_MIRROR_SSHD=1 until the T11 staged migration flips the default.
  45. 48The relay wires the GitHub credential helper for any HTTPS origin images/git/relay-refs.sh#L154-L183case "$REMOTE_URL" in https://*)
  46. 49The helper checks exact GitHub hosts and refuses others images/git/git-credential-tillandsias.sh#L69-L83github.com|api.github.com) ;;
  47. 50The vault container carries an embedded SELinux policy module crates/tillandsias-headless/src/vault_bootstrap.rs#L4543const VAULT_SELINUX_CIL: &str = include_str!("../../../images/selinux/vault_container.cil");
  48. 51The Windows guest embeds policies for the control daemon and the vault crates/tillandsias-windows-tray/src/wsl_lifecycle.rs#L311const SELINUX_VAULT_TE: &str = include_str!("../../../images/selinux/tillandsias_vault.te");
  49. 52On a rootless Linux host the vault too runs unconfined crates/tillandsias-headless/src/vault_bootstrap.rs#L4642`label=disable` runs the vault container unconfined on the host
  50. 53The vault launch adds a capability back and omits `--rm` crates/tillandsias-headless/src/vault_bootstrap.rs#L5043-L5046"--cap-drop".into(), "ALL".into(), "--cap-add".into(), "IPC_LOCK".into(),
  51. 54A test-quality check that examines only new tests plan/index.yaml#L14455-L14455the check is DIFF-SCOPED BY CONSTRUCTION (examines added steps in the outgoing change only) and is structurally incapable of flagging the existing corpus
  52. 55Serialising a launch now refuses an argv that violates the envelope, in every build profile crates/tillandsias-podman/src/container_spec.rs#L478-L479crate::policy::validate_launch_argv(&argv)?; Ok(argv)
  53. 56The attached forge launch refuses on a hardening violation crates/tillandsias-headless/src/main.rs#L15845"refusing to launch {container_name}: hardening envelope violation: {err}"
  54. 57The delegated launch path refuses too crates/tillandsias-headless/src/main.rs#L15673[security] refusing to launch delegated {container_name}: {err}
  55. 58The rewritten litmus derives its launch flags from the product's declared envelope openspec/litmus-tests/litmus-podman-idiomatic-security-flags.yaml#L23-L25So the launch argv is now DERIVED from the product's own declared envelope, `MANDATORY_HARDENING_FLAGS` in crates/tillandsias-podman/src/policy.rs
  56. 59The ninth step, cleanup, still prints its token unconditionally openspec/litmus-tests/litmus-podman-idiomatic-security-flags.yaml#L131-L134expected_behavior: "CLEANED"
  57. 60A second scanner refuses litmus steps that print the same token on both branches scripts/lua/check-litmus-steps-can-fail.lua#L7-L9step whose success and failure branches print the SAME token, because such
  58. 61The build gate fails on it build.sh#L6452a litmus step prints the same token on success and failure
  59. 62The second scanner refuses only the identical-token shape, by design scripts/lua/check-litmus-steps-can-fail.lua#L31-L32Only identical tokens are refused, because only then is
  60. 63Private flags initialized on; serialization remains conditional crates/tillandsias-podman/src/container_spec.rs#L142-L145userns_keep_id: true, cap_drop_all: true, no_new_privileges: true, label_disable: true,
  61. 64The observatory's browser container still launches through the unchecked serialiser crates/tillandsias-headless/src/main.rs#L15250-L15253let browser_container_name = format!("tillandsias-browser-observatorium-{project_name}");
  62. 65The repo's own admission that enforcement depends on call order crates/tillandsias-headless/src/main.rs#L15654-L15656An invariant that depends on call order is one refactor from being false, and this one decides whether a container runs unhardened.
  63. 66The one shared reader of the flag: a blank value is refused crates/tillandsias-control-wire/src/secure_wire_mode.rs#L85-L126is set but empty. Blank does NOT mean
  64. 67Windows tray uses shared mode reader and refuses bad values crates/tillandsias-windows-tray/src/hvsocket.rs#L74-L87let mode = match tillandsias_control_wire::secure_wire_mode::secure_wire_mode() { Ok(mode) => mode, Err(err) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, err)), };
  65. 68A build check allows no independent reader of the secure-wire setting scripts/lua/check-secure-wire-single-reader.lua#L6-L8BASELINE is 0 today: every reader was converted (972-umik commit B), so the ratchet is now effectively a refusal.
  66. 69Test pins the encrypted default through the shared parser crates/tillandsias-control-wire/src/secure_wire_mode.rs#L159-L165parse_secure_wire_mode(Err(VarError::NotPresent)).unwrap(), SecureWireMode::On,
  67. 70The Windows guest's service unit carries no secure-wire line crates/tillandsias-windows-tray/src/wsl_lifecycle.rs#L1986-L1987Environment=TILLANDSIAS_VAULT_API_BASE_URL=https://vault:8200
  68. 71The VM image's default service unit carries no environment at all images/vm/bootstrap/20-tillandsias.sh#L92-L104ExecStart=/usr/local/bin/tillandsias-headless --listen-vsock 42420
  69. 72The Linux lane asserts transitive coverage as a property over what is staged .github/workflows/release.yml#L341-L343This asserts the PROPERTY over whatever is actually staged, so an asset added later cannot slip through unverifiable just because nobody added a line for it.
  70. 73The macOS lane signs an allow-list of three assets, and its checksum manifest is not among them .github/workflows/release.yml#L597for artifact in tillandsias-tray-*-macos-arm64.tar.gz Tillandsias.dmg install-macos.sh; do
  71. 74The Windows lane's coverage check, why it follows the transitive path, and the allow-list it blames for an installer shipping bare .github/workflows/release.yml#L912-L913The check follows the TRANSITIVE path rather than counting unsigned files
  72. 75The Linux installer fetches the manifest over the same channel and continues without it scripts/install.sh#L264say "SHA256SUMS not available; skipping checksum verification."
  73. 76The Windows installer compares a hash from the fetched manifest only scripts/install-windows.ps1#L852-L857SHA-256 mismatch for ${ZipName}: expected $Expected, got $Actual
  74. 77The macOS installer likewise scripts/install-macos.sh#L166-L166curl -fsSL "$SHA_URL" -o "$TMP/SHA256SUMS-macos"
  75. 78The shipped verifier uses the same repository-name-only regexp scripts/verify.sh#L24-L25CERTIFICATE_IDENTITY_REGEXP="https://github.com/.*/tillandsias/"
  76. 79The in-app updater's spec: artifact selection, no verification requirement openspec/specs/update-system/spec.md#L1-L53The updater SHALL select the correct artifact for the current platform and handle platform-specific execution constraints.
  77. 80Sigstore/in-toto provenance studied for another channel plan/issues/homebrew-harness-distribution-research-2026-07-11.md#L27-L31every bottle built by Homebrew CI carries a **Sigstore/in-toto build-provenance attestation** (SLSA Build L2)
  78. 81The Linux soft reset keeps the Vault store and both keyring entries crates/tillandsias-headless/src/main.rs#L11641-L11641the keyring entries vault-shamir-share-v1 and vault-root-token-v1 — never cleared by a reset
  79. 83Where no keyring is available the share is written to a fallback file in the cache directory crates/tillandsias-headless/src/vault_bootstrap.rs#L5351using fallback file (expected in VM guest and headless environments)
  80. 84Inside a VM guest both fallback writes are always attempted crates/tillandsias-headless/src/vault_bootstrap.rs#L3167-L3168BOTH writes are always ATTEMPTED — a token failure must never skip the share, because the share is the half that arms the wipe.
  81. 85The "share present in keychain" check falls through to the file crates/tillandsias-headless/src/vault_bootstrap.rs#L3329-L3330The FALLBACK half of has_shamir_share_in_keyring: the whole predicate inside
  82. 86The preserve guard and the log line the smoke runs observed crates/tillandsias-headless/src/vault_bootstrap.rs#L4928[tillandsias-vault] preserving existing data volume (Shamir share present in keychain)
  83. 87Spec: every persistent on-disk copy of the share is deleted immediately openspec/specs/tillandsias-vault/spec.md#L140delete all persistent on-disk copies immediately
  84. 90The Podman client runs whatever argv it is handed, with no policy call crates/tillandsias-podman/src/client.rs#L1011-L1013let mut full_args = vec!["run".to_string()]; full_args.extend_from_slice(args); match self.execute(OperationKind::Container, &full_args).await {
  85. 91Why the CA directory moved: /tmp is volatile by design and swept on reboot images/default/ca-path.txt#L36-L38/tmp works everywhere and is the bug — volatile by design, cleared on reboot and swept by systemd-tmpfiles
  86. 92Why label=disable: no `:z`/`:Z` relabelling, so bind mounts work under SELinux hosts openspec/specs/podman-orchestration/spec.md#L110-L110no `:z` or `:Z` suffix is needed because `--security-opt=label=disable` disables SELinux confinement for the container process
  87. 93The macOS host writes the secure-wire flag into the guest's service unit crates/tillandsias-vm-layer/src/vz.rs#L1437Environment=TILLANDSIAS_SECURE_CONTROL_WIRE=__SECURE_CONTROL_WIRE__
  88. 94Why the flag is applied — SELinux relabelling would otherwise break the bind mount docs/audits/browser-isolation-audit-2026-04-30.md#L58| `--security-opt=label=disable` | ✅ | Required for Silverblue bind mounts |
  89. 95The observatorium web launch serialises without the policy call crates/tillandsias-headless/src/main.rs#L15210.build_run_args()
  90. 96The proxy launch is assembled as a raw argv the checker never sees crates/tillandsias-headless/src/main.rs#L4544fn build_proxy_run_args(certs_dir: &Path, image: &str) -> Vec<String> {
  91. 97The mirror launch is assembled the same way crates/tillandsias-headless/src/main.rs#L5642fn build_git_run_args(
  92. 98What the checker refuses beside a missing flag crates/tillandsias-podman/src/policy.rs#L189-L193if arg == "--privileged" || arg.starts_with("--privileged=") || flag_value(arg, next, "--userns").is_some_and(|value| value != "keep-id") || flag_value(arg, next, "--cap-add").is_some_and(|value| value == "ALL")
  93. 99The gate's pass token names the tree it checked plan/archive/packets-2026-08.yaml#L48763a green gate now issues a one-shot pass token naming the tree it validated
  94. 100Ownership probe distinguishes keep-id from misleading inspect labels openspec/litmus-tests/litmus-podman-idiomatic-security-flags.yaml#L41-L64The bind-mounted file discriminates — 1000 with keep-id, 0 without
  95. 101Capability probe ignores command exit status before declaring success openspec/litmus-tests/litmus-podman-idiomatic-security-flags.yaml#L111-L114expected_behavior: "CAPABILITIES_DROPPED"
  96. 102CA directory creation uses ordinary create_dir_all, followed by refresh decision crates/tillandsias-headless/src/main.rs#L4211-L4212std::fs::create_dir_all(&certs_dir) .map_err(|e| format!("Failed to create CA directory: {e}"))?;
  97. 103Host derives the control key from the build-time guest digest crates/tillandsias-secure-channel/src/lib.rs#L157-L182derive_psk(guest_binary_sha256, build_version, wire_version, hop)
  98. 104Why single-process tests could not reveal the host/guest digest mismatch crates/tillandsias-secure-channel/src/lib.rs#L205-L228The only integration proof is two DISTINCT binaries: a release-built tray and the release guest reaching Ready on a real cold provision.
  99. 105Windows refuses an absent embedded guest digest crates/tillandsias-windows-tray/src/hvsocket.rs#L25-L43EMBEDDED_GUEST_SHA256.as_ref().ok_or_else(|| {
  100. 106Windows handshake negative control rejects a different guest digest crates/tillandsias-windows-tray/src/hvsocket.rs#L525-L610a_guest_digest_mismatch_is_refused_not_carried
  101. 107Local acceptance follows upstream acceptance, so a reported success is a durable one images/git/pre-receive-hook.sh#L6-L8Validates ledger YAML, then synchronously relays the proposed ref transaction upstream before accepting it locally. A client success therefore means the configured upstream has durably accepted the same atomic ref set.
  102. 109Spec: the share lives only in an unlocking keyring, with no persisted fallback share file openspec/specs/tillandsias-vault/spec.md#L436-L439There is NO persisted fallback share file: where the keyring is unavailable the share stays in process memory or on tmpfs for the life of the process and is gone with it
  103. 110The credential clearer is uninstall-only and has no caller yet crates/tillandsias-headless/src/vault_bootstrap.rs#L3778-L3782UNINSTALL-ONLY (order 1437-qza3, operator directive 2026-09-27). No reset may call this: the store and the unseal material are operator data that survive every destructive reset
  104. 111The Linux soft reset runs no credential clearer and deletes no store crates/tillandsias-headless/src/main.rs#L11604-L11606No credential clearer and no store deletion: the store is a host directory the podman reset cannot reach.
  105. 115Open defect: the Windows installer never verifies a cosign signature plan/index.d/20261008t234534z-1562-leaked-test-creds-and-installer-cosign-yolanda-windows.yaml#L66-L66install-windows.ps1 checks the downloaded zip against SHA256SUMS-windows but never verifies a cosign signature
  106. 116Open defect: nothing verifies the install entry point itself plan/index.d/20260922t152446z-1361-pwjz-the-entry-point-verifies-everything-but-itself-pirria.yaml#L23-L24(a) the install path verifies its own published cosign bundle before executing, or
  107. 117The mirror hook can refuse pushes by a seed read from the integration branch, when the seed enforces images/git/pre-receive-hook.sh#L169-L177enforced the push is rejected BEFORE the relay, so nothing reaches upstream
  108. 118A build check refuses any code path that hands a URL to a host browser scripts/lua/check-no-host-browser-launch.lua#L4-L6host browser. Refuse any call in crates/ that can hand a URL to the host's
  109. 119The spec: evaluation is disabled in this version openspec/specs/host-browser-mcp/spec.md#L407-L412### Requirement: browser.eval is disabled by default in v1
  110. 120The secrets spec: podman's secret store is no longer the transport, because it is plaintext on durable disk openspec/specs/podman-secrets-integration/spec.md#L24-L27secret store is no longer the transport: its file driver, and the copy podman
  111. 121The unseal key is handed to the vault as a file on tmpfs, never as a podman secret crates/tillandsias-headless/src/vault_bootstrap.rs#L4149-L41531118-fqfk: the unseal key is handed to the vault container as a file on

And you would like me to be condescending about it. Very well.Assumes everything before it. Mathematics from here down.

Level 5 — For the MathWiz / Hacker

A monocot bromeliad flowers once and dies, leaving offsets behind — the pup is never the parent.

The lower levels gave you the object and its defects. What is left is the only part that can be attacked with a pencil: the formal content, stated as claims with hypotheses, sorted into (a) standard theorems being invoked, (b) what the repository actually asserts, (c) what follows, and (d) what does not. The security findings of level 4 are not repeated; they enter here only where a mathematical hypothesis depends on them.

The short verdict: the theory is small, finite, and unusually candid about the theorems it declines to claim. Its model now has generative property tests and stable requirement identifiers. Connecting those checks to live validation and justifying dependent iteration remain separate obligations.

The lattice, and the one step worth checking

An obligation is a named, auditable requirement, invariant, litmus signal, trace signal, provenance binding, or environment assumption carrying a stable identifier1. Its state ranges over a finite chain, seven long2:

\[\mathcal{O} = \{\,\texttt{absent} < \texttt{declared} < \texttt{traced} < \texttt{pos\_tested} < \texttt{neg\_tested} < \texttt{runtime\_obs} < \texttt{evidence\_bundled}\,\}\]
\[S_{\text{spec}} = \prod_{i \in \mathrm{Obl}} \mathcal{O}_i, \qquad S_{\text{proj}} = \prod_{s} S_{\text{spec}}(s), \qquad x \le y \iff \forall i,\; x_i \le_{\mathcal{O}} y_i\]

Fix the convention, since authors differ: the height of a finite poset is the maximum number of strict order steps in a chain — so a chain of \(n\) elements has height \(n-1\), and the one-element poset has height \(0\).

Proposition 1. \(S_{\text{proj}}\) is a finite complete lattice of height \(6|\mathrm{Obl}|\). Each \(\mathcal{O}_i\) is a finite chain, hence a complete lattice; a finite product of complete lattices is complete with \(\vee,\wedge\) computed componentwise; a chain of seven elements has height six and heights add over products3. Nothing here is in dispute — it is the least interesting true thing in the file, and the repository correctly declines to inflate it: "this proves monotonicity of the model, not truth of the modeled requirement or adequacy of the chosen obligation set"4.

Two hypotheses do quiet work. The order is componentwise after aligning stable IDs and tombstones1, so a renamed coordinate yields a different lattice, not a lower point in the same one; and \(|\mathrm{Obl}|\) must be finite and known, which is what makes the height bound a bound rather than an ordinal.

absentdeclared tracedtested + tested −observed less evidence → more evidence, one direction only going backwards is not a bug report, it is a rejected merge
Evidence only ever climbs. A claim may not descend a rung without an explicit, recorded scope change.
⊤ both fully evidenced ⊥ nothing evidenced joins
Two obligations, each a chain of three states, give a nine-element product lattice ordered componentwise. Real specs multiply many more chains than two.

The fixed point, in full

Let \(\mathrm{refine}: S \to S\) be validation followed by semantic distillation at a fixed bar \(B\). The repository's stated claim is idempotence5:

\[\mathrm{refine}(\mathrm{refine}(x)) = \mathrm{refine}(x)\]

Theorem 2 (Knaster–Tarski). A monotone map on a complete lattice has a complete lattice of fixed points, in particular a least one6. Applied here it gives existence, non-constructively, and says nothing about how to reach it.

Theorem 3 (finite monotone inflationary iteration). If \(f: L \to L\) is monotone and inflationary (\(x \le f(x)\)) on a finite lattice of height \(h\), then for every \(x\) the chain \(x \le f(x) \le f^2(x) \le \cdots\) stabilises after at most \(h\) strict increases, and its limit is the least fixed point \(\ge x\). The bound is immediate: each strict step rises at least one level in a poset of height \(h\). Leastness is a two-line argument worth doing, because it is the step people skip: if \(y = f(y)\) and \(y \ge x\), then monotonicity gives \(f^n(x) \le f^n(y) = y\) for every \(n\), so the limit is \(\le y\).

Note where Kleene's theorem proper — least fixed point as \(\bigsqcup_n f^n(\bot)\) for a Scott-continuous \(f\) on a CPO7 — would otherwise be invoked outside its hypotheses. Continuity is not assumed, it is free: in a finite poset every directed subset is finite and contains its own supremum, so every monotone map is Scott-continuous. That is the first place a referee probes, and it survives.

more less applications of the refinement operator lattice top — never reached, and need not be refine(refine(x)) = refine(x) the chain stops moving
Monotone refinement over a finite lattice cannot ascend forever. It stabilises — and the step where it stops is what “done” means. Provided refinement really is monotone.

So validator stability is a computable predicate: \(\mathrm{refine}(x) = x\). It is not completion: the identity operator is stable even with unmet obligations. The methodology makes this distinction explicitly40. The comparison bar \(B_v\) is fixed within a release, rising only by explicit operator decision, with the automation forbidden from self-escalating it8. A continuously rising bar makes \(\mathrm{refine}\) a moving operator and destroys stabilisation; discrete, externally gated raises preserve it. That is the standing hypothesis of Theorem 3, not a premise that forces every obligation to close.

And now the gap. Theorem 3 needs monotone and inflationary; the repository asserts idempotent. These are the three axioms of a closure operator, and the repository states one of them. Idempotence does not imply monotonicity: a map that sends \(a \mapsto \top\), \(b \mapsto b\) with \(a < b\) is idempotent and not monotone. Nor does it imply inflationarity. The validation program that would discharge the missing two is written down — enumerate the allowed transitions and property-test that each is order-preserving for every affected obligation9 — which is the right shape of obligation. That program now has executable tests over a committed rule set; it does not establish the same properties for every live validator41.

known limitation The refinement model has generative tests for monotonicity, inflationarity and idempotence, but they exercise a committed rule set rather than every live validator41. They establish a model boundary, not semantic correctness of the running system.

what happens next The staged validation program names the remaining obligation: enumerate permitted transitions and check every affected coordinate, treating tombstones and scope changes separately9.

checked Requirement identifiers now exist and a gate checks presence and uniqueness, including tombstoned specifications42. Whether an edit preserves meaning and should keep its identifier remains author judgement; the guard cannot decide that. The evidence-credit table still lists stable identifiers as a scoring input10, which by itself does not establish that credit is computed.

checked The negative results — no contraction, no Galois connection, no probabilities — are first-class claims carrying their own discharge conditions, not caveats appended to positive ones. Each names the object a future claim would have to construct.

The moving target, and what would force the floor to zero

Write \(T_v\) for the specifications at version \(v\), \(I_v\) for the implementation, \(d_v = d(I_v, T_v) \ge 0\). Because the specifications evolve, \(T\) moves and no final target exists11. The mechanically checkable release rule is non-increase.

Proposition 4 (monotone convergence). A non-increasing real sequence bounded below converges to its infimum12. Here:

\[d_{v+1} \le d_v \;\text{ for all } v, \quad d_v \ge 0 \qquad\Longrightarrow\qquad d_v \;\longrightarrow\; d_* \;=\; \inf_{v} d_v \;\ge\; 0\]

That is the whole result, and it is correct.

What does not follow is \(d_* = 0\), and the repository says so in those words11. A sufficient extra hypothesis is a uniform progress premise, in either of two forms:

\[\exists\,\varepsilon > 0 : \; d_v > 0 \implies d_{v+1} \le d_v - \varepsilon \qquad\text{or}\qquad \exists\,\theta < 1 : \; d_{v+1} \le \theta\, d_v\]

Either excludes positive residual fixed points and forces \(d_* = 0\) — the additive form in finitely many steps, the multiplicative one geometrically. The repository's phrase — "a validated progress premise that excludes positive residual fixed points"11 — can be discharged by either sufficient condition; neither is established here as a fact about the project.

What CentiColons make measurable

A concrete residual can replace an unspecified distance without becoming a metric in the sense needed by Banach. Fix a finite obligation set \(S\), positive integer weights \(w_i\), and a versioned closure predicate \(C_i(x) \in \{0,1\}\) requiring the declared evidence for obligation \(i\). A proposed binary accounting contract is

\[R_S(x) = \sum_{i \in S} w_i\bigl(1-C_i(x)\bigr).\]

This is a measurable residual of declared, unmet evidence obligations, not all uncertainty about the program. Its unit, scope and evidence policy must stay fixed across a comparison. Preserving each retained obligation's credit implies non-increase of \(R_S\); non-increase of the total does not imply preservation of each credit, because one gain can hide another loss. New obligations, retirements and evidence invalidation must be reported explicitly rather than called progress.

The termination statement now has a precise price: if there is no regression and, whenever \(R_S>0\), a decrease of at least one occurs within the next \(K\) cycles, then \(R_S=0\) within at most \(K R_S(x_0)\) cycles. There are at most \(R_S(x_0)\) such decreases to wait for. This is a conditional cycle bound, not a wall-clock bound or a proved property of the project. Non-increase alone permits \(R_S=10\) forever.

Lua makes extraction and grading executable. That closes part of the measurement gap, not the progress premise. At this release the Lua grader reports an unweighted count at the recorded positive-test bar47, while the methodology declares a richer weighted policy15. The regression reporter is advisory and stores an untracked, per-checkout baseline, not a durable accepted-release baseline48. The capability split between cacheable and observing predicates46 also cannot prove that an assertion tests the intended behavior or that a pass belongs to the current implementation and every required platform. A pure grader can give the same wrong credit on every evaluation.

known limitation CentiColons make declared residuals observable, but the stable release does not yet establish enforced monotonic reduction under a complete evidence contract. The observed grader selects results by test name and test digest, not required platform; its spec-digest check is conditional and it does not match implementation inputs47. Scenario candidates are shared at requirement level49.

what happens next Fix assertion, subject and platform attribution; align the scoring policy; then pilot a durable per-obligation non-regression check before enabling enforcement. Invariant extraction is separate from the current grading loop50. This is the proposed program, not a claim that it has shipped; strict progress remains a separate hypothesis even after these checks exist.

far close successive releases — each one re-raises the bar the floor — possibly not zero zero would mean “finished”. No proof it is reachable.
Schematic: if comparable residuals never rise, they approach a floor, and nobody has proven that floor is zero.

The Banach alternative, itemised. A contraction claim13 would owe a complete metric space \((X,d)\), an operator \(F: X \to X\), and a constant \(0 \le c < 1\) with

\[d\big(F(x),F(y)\big) \le c\,d(x,y) \quad \forall x,y \in X \qquad\Longrightarrow\qquad \exists!\,x^* = F(x^*), \quad d\big(F^n(x),x^*\big) \le \frac{c^n}{1-c}\,d\big(x,F(x)\big)\]

Missing: (i) a metric, (ii) completeness, (iii) any bound on \(c\). The repository lists all three14. The binding one is (i), and it is worse than "unproven". \(d\) as used is not a function on \(X \times X\) at all — it is only ever evaluated at the single pair \((I_v, T_v)\), giving a sequence of numbers indexed by release, not a binary function on a set. Ask which axiom fails and the honest answers are:

\[\underbrace{d(x,y) = d(y,x)}_{\text{undefined}}, \quad \underbrace{d(x,z) \le d(x,y) + d(y,z)}_{\text{undefined}}, \quad \underbrace{d(x,y) = 0 \implies x = y}_{\text{false on the natural candidate}}\]

The first two are undefined rather than false, because \(d(x,y)\) for two arbitrary project states is never given a value; identity of indiscernibles fails outright, since two materially different implementations can close the same obligation set and score residual zero. Absent (i), items (ii) and (iii) are not merely unproven — they are not well-posed. Until a metric exists, "monotonic convergence" means ordered non-regression plus finite residual descent, which is what the file claims and no more14.

Be careful about the scope of that verdict, because it is easy to overstate and I will not. Everything above concerns the \(d\) the repository actually documents. It is not an argument that no suitable metric exists on project states, and nothing here forecloses one: a genuine metric — with a completion, and a contraction constant — may well be constructible, and if it were, the Banach route would reopen exactly as written. The claim is the narrow one. The contraction argument cannot be discharged by the object currently on offer, and the work of building a better one is open rather than blocked.

There is a further boundary. Under a fixed denominator, a non-increasing integer residual is eventually constant; strict progress while positive would force zero. Changing the obligation scope breaks that comparison. A ratio alone cannot repair it: adding easy obligations can improve the percentage without closing an old gap.

known limitation The scorer marks a tombstone as a broken monotone regime, and the shell reports that regime4445. This is useful local instrumentation, but does not establish a cross-release comparison for arbitrary changes of weights or obligation scope.

what happens next The validation program requires denominator changes to be reported separately9. A complete cross-release comparison must align the obligation set and weights; no general normalisation rule is established by the cited implementation.

A repository refinement is a model, not a theorem

Here is the engineering picture in a form precise enough to attack. Let \(R_k\) be the repository state visible to a task, \(p_k\) its prompt and \(C_k\) the selected context built from source, rules, retrieved material and task state. A model proposes a delta \(Delta_k\); review and validators decide whether to commit it:

\[C_k = \operatorname{select}(R_k,p_k), \qquad \Delta_k = M(C_k), \qquad R_{k+1} = \operatorname{accept}(R_k,\Delta_k).\]

"Vectors" is a useful picture of the representations the model constructs from \(C_k\); it is not Git's data model. A commit can retain reachable history while its new tree replaces or deletes files. Nor is an accepted patch automatically an improvement. The acceptance step is where tests, review and evidence enter; the finite obligation model says exactly what those validators can record, and no more.140

local tasks: source + prompt + checks → committed delta accessibilityproduct copydashboard local targetlocal targetlocal target shared residual shared target: specifications, aligned before comparison a checked commit preserves history; it does not prove every line improved
A schematic engineering model: each checked change can move one local residual, while the shared record is judged against aligned specifications. It is not a probability measurement or a convergence proof.

For a workstream \(i\) with a fixed specification \(T_i\), write \(d_i(R_k,T_i)\) for its declared, measured residual. Accessibility and language, marketing, dashboards and customer support may each have their own \(d_i\). A shared residual is meaningful only after the obligation identities, weights and scope have been aligned; tombstoning already marks the shipped scorer's comparison as broken.4445 Thus the diagram's local traces are engineering telemetry, not samples from a common distribution.

not yet shown Repeated small, checked refinements can reduce observed local residuals faster than one large unreviewed change, while commits preserve an inspectable history of accepted evidence. This is a useful operating method, not a proof that the residual reaches zero.

The tempting probabilistic upgrade is to treat \(Delta_k\) as one random refinement with a finite-step skew, then invoke a weak or strong law over many refinements. That argument needs a stochastic model of the quality functional, a target mean and conditions on the joint process. Large context windows, high-dimensional embeddings and a fresh prompt do not supply independence. The next input contains \(R_{k+1}\), which was made using the previous output; this is feedback, not a negligible detail. The possible formal routes are the ones below: establish a dependence structure that supports an ergodic or martingale theorem, or leave the claim empirical.161819

what happens next Record each target, its scope and its negative controls; compare only aligned measurements; and retain counterexamples and retractions as first-class outcomes.940

Iteration: the law of large numbers at full strength

The methodology now withdraws its strong-law citation for dependent iterations16. The mathematics below explains why that correction matters. Set it up properly. Let \((X_k)_{k\ge1}\) be random elements on a probability space, \(X_k\) the value of a real quality functional on iteration \(k\), with \(\mathbb{E}|X_k| < \infty\) and \(\mathbb{E}[X_k] = \mu + b_k\), where \(b_k\) is the per-iteration skew. Write \(\bar{X}_N = N^{-1}\sum_{k \le N} X_k\) and \(\bar b = \lim_N N^{-1}\sum_{k\le N} b_k\) when that limit exists.

Theorem 5 (Kolmogorov's SLLN). Under independence, identical distribution, and integrability17:

\[X_k \text{ i.i.d.}, \;\; \mathbb{E}|X_1| < \infty \qquad\Longrightarrow\qquad \bar X_N \;\xrightarrow{\;\text{a.s.}\;}\; \mathbb{E}X_1\]

The iterations here are not identically distributed, so a candidate generalisation is Kolmogorov's variance criterion, which drops identical distribution and keeps independence18:

\[X_k \text{ independent}, \;\; \sum_{k\ge1} \frac{\mathrm{Var}(X_k)}{k^2} < \infty \qquad\Longrightarrow\qquad \bar X_N - \frac{1}{N}\sum_{k \le N} \mathbb{E}X_k \;\xrightarrow{\;\text{a.s.}\;}\; 0\]
\[\text{hence} \qquad \bar X_N \;\xrightarrow{\;\text{a.s.}\;}\; \mu + \bar b, \qquad \bar b = \lim_{N} \frac{1}{N}\sum_{k \le N} b_k\]

Same family as the three-series theorem; independence and the summable-variance condition are the whole price of admission.

Weak versus strong is convergence in probability versus almost-sure convergence; almost-sure convergence implies convergence in probability, but the converse need not hold. Under the criterion above and existence of the mean-bias limit, bounded bias bounds the limiting error. Bounded bias alone does not imply convergence, and unbounded skew does not by itself prove divergence. The methodology retains small, fast iterations as engineering guidance while withdrawing the almost-sure claim16.

one big slow iteration many small fast iterations truth truth lands off-target, and you cannot see by how much an illustrative running average no convergence theorem established here Bounded bias alone does not make the average reach the truth.
Schematic only: averaging can reduce variation under suitable hypotheses. Dependent iterations and persistent bias need a separate argument; this diagram is not evidence.

The objection the methodology now acknowledges is the load-bearing one. Every form of Theorem 5 quoted above requires independence. Iterations of an agent that reads its own prior output are not independent: \(X_{k+1}\) is a measurable function of \(X_k\) and the accumulated context. The architecture makes the dependence explicit and deliberate — retrieval is a cache of converged knowledge, updated on commits, with commits acting as a Lamport clock ordering the corpus against the code19. That is a feedback loop by construction, and it invalidates every independence hypothesis in the section above.

Two possible substitutes remain unestablished. Either the sequence is stationary and ergodic and one invokes Birkhoff's pointwise ergodic theorem20 — but stationarity is implausible for a process whose corpus grows monotonically, and ergodicity is exactly what fails if the chain can be absorbed. Or the increments form a martingale difference sequence with respect to the natural filtration \(\mathcal{F}_k = \sigma(X_1,\dots,X_k)\), giving an \(L^2\) or Azuma-type law — but that requires

\[\mathbb{E}\big[X_{k+1} \mid \mathcal{F}_k\big] = \mu \quad \text{a.s. for all } k\]

which is precisely the assertion that yesterday's output does not bias today's, and the cache design says it does. Under positive feedback an absorbing state is available: the stream converges almost surely to whatever the loop locked onto, and \(\bar X_N\) converges beautifully to a number with no relation to \(\mu\). Bounding per-prompt skew does not repair this; it is a hypothesis about marginals, and the failure is in the joint.

Be precise about what this objection kills. It does not show the iteration scheme is wrong, and it does not show the stream fails to converge — small fast iterations may well be the right engineering, and dependent sequences converge under plenty of other hypotheses. What it kills is the invocation of the strong law as stated: the theorem named is being applied to a sequence that does not satisfy its independence hypothesis, so the almost-sure conclusion is unearned rather than false. The repair is cheap in words and real in work — name the dependence structure, then cite the theorem that covers it.

checked The methodology explicitly withdraws the strong-law citation because the cache makes successive iterations dependent16. It names ergodic and martingale hypotheses as work needed for a future theorem, and presents the iteration practice as an empirical track record rather than an almost-sure guarantee.

What the repository renounces, and what that costs

No Galois connection. Specs and scores are described as abstractions that deliberately forget detail — the Cousot framing21 — and the file concedes that no adjunction is defined between program semantics and spec obligations22. State the missing object precisely. With \(C\) the lattice of sets of concrete executions and \(A\) the obligation lattice, a Galois connection \(\alpha \dashv \gamma\) requires monotone \(\alpha: C \to A\), \(\gamma: A \to C\) with

\[\alpha(c) \sqsubseteq a \iff c \sqsubseteq \gamma(a) \qquad\text{equivalently}\qquad c \sqsubseteq \gamma(\alpha(c)) \;\text{ and }\; \alpha(\gamma(a)) \sqsubseteq a\]
concrete: program behaviours abstract: obligation states α γ α(c) ⊑ a ⇔ c ⊑ γ(a) — not established
Without the adjunction, a closed obligation does not transport back to a statement about the program. The arrows exist; the law relating them is not proven.

What is forfeited is the transport theorem, and only that — but that is everything. Given the adjunction and a sound abstract operator (\(\alpha \circ F \sqsubseteq F^\# \circ \alpha\)), one gets \(\alpha(\mathrm{lfp}\,F) \sqsubseteq \mathrm{lfp}\,F^\#\): a fixed point computed in the abstract over-approximates the concrete, so a property proved upstairs holds of every concrete execution downstairs. Without it, closure in the obligation lattice does not inherit that transfer theorem. Tests can still provide empirical evidence about the executions they observe; they do not thereby establish a sound abstract interpretation of all program behavior.

Credit means reaching an obligation's configured evidence bar, not necessarily the top of the lattice: a passing CI check does not imply runtime observation or bundled evidence51. Applicable, discriminating tests add evidence about behavior; closure alone still proves neither that the obligation set is adequate4 nor that all concrete executions satisfy it22.

A ranking function, not a measure. The CentiColon map \(c: S_{\text{spec}} \to \mathbb{N}\) is bounded, with separately reported denominator and residual, and monotone only for transitions preserving obligation IDs and introducing no penalties, ambiguity, or denominator scope change1. Earned credit ascends; a residual derived from it can be a Floyd-style descent witness23 only with an additional strict-progress premise. Boundedness and non-increase alone do not prove termination.

It is not a measure. There is no \(\sigma\)-algebra on \(S_{\text{spec}}\); \(c\) is not additive, because obligations overlap and sixteen penalties plus six cap rules make the rollup non-linear in its parts15; and the normaliser is policy rather than a fixed total mass. A bounded ranking function into a finite ordinal therefore induces no probability, and the repository says so outright24. What that forecloses is concrete: two scores may not be combined by Bayes; there is no complement rule, so 89% closed is not an 11% chance of a defect; and no calibration statement is available, because there is no event space on which to calibrate. Shafer25 and Walley26 are cited as the layer a confidence model would have to occupy separately — correctly, since a belief function would at least supply the monotone-capacity structure that \(c\) lacks.

That is a statement about \(c\) as defined, and about nothing more. It does not show that no transformation, extension or added structure could turn part of this model into a measure or another probabilistic object — the citation of belief functions is precisely an admission that such a layer is available to be built. What is claimed is that \(c\) does not already carry that structure, so probabilistic language applied to today's scores is unlicensed. Building the layer that would license it is open work, and the repository files it as such.

known limitation The shell scorer now delegates arithmetic to the obligation model43, but still supplies weights over CI check names27. A passing check earns its weight at the positive-test bar; it is not runtime observation or bundled evidence. This wiring does not implement the full methodology table of multipliers, evidence credits, caps and penalties15. The committed dashboard remains a historical 890/990 snapshot28, not a measurement of this release.

what happens next The framework specification still names a separate scoring implementation29. The shipped path centralises the arithmetic; completing and evidencing the richer scoring contract remains distinct work. Lua adds scenario/requirement extraction and recorded positive-test grading, not yet integrated invariant credit or full evidence attribution474950. The advisory residual and the weighted CI score are distinct projections, not interchangeable units.

known limitation The methodology's own complexity constraint — methodology-to-codebase ratio below 0.15, with a red flag at 5000 lines of CI validators30 — has no enforcing check that we found, while the script that computes the score dispatches a large shell validator corpus.

what happens next The rule names two measurement procedures30; no enforcing implementation was found in the scripts searched.

CRDTs as algebra

The plan ledger is a genuine convergent replicated data type. State is \(\text{base} \oplus \mathrm{fold}(\text{fragments})\); the carriers are two join-semilattices31: grow-only sets of packets and of events keyed by \((\text{packet id}, \text{event identity})\), with \(x \sqcup y = x \cup y\); and last-writer-wins registers per \((\text{packet id}, \text{field})\), the winner chosen by the total order \((\text{timestamp}, \text{host})\).

\[\text{state} = \Big(\bigcup_i P_i,\; \bigcup_i E_i,\; \textstyle\bigsqcup_i R_i\Big), \qquad \bigsqcup \text{ componentwise}\]

The algebra: a commutative, associative, idempotent \(\sqcup\) induces a partial order \(x \le y \iff x \sqcup y = y\) under which \(x \sqcup y = \sup\{x,y\}\) — the join is the least upper bound, which is why the three properties are a definition rather than a checklist. Convergence follows because the fold is then a function of the set of delivered updates, not their sequence: idempotence kills duplicates, commutativity and associativity kill order.

agent A — offline agent B — offline a new factanother fact a third facta fourth fold — order does not matter set union preserves facts; registers choose a winning value
With the same delivered facts and valid merge rules, replicas agree regardless of arrival order. This illustration shows set union, not every field in the ledger.

State exactly what that buys, because it is strictly less than the folklore suggests32. It gives: replicas having delivered the same update set hold equal state. It does not give delivery — liveness is someone else's problem; it does not give correctness of the converged value; and it does not preserve concurrent intent, which is where LWW does its damage. Applying LWW to a list silently discards the loser's entries, which is why events are a set and not a register — a distinction the file calls the whole correctness argument31. Determinism is pinned by folding in \((\text{timestamp}, \text{filename})\) order rather than directory order33, and two of the three properties — commutativity and idempotence — are tested by name34.

The status field is not a plain register: it composes a monotone join over a closure rank with LWW as tiebreak at equal rank, treats terminal-but-lateral values as non-ladder moves, and permits descent only under an explicit falsification flag35 — a lexicographic join with a deliberate non-monotone escape hatch, so that monotonicity cannot force retention of a certainty later shown false.

checked The repository retracts its own CRDT claim where it fails, and names the property that failed: a deduplication routine's header states plainly that it is a seen-set dedup, not a CRDT, because first-wins keeps whichever duplicate arrives first, so commutativity is false36. Most projects keep the word.

checked Componentwise maximum gives version tuples a join-semilattice. The document uses that algebra to motivate mergeable version coordinates37. Its claim that SemVer has no natural total order is too broad: version precedence and preservation of branch causality are different properties.

checked Semantic merges are typed honestly as "semantic cache with CRDT preconditions" rather than as CRDTs, with the anti-pattern named: calling a lossy cache a CRDT manufactures false convergence claims32. The claim registry holds the lineage at strength "external analogy" until generative property tests exist38.

known limitation That same versioning document defines its two leading components as a contract version and a feature phase39. The live scheme is a temporal anchor throughout — years since epoch, month, day, build. The join algebra survives untouched, since componentwise max does not care what the coordinates mean; the documented semantics of two of four components are false.

what happens next The release-shape test was corrected; the versioning document still gives the old meanings.

Verdict

The formal core is a finite product lattice, a finite monotone-inflationary stabilisation under declared validators at an operator-gated bar, a candidate residual ranking function, and a moving-target result whose zero-floor corollary needs a progress premise. A metric is a separate missing hypothesis of the Banach argument. Contraction, Galois connections and probabilistic readings are named absent rather than assumed40. As a thesis position that is defensible, and I did not expect to write that sentence.

Two boundaries remain. Generative tests cover the committed refinement model, not the semantic correctness of all live validators. And dependent iteration still lacks a convergence theorem, although the methodology now retracts the unsupported strong-law claim. Stable identifiers and centralised score arithmetic make the bookkeeping more concrete; they do not turn closure into probability or a dashboard snapshot into release evidence. CentiColons supply an executable accounting direction; complete attribution, an aligned scoring policy and an enforced durable comparison are still needed for the non-regression guarantee. The stated progress premise suffices for eventual zero and the cycle bound.

How we know — 51 sources

Each small number in the text opens the exact lines of the Tillandsias source that back that sentence, as they stand in release v56.10.20.1, the one the install commands give you. Hover a number to read the quoted lines without leaving the page. A source marked with its own release points at a newer one. The links were moved to release v56.10.20.1 automatically; a person last checked the claims themselves against v56.10.9.1.

  1. 1Formal objects — obligation, the state chain, product lattices, and the ranking function's monotonicity conditions methodology/math-foundations.yaml#L13-L43A named, auditable requirement, invariant, litmus signal, trace signal, provenance binding, or environment assumption with a stable ID.
  2. 2Stated purpose — separating defensible mathematics from analogy methodology/math-foundations.yaml#L4-L11Separate the math the methodology can defend from analogies it must not overclaim.
  3. 3Davey & Priestley, *Introduction to Lattices and Order*, 2nd ed. — product orders, heights, completeness doi.org/10.1017/CBO9780511809088
  4. 4The lattice-model claim and its stated limit methodology/math-foundations.yaml#L46-L59This proves monotonicity of the model, not truth of the modeled requirement or adequacy of the chosen obligation set.
  5. 5The fixed-point claim: idempotence of refine, and its limit methodology/math-foundations.yaml#L61-L74Define the refinement operator over a finite artifact snapshot and check idempotence: refine(refine(state)) == refine(state).
  6. 6Tarski, *A lattice-theoretical fixpoint theorem and its applications*, Pacific J. Math. 5 (1955) — the Knaster–Tarski theorem, generalising Knaster's 1928 powerset case doi.org/10.2140/pjm.1955.5.285
  7. 7Kleene, *Introduction to Metamathematics* (1952), cited for the iterative least-fixed-point construction archive.org/details/introductiontome00klee
  8. 8Bar-raise governance — the bar is fixed within a release, rises only by operator decision, and the automation must not self-escalate methodology/convergence.yaml#L502-L504is a one-off scope expansion that The Tlatoāni MUST approve every time. Recurring automation (the meta-orchestration loop) MUST NOT self-escalate the bar.
  9. 9The staged validation program: the monotonicity property tests and the denominator scope-change check methodology/math-foundations.yaml#L175-L198- allowed_evidence_transitions_are_monotone - tombstone_and_scope_change_transitions_are_explicitly_non_monotone
  10. 10The evidence-credit table containing `requirement_has_stable_id` methodology/proximity.yaml#L88-L97requirement_has_stable_id: 0.10
  11. 11Multi-version convergence: the moving target, the residual floor, and the refusal of the zero-floor claim methodology/philosophy.yaml#L134-L137Release-over-release non-increase of d_v guarantees convergence to some residual floor d_* >= 0; it does not by itself prove d_* = 0. A zero-floor claim would additionally require a validated progress premise that excludes positive residual fixed points
  12. 12Rudin, *Principles of Mathematical Analysis*, 3rd ed., Thm. 3.14 — monotone bounded sequences converge archive.org/details/principlesofmath0000rudi
  13. 13Banach, *Sur les opérations dans les ensembles abstraits*, Fund. Math. 3 (1922) doi.org/10.4064/fm-3-1-133-181
  14. 14Contraction explicitly not claimed, with the metric/operator/constant debt itemised methodology/math-foundations.yaml#L108-L120Without that metric proof, "monotonic convergence" means ordered non-regression plus finite residual descent, not metric contraction.
  15. 15The cap rules, the sixteen penalties, and the rollup that makes the score non-additive in its parts methodology/proximity.yaml#L100-L135requirement_cc = weighted_obligation * earned_credit + penalties bounded to [0, weighted_obligation].
  16. 16The strong-law citation withdrawn for dependent iteration methodology/philosophy.yaml#L29-L47So the almost-sure claim is not available as stated and is withdrawn rather than weakened.
  17. 17Kolmogorov, *Grundbegriffe der Wahrscheinlichkeitsrechnung* (1933) — the strong law and its variance criterion doi.org/10.1007/978-3-642-49888-6
  18. 18Durrett, *Probability: Theory and Examples*, 5th ed. — SLLN, Kolmogorov's three-series theorem, and the ergodic and martingale substitutes doi.org/10.1017/9781108591034
  19. 19Retrieval as a cache; commits as the Lamport clock versioning it — the mechanism that makes iterations dependent methodology/philosophy.yaml#L54-L57That cache is updated on COMMITS — a commit hitting the relevant bits retrains the RAG. Commits are therefore the LAMPORT CLOCK of the RAG models: they causally order and version the cached knowledge against the code
  20. 20Birkhoff, *Proof of the ergodic theorem*, PNAS 17 (1931) doi.org/10.1073/pnas.17.2.656
  21. 21Cousot & Cousot, *Abstract Interpretation*, POPL 1977 doi.org/10.1145/512950.512973
  22. 22The concession: no Galois connection is defined — "an abstraction discipline, not a formal abstract interpreter" methodology/math-foundations.yaml#L76-L89Tillandsias does not yet define a full Galois connection between program semantics and spec obligations. Until then, this is an abstraction discipline, not a formal abstract interpreter.
  23. 23Floyd, *Assigning Meanings to Programs* (1967), cited for ranking-style progress reasoning doi.org/10.1090/psapm/019/0235771
  24. 24Scores are not probabilities; finite coverage is not proof of absence methodology/math-foundations.yaml#L122-L135Evidence bundles and CentiColon scores should not be interpreted as probabilities. If probabilistic or belief-function confidence is later added, it must be a separate layer from obligation closure.
  25. 25Shafer, *A Mathematical Theory of Evidence* (1976), cited as a possible separate confidence layer press.princeton.edu/books/paperback/9780691100425/a-mathematical-theory-of-evidence
  26. 26Walley, *Statistical Reasoning with Imprecise Probabilities* (1991) doi.org/10.1007/978-1-4899-3472-7
  27. 27The shell still supplies weights over CI check names scripts/local-ci.sh#L422-L427check_weight() { case "$1" in spec-cheatsheet-binding) echo 100 ;; spec-code-drift) echo 120 ;; spec-trace-coverage) echo 90 ;; version-monotonicity) echo 40 ;;
  28. 28The committed dashboard's earned and total figures docs/convergence/centicolon-dashboard.json#L75-L76"total_cc": 990, "earned_cc": 890,
  29. 29The framework specification names its intended scoring implementation methodology/litmus-framework.yaml#L88-L96files: - crates/tillandsias-litmus/src/convergence/mod.rs - crates/tillandsias-litmus/src/convergence/centicolon.rs
  30. 30The uninstrumented complexity constraint and its 5000-line red flag methodology/convergence.yaml#L420-L424ratio_constraint: "methodology_complexity / codebase_complexity < 0.15" anti_pattern: > A validation system that requires more code to understand than the code being validated. Red flag: CI validators exceed 5000 lines or require specialized training to understand.
  31. 31The three CRDT primitives, and why each field uses the one it does crates/tillandsias-plan/src/fragments.rs#L28-L39Applying LWW to a LIST would silently discard the loser's entries, which is why events are a set and not a register.
  32. 32Semantic merge typed as a cache with CRDT preconditions, plus the anti-pattern methodology/cheatsheets.yaml#L88-L114Calling a lossy semantic cache a CRDT without stable IDs, tombstones, deterministic merge, and property tests creates false convergence claims.
  33. 33Determinism rules: fold order and idempotence crates/tillandsias-plan/src/fragments.rs#L41-L49Fragments fold in `(ts, filename)` order, never directory order — the filesystem does not promise an order, and two hosts folding differently would compute different states from identical inputs, which presents as corruption rather than as a sorting bug.
  34. 34Commutativity and idempotence of the fold pinned as named tests crates/tillandsias-plan/src/fragments.rs#L3340-L3341fn the_fold_is_commutative_the_defining_crdt_property() { Order of arrival must not change the result.
  35. 35Rank-aware status join with a falsification escape hatch crates/tillandsias-plan/src/fragments.rs#L459-L473The closure ladder implemented<completed<verified<done is a monotone lattice: you climb UP freely and move DOWN only through a `falsified` event.
  36. 36A retracted CRDT claim, with the failed property named crates/tillandsias-plan/lua/collect.lua#L7-L11This is a SEEN-SET DEDUP, not a CRDT — the earlier header's CRDT claim (commutativity in particular) was false: first-wins keeps whichever duplicate arrives first, so order matters.
  37. 37The join-semilattice argument for the version scheme, and the SemVer critique methodology/versioning.yaml#L104-L124SemVer (Major.Minor.Patch) allows arbitrary resets: Patch can go 1→2→1 if someone hotfixes a branch. There is no natural total order.
  38. 38CRDT preconditions filed at claim strength "external analogy" methodology/provenance.yaml#L167-L180claim_strength: external_analogy
  39. 39The documented component semantics the project retired methodology/versioning.yaml#L9-L20Major: meaning: "Contract version — breaking changes only"
  40. 40Thesis defence position: finite ordered convergence under declared validators, with unknown-event intake as the escape hatch methodology/math-foundations.yaml#L200-L206The defensible claim is finite ordered convergence under declared validators: stable obligations form a finite lattice; evidence-improving transitions are checked for monotonicity; CentiColons are a bounded ranking function over that model; fixed points mean validator stability; and unknown-event intake is the escape hatch
  41. 41Generative property tests for monotonicity, inflationarity and idempotence of the refinement operator, over a committed rule set crates/tillandsias-plan/src/obligation_props.rs#L172-L219fn refine_is_monotone_on_the_real_rules((x, y) in comparable_pair()) {
  42. 42The gate that refuses a missing or duplicated requirement identifier, tombstoned specifications included scripts/lua/check-requirement-ids.lua#L5-L24TOMBSTONED SPECS ARE CHECKED TOO, deliberately
  43. 43The shell scorer hands its weights to the model instead of summing them itself scripts/local-ci.sh#L580-L582What LEFT is the arithmetic: earned/denominator/residual are now computed by `tillandsias-plan score-checks`, which runs obligation::centicolon_function over a SpecState.
  44. 44The ranking function in code names which side of the monotone band a score is on crates/tillandsias-plan/src/obligation.rs#L675-L708Regime::Broken("denominator scope changed: an obligation was tombstoned")
  45. 45The shell prints non-comparability when the scorer reports a broken regime scripts/local-ci.sh#L633-L633this score is NOT comparable with the previous run
  46. 46Cacheable and observing predicate capabilities crates/tillandsias-plan/src/lua_predicate.rs#L74-L95Pure: no shell, no clock. Results MAY be cached.
  47. 47Observed grading: latest test/digest record, conditional spec freshness and raw positive-test residual scripts/lua/centicolon-grade-observed.lua#L36-L105A run under the obligation's OWN spec binds that spec's bytes
  48. 48Advisory regression comparison and local snapshot, advanced even after a loss scripts/lua/check-centicolon-ratchet.lua#L4-L138the advisory CentiColon R line, ported from check-centicolon-ratchet.sh without changing its protocol stdout or status.
  49. 49Static grading assigns requirement-level candidates to each scenario scripts/lua/centicolon-grade-static.lua#L180-L201
  50. 50Invariants are emitted separately from scenario/requirement obligations scripts/lua/centicolon-extract.lua#L258-L266
  51. 51A configured positive-test bar does not witness the whole evidence chain crates/tillandsias-plan/src/obligation.rs#L640-L646a CI check that passes establishes `PositivelyTested` and says nothing about `RuntimeObserved` or `EvidenceBundled`.

I want it!

One line in a terminal, one photo, one GitHub app, one prompt.

Linux
macOS
Windows

Each line fetches a short script from this site, which resolves the latest stable release on GitHub and runs that release’s own installer. Stable moves only when a daily build is promoted, so it normally trails the newest code: when this page was last built, the latest stable release — the one the command above installs — was v56.10.20.1, and newer daily builds are not what these commands install. The scripts here are not rebuilt when the app releases; they look the release up every time they run. Each installer resets the application’s own derived state and sets it up again, quietly, keeping its details in a setup log. On every platform that reset keeps your downloads, your local vault and your sign-ins.

  1. Paste

    Run the line above. When it finishes, a Tillandsias icon sits in your tray.

  2. Scan

    Choose GitHub login from that icon, point your phone at the QR code in the terminal and confirm the code at github.com/login/device.

  3. Authorize

    Install the Tillandsias GitHub App and choose which repositories it can use.

  4. Prompt

    Pick a project from the same icon and start typing.

Live progress

The accountability ledger: findings, the full pinned runtime spec inventory, and recorded change checklists. Every entry starts collapsed; open it for evidence or source. This is a build-time snapshot, not live telemetry.

62 findings12 resolved185 runtime specs219 archived changes115 checklists complete

“Complete” means all tasks in that archived checklist are checked. It does not certify current runtime behavior. The canonical spec status (“active”, “obsolete”, etc.) describes document lifecycle. See the stable audit for checked capability limits.

How much work is still open

The whole history of the project up to release v56.10.20.1, from its first day. Time is squeezed toward the past: recent weeks get the most room, and older months are drawn narrower but never cut off. Dotted lines mark releases. Point at any mark to read its value. In the top chart, each grey dot is one test run. The line is the lowest value reached so far: it can only go down, while the runs themselves went up and down.

First v0.0 release, 2026-03-22v0.0First v0.1 release, 2026-03-25First v0.2 release, 2026-05-19v0.2First v0.3 release, 2026-06-05The project raised its own quality bar, 2026-07-10stricterFirst v0.4 release, 2026-07-28First v56.8 release, 2026-08-31v56.8First v56.9 release, 2026-09-02First v56.10 release, 2026-10-08v56.10The project raised its own quality bar, 2026-10-10This release, v56.10.20.1, 2026-10-20v56.10.20.1 Open obligations, as measured by each test run (CentiColons) dots: each run · line: lowest reached 0 285 570 2026-05-07: 380 still open out of 700 2026-05-07: 380 still open out of 700 2026-05-07: 380 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 520 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 340 still open out of 700 2026-05-07: 260 still open out of 700 2026-05-07: 260 still open out of 700 2026-05-07: 260 still open out of 700 2026-05-07: 140 still open out of 700 2026-05-07: 140 still open out of 700 2026-05-07: 140 still open out of 700 2026-05-07: 140 still open out of 700 2026-05-08: 0 still open out of 660 2026-05-08: 0 still open out of 120 2026-05-08: 0 still open out of 140 2026-05-08: 0 still open out of 660 2026-05-08: 0 still open out of 120 2026-05-08: 0 still open out of 140 2026-05-08: 0 still open out of 560 2026-05-08: 0 still open out of 660 2026-05-08: 0 still open out of 660 2026-05-08: 0 still open out of 120 2026-05-12: 40 still open out of 560 2026-05-12: 40 still open out of 560 2026-05-12: 0 still open out of 560 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 0 still open out of 560 2026-05-12: 0 still open out of 560 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 0 still open out of 140 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 0 still open out of 140 2026-05-12: 40 still open out of 660 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 180 still open out of 660 2026-05-12: 180 still open out of 660 2026-05-12: 0 still open out of 660 2026-05-12: 0 still open out of 120 2026-05-12: 0 still open out of 140 2026-05-13: 100 still open out of 560 2026-05-13: 0 still open out of 560 2026-05-13: 0 still open out of 560 2026-05-13: 0 still open out of 660 2026-05-13: 0 still open out of 120 2026-05-13: 0 still open out of 140 2026-05-13: 0 still open out of 560 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 660 2026-05-14: 0 still open out of 120 2026-05-14: 0 still open out of 140 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 660 2026-05-14: 0 still open out of 120 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 660 2026-05-14: 0 still open out of 120 2026-05-14: 0 still open out of 140 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 660 2026-05-14: 0 still open out of 120 2026-05-14: 0 still open out of 140 2026-05-14: 0 still open out of 560 2026-05-14: 0 still open out of 660 2026-05-14: 0 still open out of 120 2026-05-14: 0 still open out of 560 2026-05-14: 40 still open out of 560 2026-05-14: 0 still open out of 560 2026-05-14: 200 still open out of 660 2026-05-14: 140 still open out of 660 2026-05-14: 0 still open out of 660 2026-05-14: 100 still open out of 660 2026-05-14: 180 still open out of 920 2026-05-14: 40 still open out of 560 2026-05-14: 0 still open out of 560 2026-05-14: 100 still open out of 660 2026-05-14: 100 still open out of 560 2026-05-14: 270 still open out of 650 2026-05-14: 270 still open out of 650 2026-05-14: 270 still open out of 650 2026-05-14: 370 still open out of 750 2026-05-14: 370 still open out of 750 2026-05-14: 570 still open out of 1010 2026-05-14: 570 still open out of 1010 2026-05-14: 370 still open out of 750 2026-05-14: 230 still open out of 750 2026-05-14: 230 still open out of 750 2026-05-14: 290 still open out of 750 2026-05-14: 290 still open out of 750 2026-05-14: 290 still open out of 750 2026-05-14: 410 still open out of 750 2026-05-14: 250 still open out of 750 2026-05-15: 290 still open out of 750 2026-05-15: 190 still open out of 750 2026-05-15: 150 still open out of 750 2026-05-15: 390 still open out of 1010 2026-05-15: 270 still open out of 750 2026-05-15: 390 still open out of 1010 2026-05-15: 60 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 100 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 160 still open out of 750 2026-05-15: 100 still open out of 650 2026-05-15: 100 still open out of 650 2026-05-15: 100 still open out of 650 2026-05-15: 100 still open out of 650 2026-05-15: 140 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 100 still open out of 650 2026-05-15: 60 still open out of 650 2026-05-15: 0 still open out of 650 2026-05-15: 40 still open out of 650 2026-05-15: 40 still open out of 750 2026-05-15: 0 still open out of 750 2026-05-15: 100 still open out of 750 2026-05-15: 100 still open out of 750 2026-05-15: 0 still open out of 650 2026-05-15: 120 still open out of 1010 2026-05-15: 120 still open out of 120 2026-05-15: 0 still open out of 120 2026-05-15: 100 still open out of 750 2026-05-15: 0 still open out of 120 2026-05-15: 100 still open out of 750 2026-05-15: 100 still open out of 750 2026-05-15: 100 still open out of 750 2026-05-15: 100 still open out of 750 2026-05-16: 0 still open out of 1010 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 140 2026-05-16: 40 still open out of 750 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 140 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 140 still open out of 140 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 140 still open out of 140 2026-05-16: 0 still open out of 140 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 140 2026-05-16: 140 still open out of 750 2026-05-16: 140 still open out of 750 2026-05-16: 100 still open out of 750 2026-05-16: 100 still open out of 750 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 750 2026-05-16: 0 still open out of 120 2026-05-16: 0 still open out of 140 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 120 2026-05-17: 0 still open out of 140 2026-05-17: 40 still open out of 750 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 120 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 120 2026-05-17: 0 still open out of 140 2026-05-17: 40 still open out of 750 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 120 2026-05-17: 0 still open out of 140 2026-05-17: 80 still open out of 750 2026-05-17: 80 still open out of 750 2026-05-17: 80 still open out of 750 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 120 2026-05-17: 0 still open out of 750 2026-05-17: 100 still open out of 750 2026-05-17: 0 still open out of 120 2026-05-17: 0 still open out of 140 2026-05-17: 0 still open out of 750 2026-05-17: 0 still open out of 120 2026-05-17: 0 still open out of 140 2026-05-18: 0 still open out of 750 2026-05-18: 0 still open out of 120 2026-05-18: 0 still open out of 140 2026-05-18: 0 still open out of 750 2026-05-18: 0 still open out of 120 2026-05-18: 0 still open out of 140 2026-05-18: 0 still open out of 750 2026-05-18: 0 still open out of 120 2026-05-18: 0 still open out of 140 2026-05-18: 220 still open out of 750 2026-05-18: 220 still open out of 750 2026-05-18: 0 still open out of 750 2026-05-18: 0 still open out of 750 2026-05-19: 0 still open out of 750 2026-05-19: 0 still open out of 120 2026-05-19: 0 still open out of 140 2026-05-19: 0 still open out of 1010 2026-05-19: 100 still open out of 750 2026-05-19: 0 still open out of 750 2026-05-19: 0 still open out of 120 2026-05-19: 0 still open out of 140 2026-05-19: 0 still open out of 750 2026-05-19: 0 still open out of 120 2026-05-19: 0 still open out of 140 2026-05-19: 160 still open out of 750 2026-05-19: 0 still open out of 750 2026-05-19: 0 still open out of 120 2026-05-19: 0 still open out of 140 2026-05-19: 0 still open out of 750 2026-05-19: 0 still open out of 120 2026-05-19: 0 still open out of 140 2026-05-19: 80 still open out of 750 2026-05-19: 0 still open out of 750 2026-05-19: 0 still open out of 120 2026-05-19: 0 still open out of 140 2026-05-20: 0 still open out of 750 2026-05-20: 0 still open out of 750 2026-05-20: 0 still open out of 120 2026-05-20: 0 still open out of 750 2026-05-20: 0 still open out of 120 2026-05-20: 140 still open out of 140 2026-05-20: 0 still open out of 140 2026-05-20: 140 still open out of 140 2026-05-20: 140 still open out of 140 2026-05-20: 0 still open out of 140 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 140 2026-05-21: 60 still open out of 750 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 60 still open out of 750 2026-05-21: 60 still open out of 750 2026-05-21: 60 still open out of 750 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 0 still open out of 140 2026-05-21: 0 still open out of 750 2026-05-21: 0 still open out of 120 2026-05-21: 0 still open out of 140 2026-05-22: 40 still open out of 750 2026-05-22: 0 still open out of 750 2026-05-22: 0 still open out of 120 2026-05-22: 0 still open out of 140 2026-05-22: 0 still open out of 750 2026-05-22: 0 still open out of 120 2026-05-22: 0 still open out of 140 2026-05-25: 100 still open out of 750 2026-05-25: 100 still open out of 750 2026-05-25: 40 still open out of 750 2026-05-25: 0 still open out of 750 2026-05-25: 0 still open out of 120 2026-05-25: 0 still open out of 140 2026-05-25: 0 still open out of 750 2026-05-25: 0 still open out of 120 2026-05-25: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 60 still open out of 750 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 60 still open out of 750 2026-05-26: 60 still open out of 750 2026-05-26: 60 still open out of 750 2026-05-26: 0 still open out of 750 2026-05-26: 0 still open out of 120 2026-05-26: 0 still open out of 140 2026-05-26: 40 still open out of 750 2026-05-26: 40 still open out of 750 2026-05-27: 0 still open out of 750 2026-05-27: 0 still open out of 120 2026-05-27: 0 still open out of 140 2026-05-28: 40 still open out of 750 2026-05-28: 0 still open out of 750 2026-05-28: 120 still open out of 120 2026-05-28: 60 still open out of 750 2026-05-28: 0 still open out of 750 2026-05-28: 0 still open out of 750 2026-05-28: 0 still open out of 120 2026-05-28: 0 still open out of 140 2026-05-28: 0 still open out of 750 2026-05-28: 0 still open out of 120 2026-05-28: 0 still open out of 140 2026-05-28: 60 still open out of 650 2026-05-28: 0 still open out of 650 2026-05-28: 0 still open out of 650 2026-05-28: 0 still open out of 750 2026-05-28: 0 still open out of 120 2026-05-28: 0 still open out of 140 2026-05-28: 0 still open out of 750 2026-05-28: 0 still open out of 120 2026-05-28: 0 still open out of 140 2026-05-28: 0 still open out of 750 2026-05-29: 80 still open out of 750 2026-05-29: 0 still open out of 1010 2026-05-29: 0 still open out of 1010 2026-05-29: 0 still open out of 1010 2026-05-29: 0 still open out of 1010 2026-05-29: 0 still open out of 1010 2026-05-29: 120 still open out of 1010 2026-05-29: 120 still open out of 1010 2026-05-29: 0 still open out of 1010 2026-05-29: 0 still open out of 650 2026-05-29: 0 still open out of 650 2026-05-29: 0 still open out of 750 2026-05-29: 120 still open out of 120 2026-05-29: 0 still open out of 650 2026-05-29: 0 still open out of 750 2026-05-29: 120 still open out of 120 2026-05-29: 0 still open out of 750 2026-05-29: 120 still open out of 120 2026-05-29: 0 still open out of 650 2026-05-29: 0 still open out of 750 2026-05-29: 0 still open out of 120 2026-05-29: 0 still open out of 140 2026-05-29: 0 still open out of 650 2026-05-29: 0 still open out of 650 2026-05-29: 0 still open out of 750 2026-05-29: 120 still open out of 120 2026-05-31: 60 still open out of 750 2026-06-01: 60 still open out of 750 2026-06-01: 0 still open out of 750 2026-06-01: 120 still open out of 120 2026-06-01: 0 still open out of 750 2026-06-01: 120 still open out of 120 2026-06-01: 0 still open out of 750 2026-06-01: 0 still open out of 120 2026-06-01: 0 still open out of 140 2026-06-02: 0 still open out of 750 2026-06-02: 0 still open out of 120 2026-06-02: 0 still open out of 140 2026-06-02: 340 still open out of 1010 2026-06-02: 320 still open out of 750 2026-06-02: 220 still open out of 750 2026-06-02: 320 still open out of 750 2026-06-02: 280 still open out of 750 2026-06-03: 320 still open out of 750 2026-06-03: 40 still open out of 750 2026-06-03: 0 still open out of 750 2026-06-03: 0 still open out of 120 2026-06-03: 0 still open out of 140 2026-06-03: 0 still open out of 750 2026-06-03: 120 still open out of 120 2026-06-03: 0 still open out of 750 2026-06-03: 120 still open out of 120 2026-06-03: 0 still open out of 750 2026-06-03: 120 still open out of 120 2026-06-03: 0 still open out of 750 2026-06-03: 40 still open out of 750 2026-06-03: 0 still open out of 750 2026-06-03: 0 still open out of 750 2026-06-03: 0 still open out of 750 2026-06-03: 0 still open out of 120 2026-06-03: 0 still open out of 140 2026-06-03: 320 still open out of 750 2026-06-03: 480 still open out of 1010 2026-06-03: 280 still open out of 1010 2026-06-03: 160 still open out of 1010 2026-06-03: 120 still open out of 1010 2026-06-03: 120 still open out of 1010 2026-06-03: 120 still open out of 1010 2026-06-03: 120 still open out of 1010 2026-06-03: 0 still open out of 1010 2026-06-03: 0 still open out of 750 2026-06-03: 0 still open out of 750 2026-06-03: 0 still open out of 1010 2026-06-03: 0 still open out of 750 2026-06-03: 0 still open out of 120 2026-06-03: 0 still open out of 120 2026-06-03: 0 still open out of 140 2026-06-03: 0 still open out of 140 2026-06-04: 60 still open out of 750 2026-06-04: 0 still open out of 750 2026-06-04: 0 still open out of 120 2026-06-04: 0 still open out of 140 2026-06-04: 140 still open out of 750 2026-06-04: 0 still open out of 750 2026-06-04: 40 still open out of 750 2026-06-04: 0 still open out of 750 2026-06-05: 0 still open out of 750 2026-06-06: 0 still open out of 750 2026-06-07: 160 still open out of 750 2026-06-07: 200 still open out of 750 2026-06-07: 100 still open out of 1010 2026-06-07: 60 still open out of 1010 2026-06-07: 0 still open out of 1010 2026-06-07: 200 still open out of 750 2026-06-07: 280 still open out of 750 2026-06-07: 200 still open out of 750 2026-06-07: 200 still open out of 750 2026-06-07: 200 still open out of 750 2026-06-07: 200 still open out of 750 2026-06-07: 200 still open out of 750 2026-06-07: 100 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 60 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 140 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 60 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 100 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 1010 2026-06-07: 0 still open out of 750 2026-06-07: 0 still open out of 750 2026-06-08: 100 still open out of 750 2026-06-08: 40 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 200 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 40 still open out of 750 2026-06-08: 240 still open out of 750 2026-06-08: 140 still open out of 650 2026-06-08: 240 still open out of 750 2026-06-08: 100 still open out of 750 2026-06-08: 100 still open out of 750 2026-06-08: 100 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-08: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 40 still open out of 750 2026-06-09: 40 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 40 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-09: 0 still open out of 750 2026-06-12: 0 still open out of 750 2026-06-12: 0 still open out of 750 2026-06-14: 200 still open out of 750 2026-06-14: 200 still open out of 750 2026-06-14: 100 still open out of 750 2026-06-14: 0 still open out of 140 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 650 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 1010 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 750 2026-06-14: 100 still open out of 650 2026-06-14: 0 still open out of 650 2026-06-14: 40 still open out of 650 2026-06-14: 0 still open out of 650 2026-06-14: 0 still open out of 650 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 750 2026-06-14: 0 still open out of 750 2026-06-14: 40 still open out of 750 2026-06-14: 0 still open out of 750 2026-06-15: 0 still open out of 750 2026-06-16: 0 still open out of 750 2026-06-16: 0 still open out of 750 2026-06-16: 0 still open out of 750 2026-06-16: 0 still open out of 750 2026-06-16: 0 still open out of 750 2026-06-17: 180 still open out of 750 2026-06-17: 0 still open out of 750 2026-06-17: 0 still open out of 750 2026-06-17: 0 still open out of 750 2026-06-17: 0 still open out of 750 2026-06-17: 0 still open out of 140 2026-06-18: 100 still open out of 750 2026-06-18: 0 still open out of 750 2026-06-19: 140 still open out of 750 2026-06-19: 100 still open out of 750 2026-06-19: 100 still open out of 750 2026-06-19: 0 still open out of 750 2026-06-19: 0 still open out of 750 2026-06-19: 40 still open out of 750 2026-06-19: 0 still open out of 750 2026-06-19: 0 still open out of 750 2026-06-19: 0 still open out of 750 2026-06-20: 160 still open out of 750 2026-06-20: 0 still open out of 750 2026-06-20: 100 still open out of 750 2026-06-20: 0 still open out of 750 2026-06-20: 0 still open out of 750 2026-06-20: 0 still open out of 750 2026-06-20: 0 still open out of 750 2026-06-20: 100 still open out of 750 2026-06-20: 0 still open out of 750 2026-06-20: 0 still open out of 750 2026-06-21: 140 still open out of 750 2026-06-21: 100 still open out of 750 2026-06-21: 0 still open out of 750 2026-06-21: 0 still open out of 650 2026-06-23: 100 still open out of 750 2026-06-23: 100 still open out of 750 2026-06-23: 160 still open out of 750 2026-06-23: 100 still open out of 750 2026-06-23: 100 still open out of 750 2026-06-23: 0 still open out of 750 2026-06-24: 0 still open out of 750 2026-06-25: 0 still open out of 750 2026-06-26: 40 still open out of 750 2026-06-26: 0 still open out of 750 2026-06-26: 0 still open out of 750 2026-06-26: 0 still open out of 750 2026-06-26: 0 still open out of 750 2026-07-02: 0 still open out of 650 2026-07-02: 160 still open out of 650 2026-07-02: 160 still open out of 650 2026-07-02: 120 still open out of 650 2026-07-02: 0 still open out of 650 2026-07-03: 180 still open out of 750 2026-07-03: 100 still open out of 750 2026-07-04: 0 still open out of 710 2026-07-04: 100 still open out of 810 2026-07-04: 100 still open out of 810 2026-07-04: 100 still open out of 810 2026-07-06: 100 still open out of 810 2026-07-06: 100 still open out of 810 2026-07-06: 120 still open out of 1070 2026-07-06: 120 still open out of 1070 2026-07-08: 180 still open out of 810 2026-07-08: 40 still open out of 810 2026-07-09: 0 still open out of 810 2026-07-09: 0 still open out of 810 2026-07-10: 0 still open out of 810 2026-07-10: 0 still open out of 990 2026-07-10: 0 still open out of 990 2026-07-10: 0 still open out of 990 2026-07-10: 0 still open out of 990 2026-07-10: 0 still open out of 990 2026-07-10: 0 still open out of 990 2026-07-10: 140 still open out of 990 2026-07-10: 0 still open out of 990 2026-07-10: 0 still open out of 990 2026-07-11: 0 still open out of 990 2026-07-11: 0 still open out of 990 2026-07-11: 0 still open out of 990 2026-07-11: 0 still open out of 990 2026-07-13: 180 still open out of 990 2026-07-13: 0 still open out of 990 2026-07-14: 130 still open out of 990 2026-07-14: 200 still open out of 990 2026-07-15: 280 still open out of 990 2026-07-15: 100 still open out of 990 2026-07-15: 100 still open out of 990 2026-07-15: 100 still open out of 990 2026-07-16: 0 still open out of 990 2026-07-16: 0 still open out of 990 2026-07-24: 360 still open out of 990 2026-07-24: 180 still open out of 990 2026-07-24: 0 still open out of 990 2026-07-28: 120 still open out of 1250 2026-07-30: 260 still open out of 990 2026-07-30: 40 still open out of 710 2026-07-30: 40 still open out of 710 2026-07-31: 110 still open out of 710 2026-07-31: 80 still open out of 710 2026-08-01: 100 still open out of 990 2026-08-01: 280 still open out of 990 2026-08-01: 0 still open out of 990 2026-08-01: 100 still open out of 990 2026-08-01: 100 still open out of 990 2026-08-01: 0 still open out of 990 2026-08-01: 100 still open out of 990 2026-08-01: 100 still open out of 990 2026-08-01: 100 still open out of 990 2026-08-02: 0 still open out of 990 2026-08-02: 100 still open out of 990 2026-08-04: 100 still open out of 990 2026-08-04: 100 still open out of 990 2026-08-04: 0 still open out of 990 2026-08-04: 0 still open out of 990 2026-08-04: 0 still open out of 990 2026-08-06: 0 still open out of 990 2026-08-06: 0 still open out of 990 2026-08-09: 100 still open out of 990 2026-08-09: 100 still open out of 990 2026-08-09: 100 still open out of 990 2026-08-09: 0 still open out of 990 2026-08-09: 100 still open out of 990 0 lowest (Aug 9) Clean-up work the build will not let grow: change since tracking began lower is better -82 -40 +1 scripts to rewrite · 2026-10-01 04:37: 677 scripts to rewrite · 2026-10-01 21:50: 672 scripts to rewrite · 2026-10-02 05:43: 671 scripts to rewrite · 2026-10-02 05:52: 668 scripts to rewrite · 2026-10-02 15:44: 667 scripts to rewrite · 2026-10-02 15:48: 668 scripts to rewrite · 2026-10-02 16:24: 664 scripts to rewrite · 2026-10-10 16:26: 663 scripts to rewrite · 2026-10-10 16:31: 663 scripts to rewrite · 2026-10-10 16:35: 663 scripts to rewrite · 2026-10-10 16:49: 663 scripts to rewrite · 2026-10-10 16:59: 663 scripts to rewrite · 2026-10-10 17:09: 663 scripts to rewrite · 2026-10-10 17:22: 663 scripts to rewrite · 2026-10-10 17:37: 662 scripts to rewrite · 2026-10-10 17:54: 662 scripts to rewrite · 2026-10-10 18:10: 661 scripts to rewrite · 2026-10-10 18:10: 661 scripts to rewrite · 2026-10-10 18:11: 660 scripts to rewrite · 2026-10-10 18:26: 661 scripts to rewrite · 2026-10-10 18:42: 660 scripts to rewrite · 2026-10-10 18:59: 659 scripts to rewrite · 2026-10-10 19:07: 659 scripts to rewrite · 2026-10-10 19:14: 658 scripts to rewrite · 2026-10-10 19:45: 657 scripts to rewrite · 2026-10-10 19:47: 657 scripts to rewrite · 2026-10-10 20:03: 648 scripts to rewrite · 2026-10-10 21:10: 656 scripts to rewrite · 2026-10-10 22:32: 655 scripts to rewrite · 2026-10-10 22:32: 646 scripts to rewrite · 2026-10-10 22:33: 645 scripts to rewrite · 2026-10-10 22:36: 655 scripts to rewrite · 2026-10-10 23:01: 654 scripts to rewrite · 2026-10-10 23:31: 654 scripts to rewrite · 2026-10-11 00:51: 654 scripts to rewrite · 2026-10-11 01:08: 644 scripts to rewrite · 2026-10-11 01:26: 644 scripts to rewrite · 2026-10-11 02:00: 643 scripts to rewrite · 2026-10-11 02:15: 643 scripts to rewrite · 2026-10-11 02:17: 642 scripts to rewrite · 2026-10-11 02:40: 641 scripts to rewrite · 2026-10-11 02:44: 641 scripts to rewrite · 2026-10-11 03:08: 640 scripts to rewrite · 2026-10-11 03:49: 640 scripts to rewrite · 2026-10-11 03:50: 639 scripts to rewrite · 2026-10-11 04:19: 638 scripts to rewrite · 2026-10-11 06:05: 637 scripts to rewrite · 2026-10-11 07:25: 634 scripts to rewrite · 2026-10-11 08:54: 633 scripts to rewrite · 2026-10-11 09:47: 632 scripts to rewrite · 2026-10-11 10:52: 631 scripts to rewrite · 2026-10-11 10:57: 631 scripts to rewrite · 2026-10-11 11:07: 631 scripts to rewrite · 2026-10-11 11:23: 630 scripts to rewrite · 2026-10-11 11:54: 629 scripts to rewrite · 2026-10-11 12:44: 629 scripts to rewrite · 2026-10-11 13:01: 628 scripts to rewrite · 2026-10-11 16:45: 627 scripts to rewrite · 2026-10-11 22:44: 626 scripts to rewrite · 2026-10-11 23:47: 626 scripts to rewrite · 2026-10-12 01:16: 626 scripts to rewrite · 2026-10-12 01:29: 625 scripts to rewrite · 2026-10-12 02:08: 625 scripts to rewrite · 2026-10-12 02:48: 624 scripts to rewrite · 2026-10-12 03:13: 624 scripts to rewrite · 2026-10-12 03:14: 623 scripts to rewrite · 2026-10-12 04:15: 623 scripts to rewrite · 2026-10-12 04:20: 623 scripts to rewrite · 2026-10-12 04:47: 623 scripts to rewrite · 2026-10-12 06:05: 622 scripts to rewrite · 2026-10-12 06:24: 622 scripts to rewrite · 2026-10-12 06:29: 621 scripts to rewrite · 2026-10-12 06:54: 620 scripts to rewrite · 2026-10-12 07:02: 620 scripts to rewrite · 2026-10-12 07:40: 619 scripts to rewrite · 2026-10-12 09:48: 618 scripts to rewrite · 2026-10-12 10:31: 618 scripts to rewrite · 2026-10-12 10:31: 617 scripts to rewrite · 2026-10-12 15:42: 616 scripts to rewrite · 2026-10-12 20:22: 615 scripts to rewrite · 2026-10-12 21:28: 615 scripts to rewrite · 2026-10-12 23:09: 614 scripts to rewrite · 2026-10-13 00:40: 613 scripts to rewrite · 2026-10-13 13:56: 612 scripts to rewrite · 2026-10-13 15:23: 612 scripts to rewrite · 2026-10-13 15:47: 611 scripts to rewrite · 2026-10-14 00:45: 610 scripts to rewrite · 2026-10-14 18:59: 609 scripts to rewrite · 2026-10-14 19:49: 609 scripts to rewrite · 2026-10-14 21:03: 608 scripts to rewrite · 2026-10-14 21:11: 608 scripts to rewrite · 2026-10-14 22:26: 607 scripts to rewrite · 2026-10-15 02:24: 606 scripts to rewrite · 2026-10-15 15:01: 604 scripts to rewrite · 2026-10-18 13:52: 603 scripts to rewrite · 2026-10-18 14:08: 603 scripts to rewrite · 2026-10-18 14:46: 602 scripts to rewrite · 2026-10-18 15:19: 601 scripts to rewrite · 2026-10-18 23:03: 601 scripts to rewrite · 2026-10-18 23:59: 600 scripts to rewrite · 2026-10-19 03:21: 599 scripts to rewrite · 2026-10-19 03:40: 599 scripts to rewrite · 2026-10-19 04:22: 598 scripts to rewrite · 2026-10-19 07:55: 597 scripts to rewrite · 2026-10-19 23:39: 596 scripts to rewrite · 2026-10-20 19:16: 595 fragile pipelines · 2026-10-01 04:37: 951 fragile pipelines · 2026-10-01 21:50: 946 fragile pipelines · 2026-10-02 05:43: 945 fragile pipelines · 2026-10-02 05:52: 943 fragile pipelines · 2026-10-02 15:44: 942 fragile pipelines · 2026-10-02 15:48: 942 fragile pipelines · 2026-10-02 16:24: 939 fragile pipelines · 2026-10-10 16:26: 937 fragile pipelines · 2026-10-10 16:31: 938 fragile pipelines · 2026-10-10 16:35: 938 fragile pipelines · 2026-10-10 16:42: 938 fragile pipelines · 2026-10-10 16:49: 938 fragile pipelines · 2026-10-10 17:22: 938 fragile pipelines · 2026-10-10 17:37: 936 fragile pipelines · 2026-10-10 17:54: 935 fragile pipelines · 2026-10-10 18:10: 934 fragile pipelines · 2026-10-10 18:26: 934 fragile pipelines · 2026-10-10 18:42: 933 fragile pipelines · 2026-10-10 19:07: 932 fragile pipelines · 2026-10-10 19:47: 932 fragile pipelines · 2026-10-10 20:03: 926 fragile pipelines · 2026-10-10 21:10: 931 fragile pipelines · 2026-10-10 22:32: 924 fragile pipelines · 2026-10-10 22:33: 923 fragile pipelines · 2026-10-10 22:36: 930 fragile pipelines · 2026-10-10 23:01: 930 fragile pipelines · 2026-10-10 23:31: 930 fragile pipelines · 2026-10-11 01:26: 922 fragile pipelines · 2026-10-11 02:00: 922 fragile pipelines · 2026-10-11 02:15: 922 fragile pipelines · 2026-10-11 02:17: 921 fragile pipelines · 2026-10-11 02:40: 920 fragile pipelines · 2026-10-11 02:44: 920 fragile pipelines · 2026-10-11 03:08: 919 fragile pipelines · 2026-10-11 03:49: 920 fragile pipelines · 2026-10-11 03:50: 918 fragile pipelines · 2026-10-11 04:19: 918 fragile pipelines · 2026-10-11 06:05: 917 fragile pipelines · 2026-10-11 11:07: 916 fragile pipelines · 2026-10-12 01:16: 915 fragile pipelines · 2026-10-12 02:08: 915 fragile pipelines · 2026-10-12 03:13: 915 fragile pipelines · 2026-10-12 03:14: 914 fragile pipelines · 2026-10-12 04:15: 914 fragile pipelines · 2026-10-12 04:20: 914 fragile pipelines · 2026-10-12 04:47: 914 fragile pipelines · 2026-10-12 06:05: 913 fragile pipelines · 2026-10-12 06:24: 913 fragile pipelines · 2026-10-12 06:29: 912 fragile pipelines · 2026-10-12 06:48: 912 fragile pipelines · 2026-10-12 06:54: 911 fragile pipelines · 2026-10-12 07:02: 911 fragile pipelines · 2026-10-12 09:48: 910 fragile pipelines · 2026-10-12 10:31: 910 fragile pipelines · 2026-10-12 10:31: 909 fragile pipelines · 2026-10-12 13:25: 908 fragile pipelines · 2026-10-12 15:42: 908 fragile pipelines · 2026-10-12 19:25: 907 fragile pipelines · 2026-10-13 00:40: 906 fragile pipelines · 2026-10-13 13:56: 905 fragile pipelines · 2026-10-14 00:45: 904 fragile pipelines · 2026-10-14 18:59: 903 fragile pipelines · 2026-10-14 19:54: 904 fragile pipelines · 2026-10-14 21:03: 902 fragile pipelines · 2026-10-14 21:11: 903 fragile pipelines · 2026-10-14 22:26: 902 fragile pipelines · 2026-10-15 02:24: 901 fragile pipelines · 2026-10-15 15:01: 899 fragile pipelines · 2026-10-18 13:52: 898 fragile pipelines · 2026-10-18 14:08: 898 fragile pipelines · 2026-10-18 14:46: 897 fragile pipelines · 2026-10-18 15:19: 896 fragile pipelines · 2026-10-18 23:03: 896 fragile pipelines · 2026-10-18 23:59: 895 fragile pipelines · 2026-10-19 03:21: 894 fragile pipelines · 2026-10-19 03:40: 894 fragile pipelines · 2026-10-19 04:22: 893 fragile pipelines · 2026-10-19 23:39: 892 fragile pipelines · 2026-10-19 23:41: 893 fragile pipelines · 2026-10-20 02:51: 892 fragile pipelines · 2026-10-20 05:26: 891 fragile pipelines · 2026-10-20 19:16: 890 JSON tool calls · 2026-09-26 06:30: 90 JSON tool calls · 2026-09-26 09:46: 91 JSON tool calls · 2026-09-26 11:00: 86 JSON tool calls · 2026-10-11 02:15: 85 JSON tool calls · 2026-10-12 01:16: 84 JSON tool calls · 2026-10-12 06:48: 83 83 JSON tool calls 890 fragile pipelines 595 scripts to rewrite Planned work still open in the design documents rises when new work is planned 0 283 566 open tasks · 2026-03-21 21:46: 0 open tasks · 2026-03-22 22:22: 14 open tasks · 2026-03-23 21:33: 14 open tasks · 2026-03-24 23:59: 12 open tasks · 2026-03-25 23:07: 10 open tasks · 2026-03-26 20:11: 9 open tasks · 2026-03-27 08:01: 48 open tasks · 2026-03-28 04:35: 48 open tasks · 2026-03-29 16:50: 48 open tasks · 2026-03-30 23:21: 51 open tasks · 2026-03-31 23:37: 76 open tasks · 2026-04-01 22:46: 76 open tasks · 2026-04-02 21:44: 78 open tasks · 2026-04-03 23:53: 163 open tasks · 2026-04-04 20:55: 180 open tasks · 2026-04-05 23:55: 180 open tasks · 2026-04-06 06:06: 172 open tasks · 2026-04-07 23:53: 192 open tasks · 2026-04-08 22:32: 192 open tasks · 2026-04-09 16:02: 85 open tasks · 2026-04-14 21:54: 85 open tasks · 2026-04-15 21:30: 85 open tasks · 2026-04-23 23:12: 85 open tasks · 2026-04-29 23:48: 85 open tasks · 2026-04-30 08:35: 85 open tasks · 2026-05-01 17:28: 86 open tasks · 2026-05-02 18:42: 135 open tasks · 2026-05-05 23:59: 170 open tasks · 2026-05-06 10:09: 170 open tasks · 2026-05-07 04:32: 170 open tasks · 2026-05-12 20:05: 170 open tasks · 2026-05-13 15:53: 170 open tasks · 2026-05-14 23:53: 109 open tasks · 2026-05-15 19:37: 109 open tasks · 2026-05-16 23:05: 109 open tasks · 2026-05-19 22:31: 109 open tasks · 2026-05-20 21:18: 109 open tasks · 2026-05-22 20:47: 109 open tasks · 2026-05-23 23:03: 109 open tasks · 2026-05-24 02:29: 109 open tasks · 2026-05-26 23:19: 246 open tasks · 2026-05-27 20:11: 246 open tasks · 2026-05-29 18:18: 246 open tasks · 2026-05-30 18:11: 246 open tasks · 2026-05-31 18:04: 246 open tasks · 2026-06-01 18:05: 246 open tasks · 2026-06-02 21:36: 246 open tasks · 2026-06-03 18:04: 246 open tasks · 2026-06-05 23:01: 246 open tasks · 2026-06-06 18:05: 246 open tasks · 2026-06-08 17:40: 246 open tasks · 2026-06-09 02:27: 246 open tasks · 2026-06-12 21:50: 246 open tasks · 2026-06-13 06:59: 246 open tasks · 2026-06-14 00:40: 246 open tasks · 2026-06-15 20:52: 246 open tasks · 2026-06-16 23:41: 246 open tasks · 2026-06-18 17:27: 259 open tasks · 2026-06-20 19:45: 249 open tasks · 2026-06-21 18:34: 249 open tasks · 2026-06-22 23:16: 249 open tasks · 2026-06-23 20:38: 249 open tasks · 2026-06-25 00:54: 249 open tasks · 2026-06-26 21:24: 249 open tasks · 2026-06-27 23:19: 249 open tasks · 2026-06-28 01:56: 249 open tasks · 2026-06-29 23:10: 249 open tasks · 2026-06-30 18:28: 249 open tasks · 2026-07-01 23:02: 249 open tasks · 2026-07-02 23:22: 249 open tasks · 2026-07-03 22:24: 249 open tasks · 2026-07-04 03:37: 249 open tasks · 2026-07-07 08:02: 249 open tasks · 2026-07-08 21:23: 249 open tasks · 2026-07-11 19:25: 268 open tasks · 2026-07-12 19:15: 268 open tasks · 2026-07-14 18:50: 268 open tasks · 2026-07-15 06:37: 268 open tasks · 2026-07-16 10:07: 268 open tasks · 2026-07-19 01:32: 268 open tasks · 2026-07-21 02:56: 268 open tasks · 2026-07-22 04:03: 268 open tasks · 2026-07-23 03:35: 268 open tasks · 2026-07-24 21:12: 268 open tasks · 2026-07-25 02:36: 268 open tasks · 2026-07-28 23:06: 289 open tasks · 2026-08-03 18:43: 289 open tasks · 2026-08-04 01:17: 289 open tasks · 2026-08-09 04:21: 289 open tasks · 2026-08-10 01:42: 288 open tasks · 2026-08-13 05:19: 288 open tasks · 2026-08-15 05:45: 288 open tasks · 2026-08-17 12:28: 288 open tasks · 2026-08-26 01:53: 288 open tasks · 2026-08-31 22:19: 295 open tasks · 2026-09-02 16:19: 295 open tasks · 2026-09-04 21:22: 294 open tasks · 2026-09-05 02:22: 294 open tasks · 2026-09-11 21:28: 294 open tasks · 2026-09-12 09:29: 294 open tasks · 2026-09-14 00:54: 294 open tasks · 2026-09-19 17:48: 293 open tasks · 2026-09-20 23:41: 293 open tasks · 2026-09-21 04:04: 293 open tasks · 2026-09-22 14:01: 300 open tasks · 2026-09-23 21:22: 300 open tasks · 2026-09-25 23:57: 300 open tasks · 2026-09-27 07:15: 314 open tasks · 2026-10-08 22:17: 461 open tasks · 2026-10-09 15:36: 461 open tasks · 2026-10-12 17:25: 470 open tasks · 2026-10-13 21:20: 484 open tasks · 2026-10-14 15:27: 484 open tasks · 2026-10-15 18:56: 495 open tasks · 2026-10-16 14:39: 495 open tasks · 2026-10-17 18:55: 496 open tasks · 2026-10-19 19:55: 533 open tasks · 2026-10-20 20:51: 566 566 open tasks pin1 day ago1 week ago1 month ago3 months ago2026-03-212026-10-20

The test runs stopped publishing this measure on 9 August 2026, 72 days before this release. The chart leaves that stretch empty rather than guess. The clean-up counts are the only measures that continue past it.

The numbers behind the chart
MeasureFromToFirstLastPointsDirectionSource
Open obligations per test run2026-05-072026-08-093801001063went up 144 times from one run to the next; the lowest-reached line only goes downfirst · last
Shell scripts still to be rewritten in Lua2026-10-012026-10-20677595106went up 4 timesfirst · last
Fragile shell pipelines still to be replaced2026-10-012026-10-2095189082went up 7 timesfirst · last
Calls to an external JSON tool still to be replaced2026-09-262026-10-1290836went up oncefirst · last
Planned work still open2026-03-212026-10-200566118grew by 566 as work was plannedfirst · last

Every number is read from the project's public history and links to the exact version it came from.

How this section has improved
  • A chart now shows the whole history of open work and updates with each release. The most recent weeks are drawn largest.
  • Finding cards now show what was found and whether it is fixed; the dated work log behind each card moved to the source record.
  • Archived change cards now say what a change was for and how much of it is done, instead of listing every internal task.
  • Each problem found in the project now has its own card. The card says whether the problem is waiting, being worked on or fixed, and links to the source it rests on.
  • Every design document in the current release is listed, so you can see what is planned as well as what is broken.

Waiting 50

Found findings needing a supported remedy.

52c8f180Nothing runs the checked build before a push, so a broken footnote can be publishedhightillandsias.org · skills/update-website/scripts/checked-build.sh, .git/hooks · 2026-09-05
Claim

The update-website skill requires the checked build to pass before publishing, and the matrix rules say a footnote that 404s is worse than no footnote and a drifted quote is worse than a 404.

Code / observed behavior

checked-build.sh does exactly what it promises and nothing runs it. There is no .githooks directory and .git/hooks holds only the stock samples, there is no .github/ in this repository, and wrangler deploys committed assets with no build step — so a push to main publishes whatever var/html/index.html contains. Both qualities are a matter of the author remembering.

Why it matters

This is the whole class the trial build measures: if the five pins were moved to the newest daily without re-verification, 125 of the 342 footnote targets would resolve to a range that no longer contains their quote. One rule the gate still could not check is that every RED is followed by a PATH, which no script enforces today; a lint would be ten lines, and must read "followed by" as "before the next RED or GREEN", since on level 4 one REFUTED sits between a RED and its PATH — the only such case on the site, and every one of the thirty-nine REDs is answered today.

Remedy / check

A tracked .githooks/pre-push that runs checked-build.sh and refuses on a non-zero exit, plus a one-line installer setting core.hooksPath and a line in the skill telling a fresh forge to run it. Fail closed when the checkouts are absent: an unchecked level is not a checked build.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

62cacffeThe Vault unseal share persists to a plain file that satisfies the "share in keychain" check, contradicting the tmpfs-only invariant, and `--reset-guest` leaves it behindhightillandsias · crates/tillandsias-headless/src/vault_bootstrap.rs, crates/tillandsias-headless/src/main.rs, spec tillandsias-vault · 2026-09-05
Claim

`openspec/specs/tillandsias-vault/spec.md#L391-L393` — "### Invariant: Unseal key is tmpfs-only … **Expression**: `/run/secrets/vault-unseal IS_ON tmpfs AND NEVER persisted to disk`". `#L88-L91` adds that "the host launcher captures the random Vault-generated share on first boot, persists it in the host keychain, and deletes all local on-disk copies".

Code / observed behavior

`crates/tillandsias-headless/src/vault_bootstrap.rs#L1295-L1322` (`fn write_vm_credential_fallbacks`) writes the base64 Shamir share with `fs::write(cache_dir.join(format!("fallback_{VAULT_SHAMIR_SHARE_V1}")), share_b64)`. `cache_dir` is `crate::init_cache_dir()` (`main.rs#L2011-L2028`), which resolves to `$XDG_CACHE_HOME/tillandsias` or `$HOME/.cache/tillandsias` — ordinary durable disk. The write is unconditional whenever the host delivers a share into the guest (`vault_bootstrap.rs#L108-L124`), and its doc comment says so plainly: the file "is the ONLY evidence inside the guest that `operator init` ever completed".

That file is also what answers the "is the share in the keychain?" question. `has_shamir_share_in_keyring` (`#L1345-L1372`) tries the OS keychain, then falls through to `fs::read_to_string(cache_dir.join(format!("fallback_{}", VAULT_SHAMIR_SHARE_V1)))` and returns true if it base64-decodes to 32 bytes — so the predicate that guards the partial-init wipe is satisfied by a file, not by a keychain.

Nothing deletes it. There is no `remove_file` / `delete` of any `fallback_*` path anywhere under `crates/`. And `--reset-guest`, documented at `main.rs#L1430` as an "EPHEMERAL RESET: wipe the guest substrate", removes containers, volumes, podman secrets and networks (`main.rs#L8803-L8848`) and then exactly one host path — `reset_guest_wipe_paths` (`#L8746-L8748`) returns `vec![cache_dir.join("vault-data")]`. The share file sits in the same directory and survives. `--reset-guest` also never touches the OS keychain: the only `delete_credential` call in the crate is `vault_bootstrap.rs#L1924`, inside `fn sanitize_keychain`, and it deletes only the legacy `vault-unseal-v1` entry.

Why it matters

The unseal share is the key to the vault holding the user's provider tokens and GitHub credential. The spec's headline invariant is that it never reaches disk; in the VM guest it always does, in a directory that survives the command whose entire purpose is discarding guest state.

Remedy / check

Add the fallback share (and root-token) files to `reset_guest_wipe_paths`, delete the OS-keychain entry in the same reset, and either restrict the fallback file to a tmpfs path or amend the `unseal-key-tmpfs-only` invariant to state the guest exception explicitly.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

spec-vs-code · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

6767e64dThe wording rules do not reach footnote labels, and the disagreement remedy prefers the wrong halfhightillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-2, docs/matrix/level-2-phone.md · 2026-09-06
Claim

The level-2 spec's wording rules govern "a reassuring sentence", "the page" and "the visible sentence", and its "Prose and tooltip disagree" scenario resolves a conflict between the two by correcting the sentence to the footnote's terms or re-anchoring the footnote.

Code / observed behavior

A footnote's label is author-written prose the reader sees on hover and in the rendered list, and the checked build verifies only path, range and quote — never the label. So the one surface the build cannot check is also the one the spec does not govern, and the consequence is live: the verification pass withdrew the false reassurance from the page's prose, which now says secrets are re-registered at every start, while [^5]'s label still tells the reader they are removed and reissued so that an unclean shutdown leaves nothing stale — the invented staleness property this spec's own "Reassurance is bounded by the cited source" forbids. Both remedies in the disagreement scenario treat the footnote as the correct half, so an editor applying it would restore the withdrawn claim into the sentence.

Why it matters

This is the one defect of the eight specs that touches published text: a false reassurance is on the live page today, in the surface the build does not check, and the spec written to prevent exactly that failure leaves the surface ungoverned and then points the remedy at the wrong half. Finding 1e141597 records the same label from the annotation-file angle; this one is about the rule that was supposed to catch it.

Remedy / check

Say once, in this spec, that a footnote's label is page prose bound by every wording and sourcing rule here, as PATH, GREEN and RED text are. Add the third remedy — the label is rewritten to what the cited range states — and order the remedies by which half the pinned source supports rather than by which half is the footnote. Add a scenario: when a claim is withdrawn from a sentence, the label of every footnote attached to it is checked for the same claim.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

process · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

778a9057The only page build is Python, which the runtime methodology forbids for committed automationhightillandsias.org · scripts/build-matrix.py, scripts/figures.py, scripts/issues.py, skills/update-website/scripts · 2026-09-05
Claim

The runtime methodology, identical at both tags, forbids Python for runtime, harness or repository scripts, allows Rust and POSIX shell that dispatches existing binaries, and requires explicit approval from the operator before an exception is committed or used by recurring automation.

Code / observed behavior

This repository's page generator is 1,185 lines of Python plus 372 more of inline-SVG figures, and the ledger fold the page now renders adds a third file of 392; package.json wires the generator to npm run build; and two committed skill scripts, checked-build.sh and drift-report.sh, invoke it on every run of the update loop. That is recurring automation. The convention is written down in skills/README.md as an inheritance argument rather than an approval, and the rule's own checker resolves the repository root with git rev-parse and so has only ever scanned the runtime repository. No recorded approval was found in either repository.

Why it matters

The site is the project's public argument that it says what is true about itself, and the security level's whole case is that promises are machine-checked. A methodology rule this repository quietly does not follow is something a reader of that page is entitled to know about, and it is a live risk: the day the checker learns about sibling repositories, the build breaks and the page cannot be regenerated. A Rust rewrite is the approved path and is not small — the markdown dialect, seven callout kinds, the footnote parser with its release-override suffix, the checked-build resolver, the ledger fold and the whole template, with byte-identical output as the acceptance test — and a POSIX-shell port is not permitted by the rule's own wording, since it would not be dispatching an existing binary.

Remedy / check

Ask the operator for the explicit exception the rule provides for, and record it beside the build command in docs/matrix/README.md, naming the three files it covers and the condition — no new Python, replacement in Rust when the page generator is next touched substantially.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

spec-vs-code · cb07e89 · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

865e6b30`--publish-local` serves the whole repository, `.git` included, on a route with no auth gate, with none of the hardening flags the spec's builder carrieshightillandsias · crates/tillandsias-headless/src/main.rs, images/web/entrypoint.sh, spec enclave-service-catalog · 2026-09-05
Claim

`openspec/specs/enclave-service-catalog/spec.md#L61-L65` names `build_catalog_service_run_args` as a source of truth for how a catalog service is launched, and `openspec/specs/enclave-network/spec.md#L53` lists it as the enclave attach site for this service.

Code / observed behavior

`build_catalog_service_run_args` (`crates/tillandsias-headless/src/main.rs#L3368-L3394`) is hardened and digest-pinned — `--cap-drop=ALL`, `--security-opt=no-new-privileges`, `--security-opt=label=disable`, `--userns=keep-id`, `--pids-limit=32`, and an image resolved to `entry.digest`. **It has no production caller**: the only reference in the tree is the test at `#L18836`.

The live path is `fn publish_local_service` (`#L16812`), reached from `--publish-local` (`#L1082-L1084`) and from the MCP `publish_local` tool (`crates/tillandsias-headless/src/tray/mod.rs#L1012-L1017`). It builds its own argv at `#L16841-L16853`:

"--detach", "--rm", "--name", <container>, "--hostname", …, "--network", "tillandsias-enclave", "-v", format!("{}:/var/www:ro,Z", worktree.display()), image /* "tillandsias-web", a mutable tag */

No capability drop, no `no-new-privileges`, no `userns`, no pids limit, no digest pin. `worktree` is `host_project_root().join(project_name)` (`#L16836`) — the project's repository root, `.git` and all — and the image serves it with `httpd -f -p 8080 -h /var/www` (`images/web/entrypoint.sh#L17`) with no config file, so no path is excluded. The route is then registered with `new_route.public = true` (`#L16883-L16885`), which `generate_dynamic_caddyfile` renders as a bare `reverse_proxy` with no `forward_auth` gate — the project's own test at `#L18872-L18886` asserts exactly that.

Why it matters

Any forge agent (the MCP tool is in-forge reachable) can publish `http://www.<project>.localhost/`, and from that moment `.git/config`, `.git/objects/**` and any untracked secret in the working tree are served without authentication to everything that can reach the router. The hardened, digest-pinned builder that the two specs point at as the implementation is dead code, so reviewing it tells you nothing about what runs.

Remedy / check

Have `publish_local_service` call `build_catalog_service_run_args` (deleting its inline argv), and give the web image an httpd config that refuses dot-directories — or mount a build output directory rather than the repository root.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

b28aba62No default install path verifies a Cosign bundle, and the Linux installer continues when the checksum file is missinghightillandsias · scripts/install.sh, scripts/install-macos.sh, scripts/install-windows.ps1, spec update-system · 2026-09-05
Claim

`openspec/specs/binary-signing/spec.md#L27-L30` — "All signable release artifacts MUST be signed with Cosign keyless mode and MUST be verifiable locally using the bundle format." Verification itself is placed on the user: `#L45-L52` requires only that the *instructions* and `scripts/verify.sh` use `cosign verify-blob --bundle`.

Code / observed behavior

`cosign` appears in no installer. `scripts/verify.sh` is the only consumer (`#L46`, `#L78`), and it is an opt-in helper the install path never calls. The three install scripts stop at SHA-256: `scripts/install.sh#L226-L239`, `scripts/install-macos.sh#L106-L128`, `scripts/install-windows.ps1#L361-L390`.

`scripts/install.sh` additionally proceeds on every failure of that weaker check — `#L232` "sha256sum not found; skipping checksum verification.", `#L235` "SHA256SUMS did not contain $ASSET; skipping checksum verification.", `#L238` "SHA256SUMS not available; skipping checksum verification." — and then installs the downloaded bytes at `#L243`. (`install-macos.sh` and `install-windows.ps1` do die on a missing manifest; only the Linux path degrades open.)

`openspec/specs/update-system/spec.md` — the spec governing how a running install obtains a new artifact — contains one requirement, "Platform-appropriate artifact selection", and the strings `cosign`, `verify`, `signature` and `checksum` do not appear in it at all.

Why it matters

The signing story the spec tells is only ever exercised by an opt-in helper, so the default path a user actually runs verifies nothing stronger than a checksum — and on Linux not even that, because every failure of the checksum step is a warning followed by an install of the downloaded bytes. The spec that governs how a running install fetches a new artifact does not mention verification at all.

Remedy / check

Make `install.sh` fail closed when `SHA256SUMS` is missing or does not name the asset, and add a signature-verification requirement to `openspec/specs/update-system/spec.md` (verify the bundle when `cosign` is present, refuse or warn loudly when it is not).

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

da34e5eeFive production container launches never reach `validate_launch_argv`, and one of them builds an argv the policy forbidshightillandsias · crates/tillandsias-headless/src/main.rs, crates/tillandsias-podman/src/container_spec.rs · 2026-09-05
Claim

`crates/tillandsias-headless/src/main.rs#L11681-L11688` — "VERIFIED 2026-09-04: this function's only production caller is `run_agent_container_attached`, which validates first; its four other callers are inside `mod tests` … **Every production container launch therefore already passes `validate_launch_argv`.**" A second doc comment at `#L14177` states "Every option flows through the policy-validated `build_run_argv()` path".

Code / observed behavior

`ContainerSpec` has two serializers: `build_run_argv()` validates (`container_spec.rs#L394-L397`), `build_run_args()` does not (`#L274`). These production paths use the unvalidated one, or build argv by hand and hand it straight to `PodmanClient::run_container_observed` (`crates/tillandsias-podman/src/client.rs#L1022`), which performs no validation:

- the egress proxy — `main.rs#L3414` `fn build_proxy_run_args` returns a raw `Vec<String>`, launched at `#L3526`; - the git mirror — `main.rs#L4284` `fn build_git_run_args`, launched at `#L9087`; - the observatorium web container — `main.rs#L11246` `.build_run_args()`; - the project browser — `main.rs#L11295` and `#L13047` `spec.build_run_args()`; - the interactive forge — `build_forge_agent_run_args_with_vault` ends at `#L14736` with `spec.build_run_args()`, and `build_forge_agent_run_argv` (`#L14741`) merely prefixes `podman run` before `launch_forge_agent` (`#L15089`) spawns it in a host terminal emulator. This is the path the `#L14177` doc comment describes, and it never calls the validator.

And the gap is not theoretical. `fn run_provider_login` (`main.rs#L9607`) launches a container at `#L9722-L9740` with `--cap-drop=ALL`, `--security-opt=no-new-privileges` and `--userns=keep-id` — but **not** `--security-opt=label=disable`, which `policy.rs#L17-L22` (`MANDATORY_HARDENING_FLAGS`) declares mandatory. `validate_launch_argv` would return `MissingMandatoryHardening(["--security-opt=label=disable"])` for that argv. Nothing calls it there, so the launch proceeds.

Why it matters

The project's own framing (`container_spec.rs#L385-L392`) is that an argv missing a hardening flag "is not a degraded launch to be reported, it is a container that must not be created". At this tag that guarantee holds for exactly the delegated-agent lanes, while the proxy, the mirror, the observatorium, the browser and the interactive forge — the containers a user actually runs — are outside it, and there is already one production argv the policy would reject.

Remedy / check

Route every builder through `build_run_argv()` (or call `validate_launch_argv` in `run_container_observed` / `run_container_attached_observed` so no launcher can bypass it), fix `run_provider_login`'s missing flag, and correct the two doc comments.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

077e5ab2Level 3's capability inventory has no requirement in its specmediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-3/spec.md · 2026-09-05
Claim

The level-3 spec says the page is the anatomy — what runs where, what survives a teardown, and where the sharp edges are — and at c4039e1 carries four requirements: enclave anatomy, survivorship claims, every RED true at the pin, and gate and CI statements.

Code / observed behavior

The page opens with a section the spec does not mention, an eight-part inventory of what a forge gives you on launch, each part ending in a GREEN or a RED with its PATH. It is 2,119 of the page's 3,681 prose words and the part a reader arrives for.

Why it matters

A requirement is what survives the next rewrite. The four existing requirements hold the next editor to four sections; nothing holds anyone to keeping the inventory complete, to matching the audit record's capability table, or to marking a capability spec-only when the code does not carry it. The page and its contract disagree about what the page is for.

Remedy / check

Add one requirement to the level-3 spec: the page enumerates the capabilities a forge provides on launch, one flag per capability, with the status at the pin matching the current audit record's capability table.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

spec-vs-code · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

0d88cfc7The level specs restate site/level-common's shared rules instead of referencing themmediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site · 2026-09-06
Claim

site/level-common exists so that the shared rules are "stated once so the per-level specs can say only what is particular to their page", and each level spec defers to it by name where a rule is shared.

Code / observed behavior

Eight defects across five reviews are one shape: the level spec restates the shared rule rather than referencing it. Level 1 re-asserts level-common's RED lifecycle in a scenario under a requirement that has just deferred to it, and its footnote-past-the-pin scenario is level-common's. Level 2 restates the RED lifecycle and narrows it — a replaced defect "MUST be written in the past tense", where level-common allows a past-tense flag or a GREEN — and then says its lifecycle is as level-common states. The cross-level same-source sentence is verbatim in both level 1 and level 2. The page-furniture ban appears in levels 1, 2, 3 and 4. Level 3 copies level 1's footnote-target sentence verbatim, and reproduces the no-path sentence level-common already carries. Level 5 restates level-common in four requirements (own pin, verdicts re-derived, sourcing, audit annotations), and states the newer-build/@vTAG rule three times inside its own file, a fourth in level-common and a fifth in level-5-stable-pin-deltas.

Why it matters

Normative copies drift independently: an editor correcting one leaves the others contradicting it, and level 2's copy already narrows what level-common permits, so two editors reading two specs reach opposite verdicts on the same fixed RED. Two of the duplicated rules are also cross-level by construction — a level cannot satisfy "cite the same source as the other level" alone — so no level spec is the right owner. Nothing here misleads a reader today: the specs are unpublished and govern no page yet. They are what the next update loop reads.

Remedy / check

Reduce each restating half to a reference to level-common, and move the two genuinely shared rules the level specs invented — levels agree on a source for a shared claim; page furniture lives in the LEVELS table — into site/level-common, so each level spec carries only its particular.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

process · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

1e3f8c3dThe navigation spec describes a progress board the build does not producemediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/navigation, scripts/build-matrix.py · 2026-09-06
Claim

The navigation spec requires the board to place every finding in exactly one of three columns whose blurbs a reader who has never opened the ledger can apply, to show per finding whether it has been filed where it has to go, to show when each was first recorded and last moved, and to order each column by a rule the build states.

Code / observed behavior

Three of those do not hold. The ledger's lateral terminals, obsoleted and wontfix, are not ladder rungs and are placed in the middle column by the fold, but the spec's middle-column blurb requires that a plan of work exists and the left requires found-and-not-yet-tracked, so such a card matches no blurb the page states. The filing state is computed and then discarded: build-matrix.py builds the filed-or-unfiled markup into `up` and appends it behind a literal `if False`, and would emit it as a sibling list item outside the card even if the guard were flipped, so no card on the published board carries a filing link or says "not filed upstream". A finding whose events carry no timestamp renders as a blank meta field rather than as explicitly undated, and the ordering rule (severity, then id) exists only as a Python sort key.

Why it matters

The requirement the board exists for — an unfiled finding MUST NOT be displayed in a way that lets it read as filed — is unmet by omission for every runtime finding on the live board, mitigated only by the aggregate "0 filed upstream" tally beside it. The column defect is in the opposite direction: the spec is unpublished and the fold already does something defensible, but the blurb a reader is told to apply and the placement the code performs disagree, so the first wontfix will be misfiled by the page's own definition.

Remedy / check

In the build, drop the `if False` and emit the filing state inside the card. In the spec, name where a lateral terminal sits in the ledger's own terms — on the board, never in the right-hand column, its card saying the attempt ended without completion — widen the middle column's stated meaning to cover it, require a finding with no chronology to render as undated rather than blank, and say where the ordering rule is stated.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

2448903cThe startup context says on-demand installs verify a Sigstore attestation; the shim disables attestation, and the Homebrew prefix is backed by no volumemediumtillandsias · images/default/lib-common.sh, images/default/brew-shim-exec.sh, images/default/Containerfile · 2026-09-05
Claim

`images/default/lib-common.sh#L4514-L4516` — "**On-demand tools** (`tmux`, `lazygit`, `gum`, `fd`, `rg`, …): typing the command triggers a userspace Homebrew install through a shim, gated on an allowlist. That install is NOT free — **it verifies a Sigstore attestation per bottle** and is time-bounded". The same file repeats it in the shim-generation comment at `#L3336`: "(homebrew-core formulae only, Sigstore attestation verification REQUIRED)".

Code / observed behavior

`images/default/brew-shim-exec.sh#L89` — `export HOMEBREW_NO_VERIFY_ATTESTATIONS=1`, with a long, correct explanation at `#L62-L88` of why it had to be turned off (the attestation path calls `gh attestation verify`, which needs a GitHub credential the forge deliberately does not have) and what replaced it (Homebrew's own signed formula index, which the comment is careful to call "authenticated integrity from a single publisher … NOT provenance").

Separately, the prefix that install lands in is `BREW_PREFIX="${TILLANDSIAS_BREW_PREFIX:-/home/linuxbrew/.linuxbrew}"` (`brew-shim-exec.sh#L32`). `/home/linuxbrew` exists only as a directory created in the image (`images/default/Containerfile#L27`); no builder mounts a volume there. The per-project tool-cache volume mounts `/home/forge/.cache/tillandsias-project` (`crates/tillandsias-headless/src/main.rs#L14414-L14416`), a different path. Forge containers run `--rm` (`ContainerSpec::new` sets `remove: true`, `crates/tillandsias-podman/src/container_spec.rs#L121`), so the Homebrew clone and every installed bottle are discarded when the container exits and re-fetched on the next launch.

Why it matters

The startup context is the security story an agent is given about the tools it installs, and it asserts a provenance check that the shim explicitly turns off — the one place where the code comment is more honest than the user-facing text. The unbacked prefix is a second, quieter cost: an "on-demand install" that is redone from scratch every launch, against an egress allowlist, rather than cached.

Remedy / check

Rewrite both lib-common.sh strings to describe what actually holds (signed formula index → bottle checksum; no attestation, no provenance), and mount a named volume at `/home/linuxbrew` in the forge builders.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

247ed660Ten of level 5's forty-four footnotes need re-anchoring when its pin moves, and one quote no longer exists at the dailymediumtillandsias.org · docs/matrix/level-5-phd.md · 2026-09-05
Claim

The matrix rules say that when a level's tag is bumped its line ranges are re-verified, and the level-5 spec adds that a pin-bump change lists one delta per footnote whose target or quote drifted between the tags.

Code / observed behavior

A trial build pinned to the newest daily reports six broken level-5 targets, every one a quote no longer inside its cited range: [^10] and [^15] into methodology/proximity.yaml, [^11], [^16] and [^19] into methodology/philosophy.yaml, and [^34] into crates/tillandsias-plan/src/fragments.rs. The four @v56.9.5.1 footnotes [^41]-[^44] are the other half: each resolves at the daily today, but once the pin is the daily the suffix is redundant and the REDs their PATH lines hang from are fixed at that tag, so the flags have to be re-derived rather than the anchors merely moved. [^16] is the hard one: its quote about the strong law is deleted at the daily, not moved — it is one of the nine quotes in the whole site that the daily no longer contains anywhere in the file they cite — and the surrounding text records the citation as withdrawn.

Why it matters

Ten of forty-four footnotes is close to a quarter of the page, and the level-5 rule forbids doing that work as an ordinary edit: each is a delta with evidence and a verbatim quote at the target tag, in a change the operator reviews. Written down now, at the tags where both sides were read, the next pin bump is mechanical; discovered at bump time it is a re-derivation of the strong-law argument under time pressure.

Remedy / check

Put the six-row drift table and the four suffix footnotes into the next level-5 change's tasks.md as its work list, with [^16] flagged as needing new prose rather than a new anchor.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

296676d4Every broken footnote is re-anchored by hand although the search is mechanicalmediumtillandsias.org · skills/update-website/scripts · 2026-09-05
Claim

The skill's step 5 tells the editor to correct targets, quotes and prose in the level's source, following the audit-site-claims procedure per level.

Code / observed behavior

The tooling reports that a quote left its range and never says where the quote went, yet for almost all of it the answer is one command. The day's record measured 112 moved and 9 gone out of 121 breakages; re-measured against the same daily at c4039e1, after the pages grew, it is 125 breakages of which 116 are the same characters elsewhere in the same file and need only a new line range, and 9 are genuinely gone and need a human to write new prose. No such helper exists in skills/update-website/scripts, which holds five scripts.

Why it matters

It removes toil, and toil is what makes a pin bump get postponed. It is also the precondition for a delta generator: a generator can only list one delta per drifted footnote if something can compute the new target.

Remedy / check

A re-anchor.sh taking a level and a tag, about sixty lines of shell over grep, sed and git, parsing footnote definitions the way the build does and printing one of three verdicts per broken target — moved with the new range, relocated to another file, or gone — as a paste-ready block.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

30127aadThe macOS release lane signs a hardcoded three-asset list and runs no transitive integrity checkmediumtillandsias · .github/workflows/release.yml · 2026-09-05
Claim

`openspec/specs/binary-signing/spec.md#L17-L18` — "**WHEN** a release contains the Linux musl binary and installer helper scripts **THEN** each signable artifact MUST have its own `.cosign.bundle` file".

Code / observed behavior

The Linux lane discovers what to sign and then proves the property over whatever was staged: `.github/workflows/release.yml#L264-L268` loops `for artifact in *` (skipping only `SHA256SUMS` and existing bundles), and `#L293` runs `bash ../scripts/check-release-asset-integrity.sh .`, whose own header (`scripts/check-release-asset-integrity.sh#L14-L20`) exists precisely because "the Windows job's signing loop was an ALLOW-LIST".

The macOS lane at `#L507-L518` is that allow-list:

for artifact in tillandsias-tray-*-macos-arm64.tar.gz Tillandsias.dmg install-macos.sh; do

`SHA256SUMS-macos` — staged at `#L501` — is not in the list and gets no `.cosign.bundle`. And `check-release-asset-integrity.sh` is invoked in only two jobs (`#L293` Linux, `#L772` Windows); the macOS job does not run it.

Why it matters

The integrity guard was written after an asset shipped unverifiable for exactly this reason, and the lane that reproduces the pattern is the one it is not wired into. `scripts/install-macos.sh#L106-L128` downloads `SHA256SUMS-macos` and trusts it as the sole integrity source for the tarball — a manifest with no signature of its own. Any asset added to the macOS lane later inherits the same silence.

Remedy / check

Replace the three-name glob with the Linux lane's `for artifact in *` form and add `bash scripts/check-release-asset-integrity.sh release-artifacts` to the macOS job.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

3d0ef7e2The drift report computes no forecast when every level already pins stablemediumtillandsias.org · skills/update-website/scripts/drift-report.sh · 2026-09-05
Claim

The skill says that if every level already pins the stable tag and nothing is broken the report prints ok:up-to-date, and that the daily-channel counts still tell you whether any PATH lines can be updated.

Code / observed behavior

The report runs its trial build only when some level's pin differs from stable. Every level pins stable today, so it prints per-level file counts and ok:up-to-date, and the one number that matters for planning — how much work the next promotion will be — is never computed. The script already knows how; it just points its pin override at stable rather than at the daily.

Why it matters

The forecast is what turns "we should look at the site" into a number. Run against the daily today it is 125 broken targets out of 342, a week before anyone proposes moving a pin.

Remedy / check

Roughly fifteen lines: a second trial build pinned to the daily whenever the daily differs from stable, reported as a forecast and never as a failure, with a verdict carrying both numbers. The scheduling question is the real cost and is shared with finding d0c34ed6.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

3d8dffdeThe pages are 48% longer in prose than yesterday, and the length expectation is written down nowheremediumtillandsias.org · docs/matrix, openspec/changes/add-level-page-specs/specs/site/level-common · 2026-09-05
Claim

The editorial rules that are written down constrain shape, not size: one merged narrative per level, implementation trivia is not substance, a page that is all flags has stopped being prose. None of them constrains length, and a search for length, shorter, longer, concise and brev across docs, openspec, skills and scripts returns no rule.

Code / observed behavior

Prose words outside the footnote definitions and their quote lines went from 8,277 at bf5bde3, the last commit of 2026-09-04, to 12,240 across the five levels — a factor of 1.48 — and footnote definitions from 105 to 342. Level 3 more than tripled, 1,034 words to 3,681, and its new capability inventory is 2,119 of those 3,681. The spread between the shortest and the longest level is essentially unchanged, 6.6x before and 6.5x after.

Why it matters

The operator asked for the capability inventory, and an inventory is a completeness claim: it is worth having because it enumerates, and cannot be halved without becoming a sample. The unwritten length norm has no recorded justification and nothing checks it, so the next editing agent is guessing at a rule it cannot read.

Remedy / check

One requirement in the level-common spec stating the argument/reference split — levels 1 and 2 stay readable in one sitting, levels 3 to 5 are as long as their subject provided every section earns its place under the rules that already exist — so the next editor is bound by a written rule.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

3ed0ddf7Requirement text carries dated findings, counts and rosters that level-common forbidsmediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-{2,3,4},navigation · 2026-09-06
Claim

The shared contract forbids a page spec's requirement text from carrying a dated finding, a file count or a mandated verdict on a specific flag, and says such findings are work items in the change's tasks.md while the spec states the property the finding is an instance of.

Code / observed behavior

Four specs do exactly what that requirement forbids. Level 4's "RED precision and scope" hard-codes three audit findings — a litmus corpus count down to the literal replacement phrase, a "(server-side branch protection)" scope that the page has already stopped using, and a mandated verdict on one named footnote — all three already tracked as work items in this change's own tasks.md. Level 3's survivorship requirement hard-codes a roster of artifacts two requirements after the same spec rejects prose rosters on principle, and the roster does not match the page: the host working copy it names appears on the page only inside a RED. Level 2 counts today's sources and headings into MUST text ("the two honest limits", "the page's four questions"). The navigation spec conditions a MUST NOT on a dated measurement of the runtime's local-ci.sh — a weight table over CI check names — which the review found half-superseded at the newest daily, where the arithmetic is run by a score-checks subcommand.

Why it matters

A requirement that names an instance duplicates the ledger and rots against it: it mandates a false number the moment the philosophy adds a limit, the page answers a fifth question or the litmus corpus grows, and the navigation clause is unappliable because the progress page has no pin and the answer differs by release. These are the specs' own stated rule being broken by the specs, not a claim to a reader: nothing is published, so no reader is misled today.

Remedy / check

Reduce each to the durable property it is an instance of — a number in prose is re-derived at the pin or replaced by the unquantified noun; a claim about state outside the repository is scoped to what the repository evidences; the page states what survives for every artifact whose fate it asserts — and leave the instances in tasks.md and the dated audit record.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

process · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

3f11cee0`litmus:git-mirror-no-anonymous-daemon-write` asserts the opposite of its own namemediumtillandsias · openspec/litmus-tests/litmus-git-mirror-no-anonymous-daemon-write.yaml · 2026-09-05
Claim

The fixture is registered as `name: litmus:git-mirror-no-anonymous-daemon-write` (`openspec/litmus-tests/litmus-git-mirror-no-anonymous-daemon-write.yaml#L2`), `severity: critical`.

Code / observed behavior

`#L7-L17` — "Historical name retained, but the current daemon **DOES** expose anonymous enclave receive-pack (order 450). Any reachable peer can create or fast-forward refs and cause the privileged service relay to carry those updates upstream … This litmus pins only honest interim containment … It MUST NOT be cited as zero-trust closure."

Why it matters

The body is exemplary; the name is what appears in `openspec/litmus-bindings.yaml`, in gate output, and in any summary a reader or an auditing agent assembles from test names. A green `critical` test called "no-anonymous-daemon-write" reads as proof that anonymous daemon writes are impossible, which is the exact opposite of what the file establishes.

Remedy / check

Rename it to what it pins — e.g. `litmus:git-mirror-anonymous-write-relay-containment` — and leave a one-line alias note for the old name.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

467f07baThe proxy CA is one persistent RSA-2048 30-day certificate; the spec promises a per-launch two-level EC P-256 chain on tmpfsmediumtillandsias · spec proxy-container, crates/tillandsias-headless/src/main.rs, images/default/ca-path.txt · 2026-09-05
Claim

`openspec/specs/proxy-container/spec.md#L66` — "The system SHALL generate a fresh two-level CA chain on every proxy launch. The chain SHALL consist of a self-signed Root CA and an Intermediate CA signed by the root, both using EC P-256 keys. All key material SHALL be stored on tmpfs (`$XDG_RUNTIME_DIR/tillandsias/proxy-certs/`) and SHALL be destroyed when the session ends (logout/reboot). No CA keys SHALL persist to disk." The four-file table below it names `root.crt`, `intermediate.crt`, `intermediate.key`, `ca-chain.crt`, each with lifetime "Session (tmpfs)", and a scenario requires generation "in under 10ms (EC P-256 key generation)". The spec's Status is `active` with no stale or superseded marker.

Code / observed behavior

`crates/tillandsias-headless/src/main.rs#L3170-L3189` (`fn ensure_ca_bundle`) shells out to `openssl req -x509 -newkey rsa:2048 … -days 30 -nodes -subj "/C=US/ST=Privacy/L=Local/O=Tillandsias/CN=Tillandsias CA"` — one self-signed RSA-2048 certificate valid 30 days, not a two-level EC P-256 chain. It is not per-launch: `ca_bundle_needs_refresh` (`#L3055-L3068`) returns false while both files are under 25 days old, so a launch reuses the existing key. And it is deliberately not on tmpfs: the directory comes from `tillandsias_core::ca_path::ca_dir()`, whose single-source manifest `images/default/ca-path.txt` declares `${HOME}/.local/state/tillandsias` and explains at length that a durable location was chosen by measurement because "/tmp works everywhere and is the bug" — a reboot on a volatile path made the proxy permanently unrestartable (order 975-rsgm).

Why it matters

The code is right and the spec is wrong, which is the worse direction: the spec is the artifact a reader (or an auditing agent, or this site) treats as the contract, and it currently promises ephemerality that the implementation deliberately abandoned for a documented reason. "No CA keys SHALL persist to disk" is a security claim, and the CA private key persists to `$HOME/.local/state/tillandsias/ca/intermediate.key` for up to 25 days.

Remedy / check

Rewrite the "Ephemeral CA chain (per-launch generation)" requirement to describe the durable single-certificate design and its 25-day refresh, citing `images/default/ca-path.txt` for why the path is not tmpfs.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

spec-vs-code · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

4aea5a83Linux clean-room step continues after a failed credential-cold probemediumtillandsias · scripts/e2e-step2-linux.sh · 2026-09-22
Claim

The Linux clean-room run is meant to exercise first-time Vault initialization with no retained host credentials.

Code / observed behavior

scripts/e2e-step2-linux.sh#L34-L38 logs both the host-credential clear and the cold-state probe but allows either to fail and continues.

Why it matters

A later green smoke result does not establish a credential-cold starting state unless its logged probe result is inspected separately.

Remedy / check

Stop the clean-room run when the clear or cold-state probe fails, or make the final verdict explicitly conditional on the recorded cold-state result.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

defect · v56.9.21.1 · 2f5f2a90a7b43ecdee766e38561b8e5790c603aa

56435d54`browser.open` cannot resolve a browser binary: the on-demand chromium installer has no consumer, and the chromium images are built but never runmediumtillandsias · crates/tillandsias-browser-mcp/src/launcher.rs, scripts/install-chromium.sh, images/chromium/ · 2026-09-05
Claim

`openspec/litmus-tests/litmus-host-chromium-on-demand-shape.yaml` is green, and its name asserts that host chromium is installed on demand.

Code / observed behavior

`crates/tillandsias-browser-mcp/src/launcher.rs#L67-L91` (`fn resolve_browser_binary`) accepts an explicit override, then `TILLANDSIAS_BROWSER_BIN`, then `TILLANDSIAS_CHROMIUM_BIN`, then looks only under the cache root (`~/.cache/tillandsias/chromium/current/chrome` and four siblings) and otherwise returns `LaunchError::BrowserUnavailable` — declared at `#L21` as "bundled chromium not yet downloaded". There is no `PATH` fallback to a system chrome/chromium.

The only thing that would populate that directory is `scripts/install-chromium.sh`, and nothing invokes it. Outside the script itself, a repository-wide search for `install-chromium.sh` returns no caller anywhere under `crates/`, `scripts/` or `images/`; the only reference that runs is `openspec/litmus-tests/litmus-host-chromium-on-demand-shape.yaml`, whose steps (`#L33-L75`) are `grep` assertions over the script's own text. The shape test is green while the installer has no caller. (The repository already knows: `plan/index.yaml#L22893` carries a packet titled "scripts/install-chromium.sh has ZERO consumers … wire it or tombstone it", so check that packet's state before filing.)

Separately, `images/chromium/Containerfile.core` and `Containerfile.framework` are wired into the build (`scripts/build-image.sh#L214-L215`, `crates/tillandsias-core/src/image_builder.rs#L329-L330`), but no Rust code launches either image — the only non-build references under `crates/` are build plumbing and status maps.

Why it matters

On a fresh host the whole host-browser MCP surface fails at its first call with an error that names a download step no code performs, and the litmus that is supposed to cover "host chromium on demand" cannot detect that because it only greps the installer's source. Meanwhile two multi-hundred-megabyte container images are built on every image pass and never started.

Remedy / check

Call `scripts/install-chromium.sh` from `resolve_browser_binary`'s miss path (or from the tray's first `browser.open`), add a `PATH` fallback for a system chromium, and add one step to the litmus that asserts a *caller* exists rather than only the script's text.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

576200f9The level-5 deltas are live on the site while their OpenSpec change is unapproved, and openspec/specs/ holds no site specmediumtillandsias.org · openspec/changes/level-5-stable-pin-deltas, openspec/specs · 2026-09-05
Claim

The level-5 delta discipline says an editor may apply the deltas a change lists, and that the operator reviews the change before it is archived. The record of the work says the remaining deltas are waiting on the operator.

Code / observed behavior

The seven applied deltas D1-D7 are live: eleven of the thirteen task boxes in openspec/changes/level-5-stable-pin-deltas/tasks.md are [x], and the two that are not are the proposed deltas D8 and D9, which the same file says wait for the operator rather than for an editor. The prose and the four @v56.9.5.1 footnotes [^41]-[^44] are in docs/matrix/level-5-phd.md, and the result is in var/html/index.html, which Cloudflare serves on commit. Meanwhile openspec/specs/ and openspec/changes/archive/ each hold only .gitkeep, so no site capability has ever been promoted out of changes/ and the current contract for every level lives in one of the three unarchived changes there.

Why it matters

Applying before approval is permitted, so this is an unclosed loop rather than a violation. It matters because the reviewable artifact and the published artifact have drifted apart in status: a reader of openspec/ cannot tell which of the three open changes describe the live site and which are proposals, and openspec/specs/site/level-5/ — where the next editor would look for the page's contract — does not exist. The longer the change sits, the more the next audit's deltas pile onto an unreviewed base.

Remedy / check

The operator reviews level-5-stable-pin-deltas and archives it, together with add-level-page-specs, whose task 3.3 names the same sync, so that openspec/specs/site/level-{common,1..5}/ become the current contract and changes/ holds only what is genuinely proposed.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

5e0a299e`run_provider_login` attaches a container to the enclave without being a documented member, and the membership guard cannot see itmediumtillandsias · crates/tillandsias-headless/src/main.rs, scripts/check-enclave-membership-documented.sh, spec enclave-network · 2026-09-05
Claim

`openspec/specs/enclave-network/spec.md#L45-L60` — "The attach sites are these run-argument builders, named by SYMBOL so the list survives every edit that does not rename one … A new enclave member MUST be added to this list in the same commit that attaches it; `scripts/check-enclave-membership-documented.sh` refuses the divergence in both directions."

Code / observed behavior

`crates/tillandsias-headless/src/main.rs#L9728` and `#L9764` — inside `fn run_provider_login` (defined at `#L9607`), two `podman run` invocations pass `"--network", ENCLAVE_ONLY_NET`. `run_provider_login` is not in the spec's twelve-symbol list.

The guard cannot report it. `scripts/check-enclave-membership-documented.sh#L71` — `if (fname ~ /^(build|launch)_/) { print fname }`. Only enclosing functions whose names begin `build_` or `launch_` are counted, so a production attach site in any other function is filtered out before the comparison runs. The same name filter is also silently doing the job the header at L59-L61 attributes to something else: that header says "The `#[cfg(test)]` region is cut before scanning", and no such cut exists anywhere in the script.

Why it matters

The spec's stated guarantee is that the membership list cannot drift silently, and its own text explains that a hand-maintained prose list had already gone stale by six members. The replacement mechanism reproduces the failure one level down: this attach site has been undetectable since the guard landed, and the spec's "Only the proxy MUST additionally be attached to the egress network" is stated against a list that is missing a member. The guard's own header even acknowledges a second dual-homed member ("the dual-homed pair (proxy, and the login helper)") that the spec text denies.

Remedy / check

Drop the `^(build|launch)_` name filter (cutting the real `#[cfg(test)]` region instead, as the header already claims), then add `run_provider_login` to the spec's membership list or route it through a named builder.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

spec-vs-code · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

5e3a98f3The enclave-network spec's cleanup-on-exit and lifecycle-telemetry requirements have no implementationmediumtillandsias · spec enclave-network, crates/tillandsias-headless/src/main.rs · 2026-09-05
Claim

`openspec/specs/enclave-network/spec.md#L31-L34` — "**WHEN** the Tillandsias application exits **AND** no containers are attached … **THEN** the system MUST remove the network with `podman network rm tillandsias-enclave`". And `#L80-L86` — "**THEN** the system MUST log `[enclave] Network created: tillandsias-enclave`" / "`[enclave] Container attached: <name>`", both with `@trace spec:enclave-network`.

Code / observed behavior

`graceful_shutdown_async` at `crates/tillandsias-headless/src/main.rs#L16652` stops `tillandsias-`-prefixed containers and never touches a network. The only `network rm` in the crate is `#L8845`, inside `run_reset_guest` — the explicit `--reset-guest` wipe, not an exit path. `PodmanClient::remove_network` (`crates/tillandsias-podman/src/client.rs#L975`) has no production caller: the only references are `crates/tillandsias-podman-cli/src/lib.rs#L479` (the scenario tool) and a `let _ =` liveness reference at `client.rs#L3374`. The two mandated log strings — `Network created` and `Container attached` — do not appear anywhere under `crates/`, `scripts/` or `images/`.

Why it matters

Two of the four scenarios under the spec's primary requirement, and both scenarios of its telemetry requirement, describe behaviour that does not exist. The network persists across every exit, which is also what makes the `--internal` installed-base gap permanent rather than self-healing, and the `--log-enclave` accountability window the spec promises is empty for enclave lifecycle events.

Remedy / check

Either implement the exit-time `podman network rm` (guarded on zero attached containers) and the two log lines, or mark both requirements `deferred` with the reason, so the spec stops asserting them.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

spec-vs-code · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

5f323a37The Windows tray reads the secure-wire variable directly, the single-reader ratchet has two slots of slack, and the WSL guest unit never receives the flagmediumtillandsias · crates/tillandsias-windows-tray/src/hvsocket.rs, scripts/check-secure-wire-single-reader.sh, crates/tillandsias-vm-layer/src/wsl.rs · 2026-09-05
Claim

`scripts/check-secure-wire-single-reader.sh#L3-L5` — "`TILLANDSIAS_SECURE_CONTROL_WIRE` has ONE reader, and this ratchets the number of files that name it down, never up." `#L39` sets `BASELINE="${TILLANDSIAS_SECURE_WIRE_READER_BASELINE:-3}"`, and `#L37-L38` says "Lower this as each is converted; it must never rise. 0 means the flip may land."

Code / observed behavior

At this tag exactly one file outside the owning module still reads the variable — `crates/tillandsias-windows-tray/src/hvsocket.rs#L29`, `if std::env::var("TILLANDSIAS_SECURE_CONTROL_WIRE").as_deref() == Ok("on")`, i.e. the case-sensitive byte comparison the guard's own header (`#L15-L20`) identifies as the behaviour that made `On` silently plaintext. So the count is 1 against a baseline of 3: two more direct readers could be added, with two more private interpretations of the value, and the guard would still print `ok`.

The other half is that the flag never reaches the Windows-side server. `crates/tillandsias-vm-layer/src/vz.rs#L859` injects `Environment=TILLANDSIAS_SECURE_CONTROL_WIRE=__SECURE_CONTROL_WIRE__` into the macOS guest's systemd unit; the WSL equivalent, `fn headless_unit` (`crates/tillandsias-vm-layer/src/wsl.rs#L90-L110`), writes only `Environment=HOME=/root` and `Environment=XDG_RUNTIME_DIR=/run/user/0`. The guest headless therefore always runs with the variable unset, so even a tray told `on` would hand a Noise handshake to a server that is not expecting one — which is precisely the one-sided flip the guard's header records as having already happened once (`#L23-L27`, flipped at e6a80609f, reverted at 08a7d3cc7).

Worth recording alongside: the PSK root is `release_root_secret()` (`crates/tillandsias-secure-channel/src/lib.rs#L86-L101`), which on debug builds is the literal `b"tillandsias-dev-root-not-a-secret"` (`#L68`) and on release builds is the SHA-256 of the shipped binary. Both are values an attacker holding a public artifact can compute, so the channel provides version binding, not confidentiality against anyone who has the release. The source comments say this plainly; the word "secret" in the function name does not.

Why it matters

A ratchet with slack is not a ratchet: the guard prints `ok` while two more private readers of the flag may still be added. And the flip the whole mechanism exists to make safe cannot succeed today, because the WSL guest unit never carries the variable, so a tray told `on` would speak Noise at a server that is not.

Remedy / check

Lower `BASELINE` to 1 now (it can only be lowered honestly if it tracks reality), convert `hvsocket.rs` to `tillandsias_control_wire::secure_wire_mode`, and add the `TILLANDSIAS_SECURE_CONTROL_WIRE` line to `wsl.rs`'s `headless_unit` before the default is flipped.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

64d733d3The Home positioning line makes a claim no level page footnotesmediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/navigation, scripts/build-matrix.py · 2026-09-06
Claim

The navigation spec's scenario for the positioning line says that where it makes a claim about what the software does, it says only what a level page already carries with a footnote, in words no stronger, and short enough that the two can be compared at a glance.

Code / observed behavior

The line the build ships is a verbatim copy of the "What is it?" hero lede, which is page furniture carrying no footnote. The review reports that a grep across the level pages finds none of "Local hardware", "rented", "metered" or "left behind"; two of the line's clauses map loosely onto footnoted level-2 sentences, and "nothing left behind" maps onto nothing at all. The scenario names no level sentence to compare against and no party to do the comparing, so neither the build nor a reviewer can fail it.

Why it matters

This is the requirement in the navigation spec that governs a claim on the live Home page, and as written it neither identifies its source nor is checkable — the clause it exists to keep honest has no footnoted counterpart anywhere on the site. Unlike the rest of this review's findings, the sentence it fails to govern is published.

Remedy / check

Bind the positioning line to a named source — it repeats one sentence a level page carries with its footnote, and an editor can point at that sentence — or govern the hero lede in this spec as well, so the two copies have one owner. Then move the unbacked clause onto a level page with a footnote, or drop it.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

6efcbb8bThe next level-5 delta change is unwritten while two of its REDs are already outrun in the daily channelmediumtillandsias.org · openspec/changes, docs/matrix/level-5-phd.md · 2026-09-05
Claim

Task 4.18 of add-level-page-specs names the work for the next pin bump — the REDs on property tests and on requirement identifiers, the footnotes that drift between the tags, the measured numbers to re-measure — as its own OpenSpec change, one delta per item.

Code / observed behavior

The box is unchecked and no such change directory exists: openspec/changes/ holds add-container-framework, add-level-page-specs, level-5-stable-pin-deltas and an empty archive. The fixes it names are real at the daily and were read there — the generative property tests over the refinement operator, and the gate for stable requirement identifiers — and of level 5's fifteen cited files, seven differ between the two tags: fragments.rs, methodology/philosophy.yaml, methodology/proximity.yaml and scripts/local-ci.sh changed, and obligation.rs, obligation_props.rs and scripts/check-requirement-ids.sh exist only in the daily. Only the four the D2-D5 footnotes name are acknowledged on the page today.

Why it matters

Level 5 is the page that may lag, and lag is legitimate while it is recorded. Task 4.18 is the only record, it is a paragraph inside another change's task list, and it will be read by whoever bumps the pin rather than by whoever reviews the site next week.

Remedy / check

Open openspec/changes/level-5-pin-bump-<tag>/ now, empty except a proposal.md carrying task 4.18's list and the drift table of finding 247ed660, so the deltas accumulate against a change instead of a checkbox.

Evidence
Depends on

247ed660

Last recorded: · found. Full dated record ↗

Classification and release

stale-record · cb07e89 · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

764caa09Requirements freeze today's verdicts, so a pin bump would make the spec mandate a false sentencemediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-{1,4,5} · 2026-09-06
Claim

The level-5 spec states its own governing principle — this spec requires the form of the verdicts and never a particular verdict — and level-common orders that a RED the new pin's code has fixed becomes a past-tense flag or a GREEN.

Code / observed behavior

Three requirements do the opposite, and the reviews show two of them already falsified one release forward. Level 4 makes a REFUTED permanent and unconditional, with no pin condition, while the page's only REFUTED says the hardening litmus cannot fail at this release — a claim the change's own tasks.md 4.16 records as no longer holding on the newer build. Level 5's section enumeration mandates today's conclusions rather than the object each section covers: item 4 requires the gap between what the repository asserts of the operator and what the theorem needs, item 6 requires the law-of-large-numbers family the doctrine invokes and the hypothesis the architecture violates by design; the review found both falsified at the only candidate pin, where the workspace property-tests monotonicity of refine and the methodology withdraws its strong-LLN claim. Level 1 requires the method section to carry an honest limit that exists only because the pinned methodology currently withholds a metric proof.

Why it matters

At the next pin bump each of these compels the page to keep a sentence its source no longer supports — the precise failure level-common's RED lifecycle exists to prevent — or leaves the page unable to comply without inventing a defect. The level-5 case also reverses this change's own completed task 2.6, which stripped mandated verdicts out of every requirement. Nothing is published, so no reader is misled today; the cost falls on the first editor to move a pin.

Remedy / check

Condition each on the pin, as level 5's "Verdicts are re-derived, never frozen" already does: name the section's object and the job it does and let the conclusion be a fact of the pin; keep a REFUTED while it holds at the pin and restate it in the past tense, naming the release that changed it, when the pin moves; and require the limit the cited methodology records at the pin rather than today's wording of it.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

process · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

88d8d509The forge startup context tells the agent Vault is at `http://vault:8200`; the listener is TLS-onlymediumtillandsias · images/default/lib-common.sh, images/vault/vault.hcl · 2026-09-05
Claim

`images/default/lib-common.sh#L4441` — the text printed into every forge agent's opening context reads: "**Vault**: secrets are available at `http://vault:8200`; token is injected automatically."

Code / observed behavior

`images/vault/vault.hcl#L16-L21` configures the only listener with `tls_cert_file`, `tls_key_file` and `tls_client_ca_file` and no `tls_disable`, and `#L23` sets `api_addr = "https://vault:8200"`. The Rust side agrees: `crates/tillandsias-headless/src/vault_bootstrap.rs#L900` — `const VAULT_EXEC_ADDR: &str = "https://127.0.0.1:8200"`, and its own test at `#L4864` asserts `linux_vault_api_base_url(true) == "https://vault:8200"`.

Why it matters

This string is the first thing an autonomous agent reads about how to reach Vault, and following it produces a TLS-handshake failure against a plaintext request. It also mis-teaches the security posture: the in-enclave hop is authenticated TLS, and the context says it is not.

Remedy / check

Change the line to `https://vault:8200`.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

8dd0e5c5Nine requirements contradict their own scenario or a sibling requirementmediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-{1,2,3,4,5} · 2026-09-06
Claim

Each of these requirements is written to be applied by an editor who was not present when it was written, and openspec validate passes on all three changes, so nothing structural flags them.

Code / observed behavior

Nine requirements instruct and forbid the same thing. Level 4's required sections open "in this order" and close by permitting the surface sections to be reordered, silently dropping the assurance-last constraint stated two sentences earlier. Level 4 lists the argument markers as optional, with dropping one explicitly not a regression, while "Rejected arguments stay on the page" makes a REFUTED mandatory. Level 4's hardening requirement governs a "hardening section" its own section list never defines. Level 3 makes it a MUST that each subsystem subsection say what falls short at the pin, and its own third scenario says a subsystem with nothing wrong keeps its subsection. Level 3 requires each unanswered item of a multi-item RED to be named in a scoped no-path clause and then makes the scoping optional in the next sentence. Level 5 requires each argument section to end with the flags that judge it while the same requirement declares markers optional and the page's first argument section carries none. Level 1's app-vocabulary requirement asserts the pinned source records a machine check of the promise it has just shown, and its third scenario forbids presenting that check as proof of that promise. Level 2 requires a cost sentence that its own platform requirement would forbid on Linux, and gives no precedence rule where "verbs match what the code does" and "the footnote target carries the claim" pull in opposite directions — the case that produced both rules.

Why it matters

A requirement that contradicts itself cannot be applied: an editor either picks a half and calls it compliance, or reads the whole spec as approximate. Three of these would license deleting true page content, and one would license writing a claim false on a supported platform. Nothing is published, so no reader is misled today; the damage is to the next editor, who is the reader these specs are written for.

Remedy / check

Resolve each pair in favour of the scenario, which is the half derived from the page: state ordering once and positively; carve the required markers out of the optional list by reference; anchor the hardening rule to the boundary section's hardening paragraphs; make the shortcoming clause conditional; say which no-path form governs and when scoping becomes required; make the flag rule a placement rule; reword the app-vocabulary MUST to permit a machine check only when the written promise it enforces is named; and add the missing precedence clause.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

process · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

a988d3c4The forge startup context hands agents the pre-flip project.service.localhost URL shapemediumtillandsias · images/default/lib-common.sh, spec subdomain-naming-flip · 2026-09-05
Claim

The subdomain-naming-flip requirement stands at both tags and mandates service.project.localhost for every URL that names a forge service.

Code / observed behavior

The startup-context generator in images/default/lib-common.sh still emits the pre-flip shape — project.service.localhost — in the dev-server paragraph, at L4223 in v56.9.2.1 and at L4535 in v56.9.5.1, so every agent in every forge reads it. The browser MCP enforces the flipped shape and refuses any other host. The publishing paragraph a few lines below the stale one is correct, and so is the copy that lands in a project's .forge-startup-context.md, which is this file's output.

Why it matters

An agent that follows the context file hands its user a hostname the router has no route for and the browser tool refuses by shape, then debugs a dev server that looks dead. This is the fourth documented case of the startup context misinforming an agent — the vault address, the on-demand attestation claim and the Local Experts mode are already flagged on the site's level-3 page — and the only one that hands the agent a wrong artifact to give the user rather than a wrong belief about a service.

Remedy / check

Swap the two labels in images/default/lib-common.sh so the line reads service.project.localhost. The file is generated into each project and gitignored there, so nothing in the website repository can fix it: it is filed here alongside the vault-address and attestation entries, and the site's level-3 context-file RED picks it up at the next re-verification.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

cf6b4dffEvery claim MUST carry a footnote resolving at the level's pin is false of both pages that state itmediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-{1,4} · 2026-09-06
Claim

Level 1 and level 4 both open their "Claims are no stronger than their source" requirement with the same sentence: every claim MUST carry a footnote resolving at the level's pin. Level 1's Purpose says the same of every sentence on the page.

Code / observed behavior

The sentence is false of both pages and contradicted by the shared contract it sits under. level-common permits a footnote to point past the pin with an explicit @vTAG suffix, and both pages use it — the review counted roughly twenty @v56.9.5.1 footnotes on level 4 alone, none of which resolves at v56.9.2.1. Both pages also carry unfootnoted claims: level-1-five.md states its cloud framing and its doll-house mapping with no footnote marker at all, and the review lists three more lines there and three unfootnoted absolutes on level 4, none of them scheduled for repair in the change's tasks.md.

Why it matters

This is the first sentence of the requirement that governs every claim on the page, and it is contradicted by the shared contract, by the pages it describes, and — on level 4 — by another requirement in the same file that mandates PATH evidence at a newer tag. An editor either ignores it or deletes true prose to satisfy it. The specs are unpublished, so no reader is misled today; the pages themselves are unaffected by this finding, which is about what the spec says of them.

Remedy / check

Scope it: every factual claim about the runtime carries a footnote; a claim about the pinned release resolves at the pin, and a PATH clause reporting a newer build resolves at the tag its @vTAG names. Leave where and how footnotes resolve to level-common, and either schedule the unfootnoted lines or say that a section's framing sentence inherits the citations of the claims it introduces.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

d0c34ed6Nothing tells this repository that the runtime published a releasemediumtillandsias.org · skills/update-website/scripts/latest-release.sh · 2026-09-05
Claim

The skill's second automation candidate is the same drift report, fired when the runtime publishes a release, either by the release workflow pinging this repository or by a cron that polls tags.

Code / observed behavior

latest-release.sh resolves both channels correctly and cheaply, stable from the releases endpoint with cache-busting and a cross-check against the git tag, unstable over git ls-remote with no API call at all. It is polled by a human who remembers to run the loop.

Why it matters

Dailies land daily, the pages pin stable, and stable moves only on promotion — so the event worth waking up for is rare and easy to miss. During that window the site's PATH lines say a fix is not yet promoted about things that are promoted, which is the one lie this design exists to avoid.

Remedy / check

A cron entry on a host session running drift-report.sh and reporting to the operator whenever its last line is not ok:up-to-date; plan/host-notes.md is the channel a host session already has. A ping from the runtime's release workflow is better and belongs on the runtime's side of the fence, which makes it a request rather than a task.

Evidence
Depends on

3d0ef7e2

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

e2e95e62Two register MUSTs name no criterion an editor could applymediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-{1,4} · 2026-09-06
Claim

Level 1's register requirement and level 4's layer requirement are stated as MUSTs, which the change's tasks.md treats as checkable by an editor reviewing the page.

Code / observed behavior

Three clauses give nothing to check against. "One metaphor family" leaves family undefined, and the page mixes doll-house imagery, household and postal imagery and a literal UI element, so the MUST is either already breached or unenforceable. "Machine vocabulary" names no list, on a page whose own subject is that two different banned-word lists exist and differ. Level 4's requirement that a mitigation be criticised "without the criticism being softened" offers no test at all.

Why it matters

An unappliable MUST is either ignored, which teaches that the rest are optional, or used to justify an arbitrary edit; the metaphor clause's first use would be to delete prose that works. Nothing is published, so no reader is misled today.

Remedy / check

Keep the enforceable rule level 1 already states — one metaphor per object, and no second metaphor for an object that has one — and drop "one metaphor family". Define the vocabulary ban by reference to the banned-word lists the pinned source records, noting that more than one exists and the prose must avoid the union. Delete the "softened" clause, whose intent the scenario about what a cost bought already carries.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

process · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

ecbe8175Universal MUSTs assert of the pages and of the audit record what is not true of themmediumtillandsias.org · openspec/changes/add-level-page-specs/specs/site/level-{3,4,5} · 2026-09-06
Claim

These requirements are written as descriptions of the pages they govern and of how the project works, in the register of a contract the page already satisfies.

Code / observed behavior

Eight of them are false of their subject on the day they ship. Level 4 states that audits of the page are run against a build newer than the pin; the page's own annotation file is scoped to the pin itself, and the current dated record says it re-verified every claim at the pin. Level 4 requires promise, reason, price and purchase before the flags of every defended surface; the supply-chain heading is followed immediately by its first GREEN with no prose at all. Level 3 defines its required subsections as one per subsystem a launch brings up unconfigured, which by the page's own account excludes two of its eight subsections, and its add-or-remove scenario then licenses deleting them. Level 3's GREEN attribution MUST is broken by five of nine GREENs, and its "any list" MUST catches an illustrative naming of toolchains that nobody thinks is wrong. Level 5's closed section enumeration has no home for the live RED on the methodology's complexity constraint; its superlative ban forbids the theorem statements the page exists to make; and its Verdict rule requires every sentence there to restate an established claim, which the section's closing line does not.

Why it matters

A requirement falsified by its own subject teaches an editor that this spec's MUSTs are approximate — and several of these, applied literally, would have the editor delete true prose or a live RED to comply. The audit-premise sentence is worse than wrong: it reads as licence to distrust an at-the-pin audit, which is the steady state the update loop aims at. The specs are unpublished, so nothing here misleads a reader today.

Remedy / check

Scope each MUST to the case its own scenarios test, and make the audit premise conditional ("where an audit is run against a build newer than the pin"). Bind the Verdict rule to claims rather than sentences, the superlative ban to claims about the tree, the list rule to lists offered as the inventory of a bounded set, and widen level 3's subsection class to any subsystem a session reaches without the reader configuring it.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · main · cb07e89c411e6c63abb8995928ed9a6da34ddc8c

05229912The level-5 delta change is assembled by hand every timelowtillandsias.org · openspec/changes/level-5-stable-pin-deltas · 2026-09-05
Claim

The skill's fifth candidate is to produce the OpenSpec change for level 5 with the exact deltas and evidence, never the edit. The requirement it serves is one delta per requirement, flag or footnote touched, each with a line-range citation and a verbatim quote at the target tag.

Code / observed behavior

The change filed that day was assembled by hand: seven applied deltas D1-D7, four new footnotes [^41]-[^44], twenty-eight verbatim quotes added under the in-repo footnotes, and a task list whose every line had to be verified twice. Most of that is generable — the drift set, the new anchors, the quote text at the target tag, and the skeletons of proposal.md and tasks.md.

Why it matters

The failure the level-5 spec names — the page's diff containing a hunk that no delta lists — is detectable only if the change lists everything the diff should contain, and a generator that starts from the drift set does that by construction.

Remedy / check

About 120 lines on top of the re-anchor helper: take a target tag, diff the cited-file set, read level 5's rows out of a trial build at that tag, and emit the change directory with a proposal, one unchecked box per delta and the minimal delta spec validation insists on. It must never touch docs/matrix/level-5-phd.md; stopping short is the point.

Evidence
Depends on

296676d4

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

1e141597The 2026-09-04 annotation files were left unamended beside pages the verification pass rewrotelowtillandsias.org · docs/matrix/level-1-five.audit.md and its three 2026-09-04 siblings · 2026-09-05
Claim

Each annotation file is a per-footnote verdict table for the page it sits beside, written 2026-09-04 against the pinned release.

Code / observed behavior

The four 2026-09-04 files were last touched at 607b5cf, before the verification pass rewrote the pages they annotate at 4a56048, 8155885, ef65ab0 and de2759b. Their verdicts now describe text that is gone: the level-2 table still marks footnote 5 OK against ephemeral-secret-refresh/spec.md#L1-L20 with the gloss "reissued at every start", while the page's footnote 5 was re-anchored to #L9-L25 and the reassurance it carried was withdrawn as false in the reassuring direction.

Why it matters

An annotation file that contradicts the page beside it is worse than none: it is the artifact a next editor would consult to learn which claims were checked, and it will hand them a verdict that was overturned the same day.

Remedy / check

Amend each file with the corrections the verification pass applied, or — better, and jointly with finding a0be2422 — move them under docs/audit/ with their date in the name, where a superseded record is expected to be read as history.

Evidence
Depends on

a0be2422

Last recorded: · found. Full dated record ↗

Classification and release

stale-record · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

34460f90The hardening litmus header says the argv validator has zero production call sites; it has threelowtillandsias · openspec/litmus-tests/litmus-podman-idiomatic-security-flags.yaml · 2026-09-05
Claim

`openspec/litmus-tests/litmus-podman-idiomatic-security-flags.yaml#L66-L71` — "WHAT IT STILL DOES NOT COVER … that every production launch site actually APPLIES the envelope. The validator that would answer this — `validate_launch_argv` in the same module — has zero production call sites, which is order 972-6vaj's subject, not this file's."

Code / observed behavior

At this tag `validate_launch_argv` (`crates/tillandsias-podman/src/policy.rs#L110`) has three non-test callers: `crates/tillandsias-podman/src/container_spec.rs#L396` inside `fn build_run_argv`, and `crates/tillandsias-headless/src/main.rs#L11711` and `#L11877`. `container_spec.rs#L379-L384` documents the change explicitly ("THIS WAS A `debug_assert!` AND THEREFORE NOTHING … the validator had zero production call sites"), i.e. the header describes the state 972-6vaj fixed.

Why it matters

This is the file a reader consults to learn what the hardening gate does and does not prove, and it is currently more pessimistic than the truth in a way that hides the real remaining gap (see finding da34e5ee). A stale disclaimer in a security fixture is read as current fact.

Remedy / check

Replace the paragraph with the current state: the validator now guards `ContainerSpec::build_run_argv` and the two delegated-run entry points, and name the launch paths that still bypass it.

Evidence
Depends on

da34e5ee

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

373585f1The published site cannot be reached from a forge, so no claim about it was verified as served HTMLlowtillandsias.org · docs/audit, and the proxy allowlist in the runtime · 2026-09-05
Claim

The audit verifies what a reader of tillandsias.org sees, and the update loop ends by publishing to it.

Code / observed behavior

The site is not reachable from the forge that builds it: the connection is reset, because the proxy allowlist is baked into the proxy image and the project's own domain reached it only in the daily channel. So every claim in the day's records about what a reader sees rests on the committed var/html/index.html and its rebuild hash, not on served HTML.

Why it matters

The last link in the chain is unverified. A build can be byte-identical to the committed artifact and the served page still differ — a stale cache, a misrouted deployment, a worker that never took the commit — and nothing in this repository would notice. It is also the only claim class the checked build cannot reach, so it will stay unverified until a session outside the stable image looks.

Remedy / check

Until a forge on a release carrying the allowlist entry can fetch the site, have a host session or the operator fetch the published page and compare its hash with the committed artifact, and record the comparison in the run's record beside the rebuild hash.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

process · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

53650fa1Cross-references in the audit records name line ranges that moved during the daylowtillandsias.org · docs/audit/2026-09-05-issues-tillandsias.org.md, docs/audit/2026-09-05-v56.9.2.1.md · 2026-09-05
Claim

The records cite this repository's own files by path and line range, and state that line references into this repository were resolved at the site commit each record names.

Code / observed behavior

The commits that followed moved the lines. The site issues record cites skills/update-website/scripts/checked-build.sh#L22 as the line that invokes the Python build; at c4039e1 line 22 is `done` and the invocation is at L28, because the gate repair inserted a checkout loop above it. Where a reference was qualified with the commit it was read at — the two into scripts/build-matrix.py — it still resolves; the unqualified ones do not.

Why it matters

These records are the durable form of the day's findings, and a citation that lands on the wrong line is the same defect the pages are gated against. The fix pattern already exists in the same files: name the commit beside the range.

Remedy / check

Qualify every intra-repository line reference in the records with the commit it was read at, as the two build-matrix references already are, or re-anchor them at the commit the record now names.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

548baf18The audit record's Method paragraph still reports the superseded footnote countslowtillandsias.org · docs/audit/2026-09-05-v56.9.2.1.md · 2026-09-05
Claim

The record says every count in it was re-derived at the commit it describes, and that an earlier version describing the tree as it stood mid-run is superseded.

Code / observed behavior

The correction reached the header and the five per-level tables but not the Method section, whose closing paragraph still reports 22, 29, 141, 93 and 44 footnotes for levels one to five and 317 of 329 carrying a verbatim quote. Counted in the tree at c4039e1 the levels carry 23, 29, 147, 99 and 44, totalling 342 with 330 quoted — the same numbers the header now gives. The record therefore contradicts itself by thirteen footnotes, and by thirteen again on the quoted count.

Why it matters

The Method section is where the record explains why its numbers can be trusted, so a stale count there undermines the paragraph it sits in. It is also the residue of a fix believed complete, which is how a corrected record quietly goes stale again.

Remedy / check

Re-derive the three numbers in that paragraph at the commit the record names, as the header and the per-level tables already were.

Evidence
Depends on

67b4a3aa

Last recorded: · found. Full dated record ↗

Classification and release

stale-record · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

5811a59dThe byte-identical rebuild check is recorded as unrun, and it no longer passes now that the page embeds the ledgerlowtillandsias.org · openspec/changes/add-level-page-specs/tasks.md · 2026-09-05
Claim

Task 3.2 records the 2026-09-05 attempt as inconclusive — two different hashes, not comparable because three levels were being edited in the same working tree — and leaves the box unchecked, to be re-run on a clean tree.

Code / observed behavior

The check did start passing: rebuilt from the tree at c00622b, the committed page and a private build both hash to baab0e512b737f8467646dfc2688b4da609bc680b1b13c631dae2e7913cc3714, and the same run printed that all checked footnote targets resolve and exited zero. The box is still [ ] and still carries the superseded pair of hashes. It would now be wrong to tick it: since 30c6e5e the page also renders the folded findings ledger, and the committed var/html/index.html at c4039e1 was built against ledger fragments that are not in that commit, so rebuilding c4039e1 from its own tree gives a different hash — six findings on the committed board against the one fragment the commit carries.

Why it matters

That hash is the guarantee that the artifact Cloudflare serves is the one the sources produce, which is the assumption the deployment notes rest on when they leave the build command empty. An unchecked box says the guarantee is untested; worse, the guarantee has since stopped holding and nothing in the record says so.

Remedy / check

Commit the ledger fragments the published page was built from, re-run the check on the clean tree, and tick task 3.2 with the hash and the date beside it — so that the page, its sources and the ledger it renders are one commit.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

stale-record · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

71e14b04Denied runtime traffic gets a TCP reset; the proxy spec says HTTP 403 twicelowtillandsias · spec proxy-container, images/proxy/squid.conf · 2026-09-05
Claim

`openspec/specs/proxy-container/spec.md#L53` — "**THEN** the proxy SHALL deny the request with HTTP 403"; and again at `#L109` — "**AND** all other domains SHALL be denied with HTTP 403". A third scenario (`#L186-L188`) requires that "the proxy response SHALL include the blocked domain name for debugging".

Code / observed behavior

`images/proxy/squid.conf#L137-L141` — `acl strict_deny_acl localport 3128` followed by `deny_info TCP_RESET strict_deny_acl`, with the inline rationale "Send a TCP reset instead of an HTTP 403 error for strictly-denied runtime traffic. This prevents sophisticated tunneling/exfiltration over denied connections."

Why it matters

The behaviours are not interchangeable for a client: a 403 carries a body naming the blocked domain (which a third scenario in the same spec requires), a TCP reset carries nothing. Anyone debugging a blocked domain from inside a forge, or writing a test against the spec, is told to expect a status code that never arrives. The deliberate choice recorded in the config is the right one; the spec has simply not been updated to it.

Remedy / check

Change the two `HTTP 403` scenarios to `TCP_RESET` on port 3128, and either drop or re-scope the "response SHALL include the blocked domain" scenario, which a reset cannot satisfy.

Evidence

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

spec-vs-code · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

7d737704A footnote referenced more than once emits a duplicate element identifierlowtillandsias.org · scripts/build-matrix.py, var/html/index.html · 2026-09-05
Claim

The generated page gives each footnote reference an identifier so the footnote list can point back at the place in the prose that cited it.

Code / observed behavior

The identifier is built from the level slug and the footnote number alone, and is emitted at every occurrence of the reference, so a footnote cited twice on one page produces the same id twice. Of the 775 ids in the published page, 56 are emitted more than once — 69 duplicate emissions in all, every one of them a footnote back-reference. For example id="rlevel-1-five-5" appears twice in var/html/index.html.

Why it matters

Duplicate ids are invalid HTML, and the back-link they exist for lands on whichever occurrence the browser reaches first, which is not necessarily the one the reader came from. Assistive technology and in-page anchors both rely on ids being unique.

Remedy / check

Number the occurrences — append an occurrence index to the id for the second and later references — and have the footnote list link to the first.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

83309118README's release ledger has no row for v56.9.5.1lowtillandsias · README.md · 2026-09-05
Claim

`README.md#L107` — "The release skill appends a row per release; STABLE marks channel promotions." The ledger table header is at `#L112`.

Code / observed behavior

The newest row is `v56.9.4.1` (`#L114`), then `v56.9.2.1` (`#L115`), then `v56.8.31.3` (`#L116`). The string `56.9.5.1` does not appear anywhere in `README.md`, while `VERSION` at this tag reads `56.9.5.1`.

The source section records this as class `stale-plan-entry`; the ledger's nearest class is `stale-record`.

Why it matters

The ledger is explicitly addressed to agents ("Agents doing smoke curl-installs or jumpstarting work read the recent rows first", `README.md#L103-L104`), so an agent on the newest daily reads a ledger whose newest entry is a different release and has no way to see what changed under it. It is also the mechanism by which this site's explanation pages decide what a release shipped.

Remedy / check

Append the `v56.9.5.1` row, or make the release skill's ledger append a gated step rather than a convention.

Evidence
  • README.md#L107 — “The release skill appends a row per release; STABLE marks channel promotions.”
  • README.md#L110 — “<summary>Release ledger (newest first)</summary>”
  • README.md#L114 — “v56.9.4.1 (daily — cut 2026-09-04 by the coordinator's 20:09Z meta cycle, PR #105 merge + bump PR;”

No upstream issue filed.

Last recorded: · found. Full dated record ↗

Classification and release

stale-record · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

89ab166aThe loop figure is drawn and placed nowhere, and the new capability sections carry no figurelowtillandsias.org · scripts/figures.py, docs/matrix/level-3-power.md, docs/matrix/level-4-security.md · 2026-09-05
Claim

The matrix rules list eleven figures defined as inline SVG in scripts/figures.py, drawn with the page's own CSS variables so that they carry one idea each.

Code / observed behavior

All eleven exist and are registered. Ten are placed, fourteen times in all, seven of them on level 5. The loop figure — the closed loop from specs through evidence back to specs — appears in no level file, and neither its title nor its caption appears in any byte of var/html/index.html. The two sections written that day are figureless: level 3's eight-capability inventory has none, and level 4's layer-by-layer case has only the gate figure it already had.

Why it matters

A figure defined and never placed is dead weight in a file whose whole point is that the figures carry one idea each, and the longest, newest, most list-shaped section on the site has no visual anchor at all.

Remedy / check

Place @fig:loop in level 3's gate section, where the argument is exactly that specs, evidence and the gate close on each other, and move @fig:layers above the capability inventory instead of below it. Both are one-line edits under the existing dialect and no new figure needs drawing.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

956f6baaThe drift report over-counts because it ignores per-footnote release overrideslowtillandsias.org · skills/update-website/scripts/drift-report.sh · 2026-09-05
Claim

For each level the report lists the cited files that changed between the level's pin and the stable tag, on the ground that a footnote into a changed file needs re-verification even when its line range still resolves.

Code / observed behavior

Its path extraction takes the target out of every footnote definition and drops the trailing release override, so a footnote already anchored at the daily channel with an @vTAG suffix is counted as if it were resolved against the level's pin. Those footnotes are unaffected by a pin bump — the checked build resolves them against the release they name — yet they inflate both the changed-file count and the per-level totals the report prints.

Why it matters

The report exists to size the next promotion. An inflated number is the same kind of lie as an inflated claim on the page: it makes the work look larger than it is, and the one thing that makes a pin bump get postponed is the belief that it is large.

Remedy / check

Keep the @vTAG suffix when extracting targets, and exclude from the pin-versus-stable diff every footnote that names its own release.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

a0be2422The per-level annotation files are superseded and still sit in docs/matrix beside page contentlowtillandsias.org · docs/matrix/level-1-five.audit.md and its four siblings · 2026-09-05
Claim

Each annotation file says it is valid for the v56.9.2.1 pin only and superseded once the day's record under docs/audit/ lands; the audit directory's README agrees, and the level-common contract makes it a rule that they are never a source of requirement text.

Code / observed behavior

That record landed, so the annotation files are history — and they still sit in docs/matrix/, the directory whose README opens with the instruction to edit the words there and whose every other file is page content. Each opens with a comment header saying it is not page content. There are five of them now, level 5's having been added the same day at 4ba2294, so the audit's own remark that the set is incomplete is itself stale.

Why it matters

Two kinds of file in one directory, distinguished only by an infix, is a trap for the next editing agent working from a glob. The build protects itself by rendering only the slugs in its LEVELS table, so only a human reader is harmed, quietly.

Remedy / check

Move each to docs/audit/2026-09-04-level-<n>-<slug>.audit.md, then update the three places that name the old path: the audit README, the design note of add-level-page-specs, and the level-common requirement quoted above.

Evidence

Last recorded: · found. Full dated record ↗

Classification and release

doc-drift · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

c1223430Phone-level provenance claim exceeded its Linux source evidencelowtillandsias.org · docs/matrix/level-2-phone.md · 2026-09-24
Claim

The published phone-level copy said no installed Tillandsias program records its build snapshot.

Code / observed behavior

The cited runtime finding concerns the Linux launcher; the macOS tray embeds its build commit at crates/tillandsias-macos-tray/src/main.rs#L58-L64.

Why it matters

The universal wording hid the platform boundary and overstated what the Linux finding established.

Remedy / check

Scope the shortcoming and its path to the Linux launcher and cite the finding at the stable release.

Evidence
  • docs/matrix/level-2-phone.md#L66 — “> RED: After installing, you cannot tell from the program what was installed. It prints a version number that was baked in when it was compiled, and nothing on it records which snapshot of the code it was built from.”

Last recorded: · retracted. Full dated record ↗

Classification and release

site · main · d6e95bebe749e6077f375d21a503f16a0d6a00a6

e87c4f45Per-level re-verification is unautomated, and is the part that must stay reviewedlowtillandsias.org · skills/audit-site-claims/SKILL.md · 2026-09-05
Claim

The skill's fourth candidate is an agent per level running the audit-site-claims procedure, then a skeptic per level, then a human or a second agent reading the diff — needing a model, not free, and the part that should stay reviewed.

Code / observed behavior

The 2026-09-05 run did it by hand and at scale: an auditing agent per slice of the work and a skeptic per slice whose refutations overrode the audit, then a writer, a reviewer and a corrector per level. The record says why the skeptic pass is not optional — several corrections came from it rather than from the first pass, including the two sibling scripts that still create the enclave network without the isolation flag and the withdrawal of a commit-count figure.

Why it matters

This is the class of claim no script can check: a RED still literally true but no longer honest, prose that outran its footnote, a PATH line promising something the ledger has closed. It should be the last candidate automated and the first to keep a human on.

Remedy / check

Not a fix but a sequencing decision: automate the pre-push gate, the forecast and the re-anchor helper first, so that when agents do run, the objective failures are already impossible; then fan out one agent per level with a skeptic each, and gate publication on a human reading the diff.

Evidence
Depends on

52c8f180, 3d0ef7e2, 296676d4

Last recorded: · found. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

Tracked 0

Triaged, filed, active, blocked or terminal without a verified fix.

Resolved 12

Remedies checked in this website’s audit record.

56415013The checked build passed on footnotes whose release had no checkouthightillandsias.org · scripts/build-matrix.py, skills/update-website/scripts/checked-build.sh · 2026-09-05
Claim

The checked build is the gate this repository calls the thing that stops a lie from deploying: it resolves every footnote target against the checkout for the tag that footnote names, and fails on any unresolved target or drifted quote.

Code / observed behavior

With only the stable checkout present the build printed that all checked footnote targets resolved and exited zero while all 38 footnotes naming the daily channel went unopened — ten of them citing one of eight files that do not exist at the stable tag at all. Three causes: the check returned early when a release had no checkout and recorded nothing; the per-level line said "checked at <pin>" whatever it had actually opened; and the wrapper demanded checkouts only for the tags the levels pin, so the daily tag was never required.

Why it matters

The gate was silent about exactly the citations most likely to drift — the ones a PATH line uses to acknowledge a fix that is in the daily channel and not yet promoted. A gate whose green means only that it opened the easy half is worse than no gate, because the publishing step trusts it.

Remedy / check

Make an unchecked target a first-class result, report it per level and in a summary naming each level and release it could not answer for, require a checkout for every release any footnote names, and refuse the push on any unchecked target.

Evidence

Last recorded: · resolved. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

572f7388The safe-browser launcher defaults to host networking; its spec promises enclave-only egress through the proxyhightillandsias · scripts/launch-chromium.sh, spec chromium-safe-variant · 2026-09-22
Claim

`openspec/specs/chromium-safe-variant/spec.md#L18-L19` — "Full network isolation inside the enclave, with allowlist enforcement via the proxy only; no host-gateway internet fallback"; and `#L137-L138` — "**THEN** it SHALL connect through the enclave network **AND** all web egress SHALL traverse the proxy/allowlist path".

Code / observed behavior

`run-safe-browser.sh` execs `scripts/run-safe-browser.sh`, which execs `scripts/launch-chromium.sh safe-browser "$URL" 9222 open_safe_window` (`scripts/run-safe-browser.sh#L42`). That script's podman argv is built at `scripts/launch-chromium.sh#L84-L98`, and line 90 reads:

"--network=${TILLANDSIAS_BROWSER_NETWORK:-host}"

Neither wrapper sets `TILLANDSIAS_BROWSER_NETWORK` — the string does not appear in either file — so the documented entry point for the "safe" variant launches the browser in the host network namespace, with direct internet access and no proxy in the path. The surrounding flags (`--cap-drop=ALL`, `--security-opt=no-new-privileges`, `--read-only`, tmpfs mounts) are all correctly applied; only the network default contradicts the spec.

Why it matters

The variant's name and its spec both say the containment is network containment, and that is the one control the default turns off. A user running `./run-safe-browser.sh --url …` gets a hardened-but-unisolated browser while every other property the spec lists is genuinely enforced, which is the hardest kind of gap to notice.

Remedy / check

Default to `tillandsias-enclave` (`:-tillandsias-enclave`) and keep `host` reachable only by explicitly setting `TILLANDSIAS_BROWSER_NETWORK`.

Evidence

No upstream issue filed.

Last recorded: · resolved. Full dated record ↗

Classification and release

spec-vs-code · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

6cd2f616The permissive proxy port grants unfiltered egress to every enclave member with no source restrictionhightillandsias · images/proxy/squid.conf, spec proxy-container · 2026-09-22
Claim

`openspec/specs/proxy-container/spec.md#L104` — the dual-port table lists port 3129 as "Permissive | All domains allowed", and `#L112-L114`: "**WHEN** a request is sent through port 3129 **THEN** all domains on SSL ports (443, 8443) SHALL be permitted **AND** domain filtering SHALL NOT apply."

Code / observed behavior

`images/proxy/squid.conf#L60` — `http_port 3129 ssl-bump \` with no bind address, so squid listens on `0.0.0.0:3129` on every interface the container has. `#L133-L134` — `http_access allow CONNECT SSL_ports build_port` / `http_access allow build_port`, with `build_port` defined at `#L93` as `acl build_port localport 3129` and no source ACL anywhere in the file. The container is dual-homed: `crates/tillandsias-headless/src/main.rs#L3425-L3426` passes `--network ENCLAVE_EGRESS_NETS` (= `"tillandsias-enclave,tillandsias-egress"`, `#L1651`), so the listener is reachable from both networks.

Why it matters

Every enclave container is handed `http_proxy=http://proxy:3128` (`main.rs#L1711-L1717`), but nothing prevents any of them from using `http://proxy:3129` instead and reaching the internet with the allowlist skipped entirely. The port's own header comment (`squid.conf#L6-L45`) is careful and honest about the port having no consumer today, and `scripts/check-proxy-permissive-port-routing.sh` pins that honesty — but "no consumer" is a statement about Tillandsias's own code, not about what an agent inside a forge can dial. Filtering that a client can opt out of by changing a port number is not a boundary, and the explanation pages describe the allowlist as one.

Remedy / check

Bind the permissive listener to loopback (`http_port 127.0.0.1:3129 …`) so it is unreachable from the enclave, or add a source ACL restricting it, until a real consumer exists.

Evidence

No upstream issue filed.

Last recorded: · resolved. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

6e61831eThe mirror relay wires the GitHub credential helper for any `https://` origin, and the helper answers unconditionallyhightillandsias · images/git/relay-refs.sh, images/git/git-credential-tillandsias.sh · 2026-09-22
Claim

`images/git/git-credential-tillandsias.sh#L34-L37` states the assumption the design rests on: "Drain stdin (git sends protocol/host/path). We do not branch on it: this helper is wired per-invocation by the relay for one specific remote, so answering unconditionally is correct".

Code / observed behavior

`images/git/relay-refs.sh#L182-L221` — the credential wiring is inside `case "$REMOTE_URL" in https://*)`, i.e. it triggers for any HTTPS upstream, not for GitHub specifically. `REMOTE_URL` is the project's own `remote.origin.url`, read from the host checkout and passed into the mirror (`crates/tillandsias-headless/src/main.rs` `fn read_host_project_origin_url`, `#L3653-L3660`, and the `remote.origin.url` read at `#L3588`). The helper it installs is `images/git/git-credential-tillandsias.sh`, which does not branch on the host git asks about and returns the Vault-held GitHub token (`#L48-L57`) for whatever host that is.

Why it matters

The helper's assumption ("wired for one specific remote") is enforced nowhere: the relay wires it on the basis of the URL *scheme* alone. A project whose `origin` is any non-GitHub HTTPS remote — or a redirect chain ending at one — gets the user's GitHub token offered to that host on the first push through the mirror. The mirror is the privileged component in this design precisely because it holds the credential the forge is denied; the condition that decides when it hands that credential out should be the host, not the scheme.

Remedy / check

Match on the origin host (`https://github.com/*`, plus any explicitly configured enterprise host) rather than `https://*`, and have the helper read `host=` from stdin and refuse anything it does not recognise.

Evidence

No upstream issue filed.

Last recorded: · resolved. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

9f823c95Two scripts create the enclave network without --internal, and the new guard cannot see themhightillandsias · scripts/orchestrate-enclave.sh, scripts/run-forge-project.sh, scripts/diagnose-proxy.sh · 2026-09-22
Claim

The enclave is isolated because its network is created with --internal, and a build-gate check now reads the create sites.

Code / observed behavior

scripts/run-forge-project.sh#L116-L119 and scripts/diagnose-proxy.sh#L90-L93 still create tillandsias-enclave with no --internal; scripts/check-enclave-network-internal.sh#L87-L115 checks a restated three-file list rather than discovering create sites, so both pass the gate.

Why it matters

A stack brought up by either script is not isolated, and the guard's green means only that the three files it names were clean. A check whose coverage is a hardcoded set reports the set, not the property.

Remedy / check

Make the guard discover every create site from source rather than restating a file list, and add the flag to both scripts.

Evidence

No upstream issue filed.

Last recorded: · resolved. Full dated record ↗

Classification and release

spec-vs-code · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

f664ad4aThe inference engine is fetched at first run from an unpinned `latest` release with no integrity check, the specs promise baked models the image does not carry, and the health check tests the runner binaryhightillandsias · images/inference/entrypoint.sh, images/inference/Containerfile, specs inference-container and zen-default-with-ollama-analysis-pool · 2026-09-22
Claim

`openspec/specs/zen-default-with-ollama-analysis-pool/spec.md#L47-L54` — T0 `qwen2.5:0.5b` and T1 `llama3.2:3b` are both marked "Baked", and "T0 and T1 are baked into the inference image at build time so the first attach has a usable analysis model with zero network overhead"; `#L59` repeats it as a scenario outcome. `openspec/specs/inference-container/spec.md#L285` lists "T0 models baked into image" as a gating point (while `#L110` of the same file says they are pulled at startup — the two specs already disagree with each other).

Code / observed behavior

Three separate gaps in one container.

*Unpinned, unverified engine.* `images/inference/entrypoint.sh#L293` — `OLLAMA_URL="https://github.com/ollama/ollama/releases/latest/download/ollama-linux-${OLLAMA_ARCH}.tar.zst"`. `latest` is a moving target, and the download at `#L309-L312` is followed by `zstd -dc | tar -xf` and `install -m 0755 … "$OLLAMA_BIN"` (`#L345-L348`) with no checksum, no signature and no pinned digest anywhere in the file. The fetch goes through the enclave proxy, which bumps `release-assets.githubusercontent.com` with a certificate the container is configured to trust (`openspec/specs/proxy-container/spec.md#L36-L42`), so TLS is not standing in for provenance here either.

*Promised baked models that are absent.* `images/inference/Containerfile#L70` only runs `mkdir -p /opt/baked-models && chown`; no `COPY` ever puts a model there, so the seeding block at `entrypoint.sh#L399-L406` has nothing to seed from. T1 is absent altogether: `entrypoint.sh#L120` sets `DEFAULT_MODELS="${TILLANDSIAS_DEFAULT_MODELS:-qwen2.5:0.5b}"`, and the comment at `#L114-L115` records that `llama3.2:3b` was replaced. So `llama3.2:3b` is named by both specs and is neither baked nor pulled.

*A health check that cannot see the failure it was written for.* `images/inference/Containerfile#L118-L121` — `curl -sf http://127.0.0.1:11434/api/version && test -x /home/ollama/.ollama/models/.tools/lib/ollama/llama-server`. The header above it (`#L100-L117`) explains the live incident it exists for: the container sat at `Up (healthy)` while every `/api/generate` returned HTTP 500. The repair upgrades the probe from "the port is bound" to "the port is bound and a file is executable" — it still never loads a model, so a present-but-broken engine or an empty model cache still reads healthy.

Why it matters

The engine binary is the largest single untrusted input the runtime installs, and it arrives from a mutable URL into a container that then serves every forge on the enclave. The baked-model promise is what the specs offer as the offline/first-attach guarantee, and it is not there. And the health signal that the launcher's readiness gate believes is still a proxy for serving, not serving.

Remedy / check

Pin the ollama release to a version and verify its published checksum before extracting; then either bake T0/T1 (`COPY` into `/opt/baked-models`) or correct both specs to say the models are pulled and drop `llama3.2:3b`; and make the health check issue one tiny `/api/generate` (or `/api/show`) against T0 rather than stat a file.

Evidence

No upstream issue filed.

Last recorded: · resolved. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

0fdb0cb3The browser allowlist cannot open any window for a project whose directory name contains a dotmediumtillandsias · crates/tillandsias-browser-mcp/src/allowlist.rs, crates/tillandsias-headless/src/local_projects.rs · 2026-09-22
Claim

`browser.open` is offered as a working MCP surface for any local project, and the allowlist is the component that decides which host a project may open.

Code / observed behavior

`crates/tillandsias-browser-mcp/src/allowlist.rs#L233-L246` — `validate()` splits the host on `.` and refuses anything that is not exactly three labels:

let labels = split_host_labels(&host); if labels.len() != 3 || labels[2] != "localhost" { return Err(AllowlistDeny::HostShape); } … if host_project_label != project_label { return Err(AllowlistDeny::ProjectMismatch { … }); }

The project label it compares against is the raw directory name. `crates/tillandsias-browser-mcp/src/server.rs#L62-L63` reads it from `TILLANDSIAS_PROJECT`, which the forge builders set to `project_name` (`crates/tillandsias-headless/src/main.rs#L11236`, `#L14437`), and `project_name` comes from `scan_project_root`'s `path.file_name()` (`crates/tillandsias-headless/src/local_projects.rs#L76-L78`) with no dot-substitution — only a leading-dot skip.

Why it matters

For a project directory such as `tillandsias.org`, the label is `tillandsias.org`, so every candidate host is four labels (`web.tillandsias.org.localhost`) and hits `HostShape`; and a three-label host would fail `ProjectMismatch` because `labels[1]` is `tillandsias`. There is no path through `validate()` that succeeds, so `browser.open` is unusable for the whole class of projects named after a domain — a common naming convention, and the one this very site's repository uses.

Remedy / check

Compare the trailing labels against the project label rather than requiring `len() == 3`, or normalise dots to `-` when deriving the label (matching `project_label_from_path` at `main.rs#L11189`, which already does this for container hostnames) and use the same normalised label on both sides.

Evidence

No upstream issue filed.

Last recorded: · resolved. Full dated record ↗

Classification and release

defect · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

1b27ef15CentiColon page overstates enforcement and fixed-point completionmediumtillandsias.org · CentiColons and Level 5 · 2026-09-28
Claim

The site presents CentiColons as an enforceable metric, a validator fixed point as completion, and the Lua path as grading systemic invariants hermetically in milliseconds.

Code / observed behavior

At runtime v56.9.27.2, scripts/check-centicolon-ratchet.sh#L4-L29 reports advisory results against an untracked local baseline. scripts/lua/centicolon-grade-observed.lua#L38-L105 selects observations by test/digest, not required platform, and counts positive-test credit. methodology/math-foundations.yaml#L200-L206 calls fixed points validator stability, not completion.

Why it matters

A measurable score does not prove evidence applies or that residuals strictly decrease; readers could infer an enforced convergence guarantee that the stable release does not provide.

Remedy / check

Distinguish the simplified example, shipped advisory measurement, proposed evidence contract and conditional finite-time progress theorem.

Evidence

Last recorded: · resolved. Full dated record ↗

Classification and release

site · main · b3f3091cb6d21b47349e2b407c46981d9d34b515

66b46da4The runtime issue drafts cited annotated-tag object hashes as commitsmediumtillandsias.org · docs/audit/2026-09-05-issues-tillandsias.md, skills/file-findings/SKILL.md · 2026-09-05
Claim

Each section of the runtime issue drafts is meant to be pasted into the runtime's tracker with the commit its evidence was read at, so a reader can follow the citation to the code.

Code / observed behavior

The header and the Tag line of all 22 issue sections carried the hash of the annotated tag object — 77958f55… for v56.9.5.1, 49458e96… for v56.9.2.1 — rather than the commit it points at. Pasted into the tracker each would have cited a hash that resolves to no commit there. The mistake was in the instruction rather than in the transcription: the issue template in the file-findings skill said only "commit …" beside the tag.

Why it matters

A finding relayed to another project is only as good as its citation. A hash that resolves nowhere makes the whole relay unusable, and the template would have reproduced the error in every future run.

Remedy / check

Peel every hash to the commit, and make the template say to peel — git rev-parse <tag>^{commit} — because a bare rev-parse of an annotated tag gives the tag object.

Evidence

Last recorded: · resolved. Full dated record ↗

Classification and release

defect · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

67b4a3aaThe audit record's own counts went stale before it was publishedmediumtillandsias.org · docs/audit/2026-09-05-v56.9.2.1.md · 2026-09-05
Claim

The run's record carries the per-level tables and the headline totals for the site the run produced.

Code / observed behavior

It was written mid-run and never updated, so it described a site that was never published: 329 footnotes with 317 quoted against 342 and 330 in the tree it claimed to describe, per-level counts of 22, 141 and 93 against 23, 147 and 99, and a level-2 section still counting a verdict the verification pass deleted and a verified claim the same pass withdrew as false.

Why it matters

This record is the artifact a reader consults to learn what the day's work established. Counts that belong to no tree make every other number in it unfalsifiable, and the level-2 error pointed the wrong way — it reported a reassurance that had been withdrawn precisely because it was false in the reassuring direction.

Remedy / check

Re-derive every count at the commit the record describes, name that commit in the record, and say what actually happened to both level-2 flags.

Evidence

Last recorded: · resolved. Full dated record ↗

Classification and release

stale-record · c4039e1 · c4039e18f5533054f368b9836867655880d55c09

60a07324Checkout preparation omitted runtime evidence releases needed by ledger validationlowtillandsias.org · skills/update-website/scripts/fetch-checkouts.sh · 2026-09-24
Claim

The update skill's checkout step prepares releases used by the checked build.

Code / observed behavior

Before the fix, fetch-checkouts.sh gathered pins, stable, daily, daily footnote overrides, and explicit arguments, but not historical runtime tags carried only by issues.d findings.

Why it matters

A fresh checked build then left historical ledger evidence unchecked, so the required gate failed despite every page pin checkout being present.

Remedy / check

Include each folded runtime finding's cited release tag in the checkout set.

Evidence

Last recorded: · resolved. Full dated record ↗

Classification and release

process · main · d6e95bebe749e6077f375d21a503f16a0d6a00a6

984bf6ccThe published `stable` git tag points at a stale commitlowtillandsias · git tags, release promotion · 2026-10-24
Claim

The `stable` git tag is the channel pointer a reader (and this site's pins) treats as naming the current stable release.

Code / observed behavior

What was observable at v56.9.5.1: the runtime mirror this site audited from carried no `stable` ref at all, so a reader resolving `stable` there got nothing, while the release ledger named v56.9.2.1 as the current stable.

What is observable at v56.10.20.1: the origin repository carries a `stable` tag, and it names the commit that merged the 56.10.20.1 version bump, the release the site now pins. The channel pointer and the ledger agree.

Why it matters

A channel tag that names the wrong commit misdirects anyone resolving `stable` — including this site's release pins — and the correction is an operator action rather than a code change.

Remedy / check

Operator decision: move the published `stable` tag, and make promotion tag the pushed release commit rather than the promoting checkout's local main.

Evidence
  • README.md#L115 — “v56.9.2.1 (**STABLE** — cut and promoted 2026-09-02 unattended, operator-authorized while asleep; PRs #103 merge + #104 bump;”

No upstream issue filed.

Last recorded: · resolved. Full dated record ↗

Classification and release

stale-record · v56.9.5.1 · b528680d2b938b09279e31faee981a8f6e370b3b

Runtime specifications 185

All canonical specs at v56.10.20.1, including retired documents. Status here is the document’s own lifecycle label.

specaccel-capability-probestatus: active authored-by: order 479 (`heterogeneous-inference-specs`), spec-before-implementation derived-from: `plan/issues/heterogeneous-inference-cpu-gpu-npu-research-2026-07-24.md` gates: order 480 (`accel-capability-probe`), order 481 (`npu-device-passthrough`), order 483 (`host-native-sidecar-endpoints`), order 484 (`inference-policy-router`)9 requirements

Replace the single-string inference tier (`detect_inference_tier()` → `metal|gpu-cuda|gpu-rocm|cpu`) with a structured, versioned, host-side capability document describing every execution device the host actually has, which engine lanes can reach each one, and how fast each combination measured.

Document status: status: active authored-by: order 479 (`heterogeneous-inference-specs`), spec-before-implementation derived-from: `plan/issues/heterogeneous-inference-cpu-gpu-npu-research-2026-07-24.md` gates: order 480 (`accel-capability-probe`), order 481 (`npu-device-passthrough`), order 483 (`host-native-sidecar-endpoints`), order 484 (`inference-policy-router`). This describes the spec’s lifecycle, not verified implementation.

Requirements (9)
  1. PROBE-1 — Structured capability document replaces the string tier
  2. PROBE-2 — Device enumeration is explicit and vendor-resolved
  3. PROBE-3 — Engine inventory gates usability
  4. PROBE-4 — Memory bandwidth is recorded as a first-class quantity
  5. PROBE-5 — Microbenchmarks are bounded, one-time, and degrade gracefully
  6. PROBE-6 — Roofline sanity check flags degraded lanes
  7. PROBE-7 — The probe runs on the host and records per-lane reachability
  8. PROBE-8 — The capability cache is derived, ephemeral, and self-invalidating
  9. PROBE-9 — The document names the host it describes

Read full pinned spec ↗

specagent-cheatsheetsobsolete0 requirements

This umbrella spec has been retired. Its remaining live obligations now live in:

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specapp-lifecycleactive14 requirements

TBD - created by archiving change tillandsias-bootstrap. Update Purpose after archive.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (14)
  1. User-facing app semantics
  2. Running environment tracking with lifecycle iconography
  3. Graceful stop
  4. Destroy with safety hold
  5. Tray status display
  6. shutdown_all terminates web containers and closes webviews
  7. Orphan web containers are swept on shutdown
  8. Webview close is not an exit signal
  9. ExitRequested discriminates on code
  10. Tray Quit dispatches MenuCommand::Quit (no direct process exit)
  11. Event loop owns the sole shutdown_all invocation
  12. shutdown_all removes containers AND destroys the enclave network
  13. shutdown_all verifies post-sweep state and escalates stragglers
  14. kill_container accepts an explicit signal

Read full pinned spec ↗

specappimage-build-pipelinestatus: obsolete0 requirements

No purpose recorded in this spec.

Document status: status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specartifact-detectionobsolete0 requirements

This umbrella spec has been retired. Its live obligations are now covered by:

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specasync-inference-launchactive promoted-from: openspec/changes/archive/2026-04-16-async-inference-launch/ annotation-count: 93 requirements

Defer inference container startup to a background task, unblocking forge launch within 2-5 seconds while inference initializes asynchronously. Inference is a soft requirement — failure logs `DEGRADED` but does not block coding session start.

Document status: active promoted-from: openspec/changes/archive/2026-04-16-async-inference-launch/ annotation-count: 9. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Detached inference startup task
  2. Startup-time empirical validation
  3. Enclave readiness message clarity

Read full pinned spec ↗

specbinary-signingactive3 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Cosign bundle artifacts
  2. Cosign signing produces verifiable bundles
  3. Release verification instructions stay bundle-based

Read full pinned spec ↗

specbranch-disciplinestatus: draft7 requirements

A project's branch discipline — the protected default branch, the integration branch per platform, the work-ref and salvage grammar, the plan-only lane, the freeze namespace and the operator-worded denial message — is DATA in the project (`.tillandsias/branch-discipline.yaml`), not a constant in a tool — and it is checked against what the project's history a

Document status: status: draft. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. The seed is per project and has a built-in default
  2. The runtime answers discipline questions
  3. The mirror enforces the seed and publishes the enforced level
  4. The level is derived from observed facts and checked against the seed
  5. Hooks are templates installed on demand for the project's level
  6. The mirror dispatches the project's hooks per push event
  7. The land tool asks before it fetches, gates or pushes

Read full pinned spec ↗

specbrowser-daemon-trackingobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specbrowser-debounceobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specbrowser-isolation-coreactive3 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Security model for chromium-core image
  2. Content-hash image identity with human aliases
  3. Build invocation via build-image.sh

Read full pinned spec ↗

specbrowser-isolation-frameworkactive4 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Content-hash image identity with human aliases
  2. Image hierarchy locked to chromium-core
  3. Security model inheritance and framework-specific isolation
  4. Build invocation via build-image.sh

Read full pinned spec ↗

specbrowser-isolation-launcherobsolete0 requirements

This placeholder spec has been retired. The real browser-launch contract is now owned by:

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specbrowser-isolation-tray-integrationactive7 requirements

Define how the tray menu integrates with browser isolation containers to launch safe GUI browser windows for OpenCode Web and other web-based tools.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Tray launches run in the desktop user session
  2. OpenCode Web launches in browser isolation
  3. Versioned identity without `latest`
  4. Safe window type by default
  5. Browser launcher uses the secure OpenCode Web session
  6. Browser window lifecycle
  7. No Tauri webview path

Read full pinned spec ↗

specbrowser-mcp-serverobsolete0 requirements

Historical stub retained for traceability only. The live browser MCP server contract now lives in `host-browser-mcp`, with transport and tray ownership split across `tray-host-control-socket` and `browser-isolation-tray-integration`.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specbrowser-routing-allowlistactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Browser routes are allowlisted by project and service

Read full pinned spec ↗

specbrowser-tray-notificationsobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specbrowser-window-lifecycleactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Browser windows have explicit lifecycle state

Read full pinned spec ↗

specbrowser-window-rate-limitingobsolete0 requirements

Historical duplicate of the browser launch throttle. The live debounce ownership now lives in `host-browser-mcp`.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specbrowser-window-timeoutactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Browser windows expire after inactivity

Read full pinned spec ↗

specbuild-lockobsolete0 requirements

Historical build-serialization helper. The live build coordination behavior is now owned by `init-command` and the higher-level build orchestration specs.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specbuild-script-architecturestatus: obsolete0 requirements

No purpose recorded in this spec.

Document status: status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speccache-isolationactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Durable cache and project state remain separated

Read full pinned spec ↗

speccache-recovery-mechanismactive5 requirements

Formalize the behavioral contract for fresh-start cache initialization. An absent `cache_version` file indicates a valid fresh system state (first run, post-reset, ephemeral deployment), not a version mismatch error. This spec prevents regression of the unwrap_or(false) fix at main.rs:549 and documents the cache lifecycle from initialization through corrupti

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Fresh Start is NOT a Version Mismatch
  2. Actual Mismatch Triggers Error (with Recovery Path)
  3. Corruption Recovery
  4. Cache Directory Lifecycle and Paths
  5. Version File Format and Semantics

Read full pinned spec ↗

speccdp-robustnessactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. CDP operations handle transport failure explicitly

Read full pinned spec ↗

speccertificate-authorityobsolete0 requirements

Historical umbrella for the ephemeral CA lifecycle. The live requirements are now owned by `reverse-proxy-internal`, `podman-secrets-integration`, and `secrets-management`.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speccertificate-validationactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Enclave TLS validation is explicit

Read full pinned spec ↗

speccheatsheet-mcp-serverobsolete0 requirements

This spec is a historical tombstone.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speccheatsheet-methodology-evolutionobsolete0 requirements

This placeholder spec has been retired. Its intended live behavior is now covered by methodology:

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speccheatsheet-source-layeractive6 requirements

Define the repository-side source binding, validation, and refresh tooling for cheatsheet provenance.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Verbatim source storage
  2. License allowlist gates bundling
  3. Provenance binding
  4. Validator invariants
  5. Hot/cold separation
  6. Refresh behaviour

Read full pinned spec ↗

speccheatsheet-toolingactive4 requirements

Define the repository-side cheatsheet structure, template, and generated index contract. This spec owns the cheatsheet tree layout and the authoring/indexing tooling that keeps the cache queryable.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Cheatsheet tree structure is fixed
  2. Cheatsheet template is standardized
  3. INDEX.md is generated, not hand-edited
  4. Cheatsheet authoring stays toolable

Read full pinned spec ↗

speccheatsheets-license-tieredactive0 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specchromium-debug-variantactive8 requirements

Unrestricted, ephemeral Chromium variant for testing and debugging. Runs with all domains accessible, DevTools enabled, extensions allowed, and verbose logging. Profile and cache are tmpfs-backed and destroyed on shutdown. Designed for engineers testing web features in development environments without production safety constraints.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. Unrestricted domain access
  2. DevTools and console access
  3. Extensions and plugins allowed
  4. Verbose network logging
  5. Ephemeral profile and cache
  6. Verbose logging to host
  7. Safe isolation (still ephemeral)
  8. Litmus test — chromium-debug-variant lifecycle

Read full pinned spec ↗

specchromium-safe-variantactive7 requirements

Ephemeral, restricted Chromium variant for production web access. Runs in a tmpfs-backed container with minimal capabilities, domain allowlist enforcement, zero credential persistence, zero cache, and zero sync. Profile and tabs are destroyed on shutdown. Designed for safe, auditable web access in development environments.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Ephemeral profile on tmpfs
  2. Domain allowlist enforcement
  3. Credential isolation — no persistence
  4. Cache is ephemeral and minimal
  5. Minimal capabilities — cap-drop=ALL
  6. No extensions or plugins
  7. Litmus test — chromium-safe-variant lifecycle

Read full pinned spec ↗

specci-releaseactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. A new gate decider or gate fixture runs on the Lua runner

Read full pinned spec ↗

speccli-bash-modestatus: obsolete0 requirements

No purpose recorded in this spec.

Document status: status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speccli-diagnosticsobsolete0 requirements

This specification documents the retired Tauri-era `--diagnostics` live-tail flow. That design mixed log streaming, container discovery, and debugging output into a single CLI command and is no longer part of the current architecture.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speccli-modeactive10 requirements

Define the interactive CLI contract for the shipped Tillandsias binary. The binary is a compiled runtime orchestrator: it may embed static assets and metadata, but it MUST NOT depend on repository shell scripts for user-facing runtime behavior.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (10)
  1. CLI mode launches container from terminal
  2. Runtime paths are compiled Rust
  3. Observatorium project view
  4. OpenCode Web launch reuses the router when already present
  5. Image selection flag
  6. Debug flag
  7. User-friendly output
  8. Security flags are non-negotiable
  9. CLI modes are tray-aware
  10. SIGINT triggers clean shutdown on every CLI path

Read full pinned spec ↗

specclickable-trace-indexactive1 requirements

Generate a searchable, clickable index of all `@trace spec:<name>` annotations in the codebase, enabling developers and agents to navigate from any spec to all code locations that implement it. The index lives in `TRACES.md` at the repository root (GitHub-renderable Markdown with clickable links) and per-spec back-link files at `openspec/specs/<name>/TRACES.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Project observatorium view

Read full pinned spec ↗

speccodex-tray-launcheractive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Tray-launched Codex uses the forge entrypoint contract

Read full pinned spec ↗

speccommand-policiesstatus: draft6 requirements

A declarative COMMAND POLICY decides, for one argv request, whether the runtime may run it: allow, deny, or consent-required. Policies are versioned in the repository, evaluated inside every door onto the runtime (the Lua `proc`/`sh`/`fs` verbs, the `run` verb, the MCP tool, the temporary PreToolUse bridge), keyed by command family, host kind and regime, and

Document status: status: draft. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. A built-in floor that a project seed can only tighten
  2. Evaluation is keyed by command family, host kind and regime
  3. Every refusal names why and what would clear it
  4. Filesystem scope under the fixture regime
  5. Consent is per run; soft reset is pre-authorised in forges, hard reset never is
  6. Every decision is audited with secrets redacted

Read full pinned spec ↗

speccommand-runtimestatus: draft8 requirements

Agents and committed automation execute commands through ONE Rust-hosted runtime (`tillandsias_exec` behind the plan binary's Lua environment and its `run` verb) instead of through shell strings. A command is an argv with named parameters; stdout, stderr and the completion are three separate values; time and output are bounded; the child starts from a fixed

Document status: status: draft. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. A command is an argv with named parameters, never a shell string
  2. The run verb is the agent's door and reads argv as JSON when asked
  3. A result is typed and carries its run identity
  4. Time and output are bounded and a clipped capture cannot look whole
  5. Every Lua door judges and runs in one directory, from one environment, under one deadline rule
  6. Script code cannot outrun its verdict or load bytecode
  7. A terminated runner leaves no script-owned process behind
  8. The runtime runs identically on every locus

Read full pinned spec ↗

speccontainerfile-stalenessactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Runtime image source digest detects stale images

Read full pinned spec ↗

speccross-platformdeprecated0 requirements

No purpose recorded in this spec.

Document status: deprecated. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specdefault-imageactive27 requirements

TBD - created by archiving change attach-here-mvp. Update Purpose after archive.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (27)
  1. Fedora Minimal base image with dev tools
  2. Non-root user with UID 1000
  3. Entrypoint launches OpenCode
  4. Prompted CLI entrypoints emit structured results only on request
  5. Declarative image definition via embedded Containerfiles
  6. Image identity is content-hash based with human aliases
  7. Forge image ships an OpenCode Web entrypoint
  8. OpenCode Web entrypoint runs opencode serve
  9. Default opencode model is a tool-capable Zen provider
  10. Cooperative split documented in agent instructions
  11. Coding-agent harnesses refresh at launch with persistent fallback
  12. Forge worker identities are collision-resistant across launch and teardown
  13. Codex state persistence excludes provider authentication and is worker-namespaced
  14. OpenCode consumes Vault authentication without credential files
  15. Agent instructions document subdomain routing convention
  16. Forge ships headless Chromium and headless Firefox with WebDriver bridges
  17. Forge image bakes the cheatsheets directory at /opt/cheatsheets/
  18. Forge entrypoint surfaces TILLANDSIAS_CHEATSHEETS to agents
  19. forge-welcome.sh prints the cheatsheet location once per session
  20. Forge image ships cheatsheets at /opt/cheatsheets-image (image-baked canonical)
  21. OpenCode config includes 4 new instruction files
  22. config-overlay installs 4 new instruction files
  23. methodology.md becomes an index
  24. Forge image ships the host-browser MCP stub
  25. Agent permission defaults — pre-grant container-local filesystem; enforce at the boundary
  26. Forge validation profile is non-destructive and machine-readable
  27. Forge harness permission bypass is pre-accepted at launch, never prompted

Read full pinned spec ↗

specdev-buildactive15 requirements

Define the build pipeline for local development, install, and CI gating. The pipeline MUST keep cheap pre-build validation separate from the expensive post-build smoke so measurable debt is visible instead of folded into one blob.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (15)
  1. Transparent build-substrate re-exec
  2. Debug build by default
  3. Release build
  4. Test execution
  5. Clean build
  6. Build churn directories opt out of copy-on-write on btrfs
  7. Install to local path
  8. CI full runs in explicit phases
  9. Post-build status check smoke
  10. Remove installed binary
  11. Wipe caches and artifacts
  12. Installer triggers init
  13. Cross-platform build documentation
  14. Install exits with deterministic exit codes
  15. Transparent HTTPS caching via dev proxy

Read full pinned spec ↗

specdirect-podman-callsobsolete superseded_by: podman-container-spec + podman-container-handle + podman-orchestration0 requirements

Historical tombstone for the earlier direct-shell-launch runtime model. The current runtime shape is the typed Podman layer (`podman-container-spec`, `podman-container-handle`, and `podman-orchestration`). This tombstone is kept for traceability only and should not receive new behavior.

Document status: obsolete superseded_by: podman-container-spec + podman-container-handle + podman-orchestration. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specdisk-usage-detectionactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Cache tooling reports disk pressure before eviction

Read full pinned spec ↗

specdistributed-tracingactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Runtime events can share trace context

Read full pinned spec ↗

specdownload-telemetrydeprecated0 requirements

No purpose recorded in this spec.

Document status: deprecated. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specembedded-scriptsobsolete superseded_by: podman-container-spec + podman-container-handle + podman-orchestration0 requirements

Historical tombstone for the retired script-embedding model. The shipped Tillandsias binary no longer extracts or executes repository shell scripts at runtime. Static assets such as icons, version metadata, and Containerfile-adjacent build inputs may still be embedded where needed, but executable runtime behavior is owned by compiled Rust and direct CLI call

Document status: obsolete superseded_by: podman-container-spec + podman-container-handle + podman-orchestration. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specenclave-networkactive3 requirements

Internal podman network that isolates every Tillandsias-managed container. Only the proxy is dual-homed for external access; all other members communicate exclusively through the enclave.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Internal podman network for container isolation
  2. Container attachment to enclave network
  3. Enclave lifecycle telemetry

Read full pinned spec ↗

specenclave-service-catalogactive3 requirements

The catalog of publishable enclave services: how a project's container (today the single fixed category `WEB`) is brought up on the enclave network and exposed through the reverse proxy under a friendly local URL. Orders 357/363/ 364 built the surface (`--publish-local`, the MCP `publish_local` tool, and `build_catalog_service_run_args`); this spec pins what

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. The catalog is an allowlist, not a namespace
  2. Published services join the enclave by fixed name
  3. Public routes drop auth deliberately, never by default

Read full pinned spec ↗

specenforce-trace-presenceobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specenvironment-runtimestatus: active7 requirements

No purpose recorded in this spec.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Global and per-project configuration
  2. User-facing files must be verbose and non-technical
  3. Accountable uninstall
  4. Desktop user-session runtime
  5. Dedicated service account runtime for headless installs
  6. TILLANDSIAS_AGENT accepts opencode-web
  7. Forge launchers export the exact active harness identity

Read full pinned spec ↗

specephemeral-guaranteeactive6 requirements

Prevent state pollution, cross-session leakage, and unverified persistence by formalizing the ephemerality invariants governing forge experts, tmpfs allocations, container lifecycles, and diagnostic log lifecycles.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Ephemeral Expert State and Binary Surfaces
  2. Persistent Volume Disjointness
  3. Containment of Build-Cache Carve-Out
  4. Read-Only Named Volume for Content-Addressed Spec Index
  5. Zero Artifact Persistence Post-Teardown
  6. Ephemeral Initialization and Diagnostic Log Lifecycle

Read full pinned spec ↗

specephemeral-secret-refreshunspecified2 requirements

Tillandsias tray ensures podman secrets (CA certificates, tokens) are refreshed on each startup, preventing "secret name in use" errors from unclean shutdowns.

Document status: unspecified. This describes the spec’s lifecycle, not verified implementation.

Requirements (2)
  1. Stale secrets are automatically refreshed on startup
  2. Secret removal failures are reported and propagate

Read full pinned spec ↗

specexpert-serve-grounded-pipelineactive6 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. R1 — one pipeline, two front-ends
  2. R2 — retrieval only from the published index
  3. R3 — answer or typed refusal, no third state
  4. R4 — citations survive only if used
  5. R5 — freshness from the entry's own frame
  6. R6 — OpenAI-compatible loopback endpoint

Read full pinned spec ↗

specexternal-logs-layeractive6 requirements

Define the host-side external log tier for producer/consumer container pairs, including the shared parent directory layout, producer manifests, and auditor invariants that keep cross-container log visibility explicit and bounded.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Two-tier observability model
  2. Producer mount + manifest contract
  3. Consumer mount
  4. Auditor invariants
  5. Reverse-breach refusal
  6. Migration of git-push.log

Read full pinned spec ↗

specfilesystem-scannerstatus: active2 requirements

No purpose recorded in this spec.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (2)
  1. OS-native event-driven watching
  2. Debounced event batching

Read full pinned spec ↗

specfix-podman-machine-host-aliasesobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specfix-windows-extended-pathobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specfix-windows-image-routingstatus: active promoted-from: openspec/changes/archive/2026-04-16-fix-windows-image-routing/ annotation-count: 1 implementation-complete: true5 requirements

Fix Windows container image building by routing each image type (forge, proxy, git, inference, web) to its own Containerfile and build context. Previously, Windows hardcoded all builds to `images/default/Containerfile` (the forge), resulting in four image tags pointing to the same image ID.

Document status: status: active promoted-from: openspec/changes/archive/2026-04-16-fix-windows-image-routing/ annotation-count: 1 implementation-complete: true. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Image Build Path Routing by Type
  2. Correct Image Entrypoints
  3. Image Build Centralized in Helper
  4. Defensive Integration Test or Self-Check
  5. Build Number Bump

Read full pinned spec ↗

specforge-as-only-runtimeactive7 requirements

Lock in a single, falsifiable runtime contract for Tillandsias' interactive surface so that:

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Forge Image Bakes the Full Agent Set
  2. All Podman Run Goes Through the Idiomatic Layer
  3. No Host-Side Agent Binaries
  4. Mount Categories Are Exhaustive
  5. Attached forge exit cleans idle stacks
  6. Delegated agent results come from the current scoped run
  7. Host Terminal Is the Only Host-Side Process

Read full pinned spec ↗

specforge-cache-architectureobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specforge-cache-dualstatus: active5 requirements

Define the live cache discipline for forge containers: shared Nix store populated by build-time Nix-managed processes, per-project cache, and per-language env var routing.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Forge containers see exactly four path categories
  2. Per-language env vars resolve into the per-project cache
  3. Shared cache uses nix as the single entry point
  4. Project workspace is the user's git repo, not a cache
  5. cache_version file lifecycle

Read full pinned spec ↗

specforge-environment-discoverabilityactive15 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (15)
  1. Inventory CLI lists installed toolchains
  2. Services CLI lists running containers and services
  3. Models CLI queries available LLM models
  4. Welcome banner on terminal entry
  5. Discovery commands are discoverable via `--help`
  6. MCP host-services tool surface is organically discoverable
  7. Plan expert preserves release constraints and dependency direction
  8. Plan expert ranks claimable next work deterministically
  9. Generic project expert bootstrap is zero-intervention
  10. Project index is ephemeral and rebuilt from the active checkout
  11. Host-session experts refresh on commit, env-gated
  12. Generic expert readiness is machine-readable and never blocks launch
  13. project_answer is one uniform cited surface with a deterministic fallback
  14. Every citation names the frame it was read in, and the reader's position is derived rather than assumed
  15. local-experts agent is grounded-endpoint only

Read full pinned spec ↗

specforge-forward-compatdeprecated0 requirements

No purpose recorded in this spec.

Document status: deprecated. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specforge-git-identity-anonymizationactive5 requirements

A forge guest commits as a project-scoped identity, never as the operator, and every agentic commit carries machine-readable attribution. Seven guest entrypoints (`images/default/entrypoint-forge-*.sh`, `entrypoint-terminal.sh`) and `lib-common.sh` traced this contract for months while the spec file did not exist — order 877 closed that ghost. The 2026-08-24

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. The guest never inherits the host's git identity
  2. Guest identity is config, not exported environment
  3. Committer host stays derivable
  4. Agentic commits carry attribution trailers
  5. Hook installation cannot break a hostile environment

Read full pinned spec ↗

specforge-hot-cold-splitstatus: active7 requirements

Define the forge storage split between HOT tmpfs mounts, COLD disk-backed state, and the runtime banner that makes those boundaries visible to agents and humans.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. HOT tier — RAM-backed tmpfs for finely curated paths
  2. Per-mount size caps
  3. --memory ceiling pairs with tmpfs caps
  4. Pre-flight RAM check refuses launch on insufficient host RAM
  5. Per-launch project source budget
  6. Agent transparency
  7. Tmpfs-overlay lane for per-project ephemeral cache

Read full pinned spec ↗

specforge-offlinestatus: active3 requirements

Forge containers operate offline -- no credentials, no project mounts, no direct internet. Code comes from git clone, packages come through the proxy, secrets live exclusively in the git service.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Forge containers have zero credentials
  2. Forge containers have no direct project mount
  3. Forge containers are enclave-only

Read full pinned spec ↗

specforge-opencode-onboardingstatus: active3 requirements

The OpenCode onboarding bundle SHALL define the live launch-time contract for newly attached forge sessions: discovery, modular instructions, OpenSpec bootstrap, and the synthetic `/startup` prompt.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. First-turn environment discovery
  2. Modularized instruction files
  3. OpenCode bootstrap seeds /startup and OpenSpec init

Read full pinned spec ↗

specforge-shell-toolsstatus: active3 requirements

Define the live shell-tooling surface in the forge image: alternative shells, modern terminal utilities, and shell startup config that make interactive sessions useful by default.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Alternative shells available
  2. Terminal tools installed
  3. Shell configurations

Read full pinned spec ↗

specforge-stalenessstatus: active promoted-from: openspec/changes/archive/2026-04-08-fix-forge-staleness-and-pruning/ annotation-count: 136 requirements

Ensure forge image staleness detection is driven by the source hash, always invokes the build script for freshness checks, refreshes human aliases on rebuild, auto-prunes old images to save disk space, and forward-compatible detects newer forge images from a different binary version.

Document status: status: active promoted-from: openspec/changes/archive/2026-04-08-fix-forge-staleness-and-pruning/ annotation-count: 13. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Source-hash staleness file
  2. Tray always invokes build script for staleness check
  3. Prune old forge images after successful build
  4. Forward-compatible newer image detection
  5. Staleness detection in init command
  6. Absent cache version file means fresh start, not a staleness signal

Read full pinned spec ↗

specforge-standaloneactive5 requirements

Define the single-container forge troubleshooting path. This artifact lets an operator launch only the forge image, mount one host project directory, and enter an interactive bash session for local diagnosis or in-container development. This is the canonical name for the standalone forge launcher and supersedes the older `forge-standalone-runner` artifact se

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Standalone forge runner uses the forge image only
  2. Source mount is project-scoped
  3. Interactive bash session
  4. Full network access without enclave wiring
  5. Runner is fail-fast and explicit

Read full pinned spec ↗

specforge-standalone-runnerobsolete0 requirements

This document is retained for history only. The canonical standalone forge launcher contract now lives in `openspec/specs/forge-standalone/spec.md`.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specforge-welcomestatus: active2 requirements

Define the interactive terminal welcome banner and its once-per-session startup gating so new forge sessions expose project context, OS context, and discovery tips without repeating themselves.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (2)
  1. Welcome message on terminal launch
  2. Fish as default interactive shell

Read full pinned spec ↗

specgh-auth-scriptstatus: active7 requirements

The interactive GitHub Login user experience. Both the CLI entry point (`tillandsias --github-login`) and the tray menu item ("GitHub Login") drive the same single Rust implementation: spin up an ephemeral container from the git service image, run and verify `gh auth login` interactively, write the resulting OAuth token to Vault from inside the container, an

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Single implementation behind tray and CLI entry points
  2. Non-interactive login is explicit and cannot hang on `/dev/tty`
  3. Interactive login uses an ephemeral git-service-image container
  4. Container verifies the session, writes the token to Vault, never reaches the host
  5. Drop guard tears down the login container on every exit path
  6. Mobile QR Code Device Flow
  7. Token Rotation and Expiration Management

Read full pinned spec ↗

specgit-mirror-servicestatus: active14 requirements

Per-project bare mirror repositories live inside a named Podman volume mounted at `/srv/git` in the git service container. Git daemon serves clones and pushes over the enclave network. A pre-receive relay forwards exactly the proposed ref transaction to the configured upstream with `git push --atomic` before the mirror acknowledges it locally. GitHub credent

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (14)
  1. Bare mirror repository management
  2. Git daemon serves mirrors on enclave network
  3. Existing-volume receive hardening
  4. Pre-receive relay verifies acknowledgement durability
  5. Reconciliation fetch never clobbers exported refs
  6. GitHub token delivery uses Vault AppRole
  7. Git service container lifecycle
  8. Git accountability window
  9. Mirror → host working-copy auto-sync on push
  10. Mirror sync never clobbers user work
  11. Tray startup sweeps all mirrors
  12. Tray Quit triggers a final sync
  13. Per-project transparency — no hardcoded project names
  14. Repository-local Git metadata is bidirectionally quarantined

Read full pinned spec ↗

specgithub-credential-healthstatus: active5 requirements

TBD - created by archiving change tray-responsiveness-and-startup-gating. Update Purpose after archive.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Credential health probe distinguishes "down" from "unauthenticated"
  2. Probe runs off the event loop with a bounded timeout
  3. Result is cached per process lifetime but invalidated on explicit sign-in / sign-out
  4. A credential that goes bad IN PLACE is observed without a presence transition
  5. The implemented vocabulary is named, and the tray-loop polling ban is scoped

Read full pinned spec ↗

specgraceful-shutdownstatus: active promoted-from: direct annotation-count: 87 requirements

Define the graceful shutdown contract for Tillandsias headless binary: signal handling (SIGTERM/SIGINT), container lifecycle management with timeout, resource cleanup, exit codes, and verification that no ephemeral artifacts remain after shutdown.

Document status: status: active promoted-from: direct annotation-count: 8. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. SIGTERM and SIGINT trigger graceful shutdown immediately
  2. All managed containers receive stop signals before force-kill
  3. Foreground forge exits clean idle stacks
  4. Graceful shutdown has a configurable timeout with force-kill fallback
  5. Exit code reflects shutdown status
  6. No stale sockets remain after shutdown
  7. No stale mounts remain after shutdown

Read full pinned spec ↗

specguest-crashloop-detectionactive3 requirements

The host tier must tell a guest that is LOOPING — restarting over and over, never converging — apart from one that is merely progressing slowly, and say so in a pinned, regex-testable grammar. One pure-Rust state machine (`tillandsias-control-wire::crashloop`) is consumed by every tray (Windows NotifyIcon, macOS AppKit, Linux), so the platforms cannot drift

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. A loop is detected within the window
  2. Slow progress is never a crash loop
  3. The detector is proven where it ships

Read full pinned spec ↗

specheadless-modestatus: active promoted_from: direct annotation_count: 45 requirements

Define the contract for running Tillandsias without a graphical user interface. Headless mode emits structured JSON events to stdout and is suitable for CI/CD automation, server deployments, and containerized environments. Headless mode must not depend on GTK or display subsystems, ensuring portability and composability.

Document status: status: active promoted_from: direct annotation_count: 4. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Headless binary invocation with --headless flag
  2. Container status events and metrics reporting
  3. No GTK dependency in headless path
  4. Status-check mode for initialization verification
  5. Per-operation image ensure lists, and the Build-context vault

Read full pinned spec ↗

spechost-browser-mcpstatus: active14 requirements

Define the host-resident MCP browser bridge that lets forge agents request allowlisted project-local browser windows over the tray control socket. The live implementation is the Rust `tillandsias-browser-mcp` crate plus the forge-side `host-browser.sh` stdio bridge, and it exists to launch isolated Chromium windows for mapped `<service>.<project>.localhost`

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (14)
  1. Host MCP server exposes browser-control tools to forge agents
  2. MCP reaches this server over the per-lane tool socket
  3. Browser provider is bundled Chromium only
  4. Process-per-window with ephemeral profile
  5. Window survives MCP connection drop
  6. URL allowlist denies opencode-self and non-project hosts
  7. Authorisation by Unix-socket peer credential
  8. Per-(project,host) debounce on browser.open
  9. browser.eval is disabled by default in v1
  10. CDP method usage is pinned to stable 1.3 and the bundled-Chromium version
  11. Per-call accountability log with redacted payloads
  12. Per-message size cap and DoS protection
  13. A successful tool call returns its payload as MCP content
  14. Development windows bypass service workers unless the lane keeps them

Read full pinned spec ↗

spechost-chromiumobsolete0 requirements

Historical wrapper retained for traceability only. The live Chromium installation and detection contract now lives in `openspec/specs/host-chromium-on-demand/spec.md`.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

spechost-chromium-on-demandstatus: active promoted-from: openspec/changes/archive/2026-04-26-host-chromium-on-demand/ annotation-count: 254 requirements

Provide an isolated, on-demand Chromium runtime for tray-launched browser windows and the secure OpenCode Web attach flow. Downloads Chrome for Testing into a shared host cache on first launch, verifies integrity via SHA-256, and launches windows with per-session incognito profiles isolated from the user's daily browser. Enables CDP (remote debugging protoco

Document status: status: active promoted-from: openspec/changes/archive/2026-04-26-host-chromium-on-demand/ annotation-count: 25. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Download and Verify Bundled Chromium
  2. Launch Windows with Ephemeral Profiles
  3. Browser Detection
  4. Download Telemetry

Read full pinned spec ↗

spechost-guest-transportactive phase: 1 (facade contract landed; per-platform backends in progress)4 requirements

Normalize every host→guest interaction onto a single platform-agnostic facade so the Linux, macOS, and Windows trays stop drifting into separate "connect from host to guest" implementations. Two primitives cover every need the operator named:

Document status: active phase: 1 (facade contract landed; per-platform backends in progress). This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Two canonical primitives
  2. Callers are backend-agnostic
  3. One wire protocol for both primitives on every backend
  4. Cross-platform conformance

Read full pinned spec ↗

spechost-shell-architectureproposed phase: 15 requirements

Define the contract for the shared host-side library `tillandsias-host-shell` that backs both the Windows (Win32 NotifyIcon) and macOS (AppKit NSStatusItem) native tray binaries. The host shell owns the portable, OS-agnostic responsibilities that previously lived inside `tillandsias-headless`'s Linux tray module: project discovery, menu state modelling, cont

Document status: proposed phase: 1. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Host filesystem scanner enumerates `~/src/` projects
  2. Portable `MenuStructure` is the single source of truth for menu paint
  3. vsock client lifecycle is owned by the host shell
  4. VM orchestration is delegated to `tillandsias-vm-layer`
  5. Host shell never holds long-lived credentials

Read full pinned spec ↗

spechost-state-lifecycleactive4 requirements

State what a destructive reset destroys, what it preserves, and what only a manual uninstall removes — on every platform, by named path — so that "reset is the baseline" and "credentials survive wipes" hold at the same time.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Everything Tillandsias owns on a host lives under `~/.tillandsias/`, and a first launch migrates the legacy roots there
  2. Destructive reset destroys derived state and preserves operator data; guest regimes have a SOFT and a HARD reset
  3. Every download is recorded in one manifest under `~/.tillandsias/downloads/`
  4. `--uninstall` is the preferred removal, leaves ZERO traces, and asks before removing `~/.tillandsias/`

Read full pinned spec ↗

specinference-containerstatus: active10 requirements

Shared ollama inference container on the enclave network. Forge containers query it via OLLAMA_HOST. Models persist in a host-mounted cache volume. Downloads route through the proxy.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (10)
  1. Local LLM inference via ollama
  2. Shared model cache
  3. Inference container lifecycle
  4. Inference NO_PROXY covers loopback + enclave peers
  5. Tier-tagged tool-capable model pre-pulls
  6. CPU tier-S is the always-available terminal
  7. NPU tier rows are additive and engine-gated
  8. Tier classification logged once at boot
  9. The engine behind the endpoint is a slot
  10. The WSL2 Vulkan opt-in keeps small models' decode on the CPU

Read full pinned spec ↗

specinference-engine-slotsstatus: active authored-by: order 479 (`heterogeneous-inference-specs`), spec-before-implementation derived-from: `plan/issues/heterogeneous-inference-cpu-gpu-npu-research-2026-07-24.md` gates: order 481 (`npu-device-passthrough`), order 482 (`llama-server-engine-slot`), order 483 (`host-native-sidecar-endpoints`) answers: the order-478 enclave-boundary ques8 requirements

Make the inference *engine* pluggable while the *endpoint* stays frozen.

Document status: status: active authored-by: order 479 (`heterogeneous-inference-specs`), spec-before-implementation derived-from: `plan/issues/heterogeneous-inference-cpu-gpu-npu-research-2026-07-24.md` gates: order 481 (`npu-device-passthrough`), order 482 (`llama-server-engine-slot`), order 483 (`host-native-sidecar-endpoints`) answers: the order-478 enclave-boundary ques. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. SLOT-1 — One stable enclave endpoint, selectable engine behind it
  2. SLOT-2 — Slot contract: OpenAI-compatible surface plus the existing readiness probe
  3. SLOT-3 — Engine registry, default engine, and the NPU integration point
  4. SLOT-4 — Vulkan via /dev/dri is the default Linux GPU lane
  5. SLOT-5 — Device passthrough for container-resident slots is graceful and unprivileged
  6. SLOT-6 — Host-native sidecar slots are reachable only behind the enclave proxy
  7. SLOT-7 — The model tier table is data consumed by every engine
  8. SLOT-8 — Slot lifecycle is idempotent, ephemeral, and soft-failing

Read full pinned spec ↗

specinference-host-side-pullobsolete0 requirements

Historical wrapper retained for traceability only. The live inference startup and model-pull contract now lives in `inference-container` and `zen-default-with-ollama-analysis-pool`, with runtime behavior owned by the inference image entrypoint.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specinference-policy-routerstatus: active authored-by: order 479 (`heterogeneous-inference-specs`), spec-before-implementation derived-from: `plan/issues/heterogeneous-inference-cpu-gpu-npu-research-2026-07-24.md` gates: order 484 (`inference-policy-router`)12 requirements

Decide, transparently and without operator configuration, which device, which engine slot, and which model tier serve a given inference request — and revise that decision when the machine's power and thermal situation changes.

Document status: status: active authored-by: order 479 (`heterogeneous-inference-specs`), spec-before-implementation derived-from: `plan/issues/heterogeneous-inference-cpu-gpu-npu-research-2026-07-24.md` gates: order 484 (`inference-policy-router`). This describes the spec’s lifecycle, not verified implementation.

Requirements (12)
  1. CLASS-1 — Four workload classes with declared objectives
  2. ROUTE-1 — The routing table is data and route() is a pure function
  3. ROUTE-2 — Placements are ordered fallback chains terminating in CPU tier-S
  4. ROUTE-3 — Failures advance the chain; three strikes quarantine the device
  5. ROUTE-4 — A model must fit device-visible memory with at least 10% headroom
  6. ROUTE-5 — First request is served from a resident small model while the preferred model loads
  7. ADAPT-1 — Power and thermal signals are subscribed, not polled-only
  8. ADAPT-2 — On battery or power-saver, background work suspends within 5 seconds
  9. ADAPT-3 — Background work resumes only on AC and thermal-nominal and idle
  10. ADAPT-4 — Status surface and pin override with auto-release
  11. NEG-1 — Non-goals are binding prohibitions
  12. NEG-2 — Degraded lanes never outrank healthy ones

Read full pinned spec ↗

specinit-commandstatus: active8 requirements

Define behavior of `tillandsias --init` command including incremental builds, debug mode, and the runtime contract that the shipped binary performs image orchestration directly from Rust without shell-script wrappers or a source checkout.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. Init CLI command
  2. Init path does not invoke shell wrappers
  3. Init does not require a checkout
  4. Init build uses host user namespace
  5. Init failure diagnostics are host-specific
  6. Exit code contract for init command
  7. Debug mode log capture
  8. All images built

Read full pinned spec ↗

specinit-incremental-buildsstatus: active4 requirements

Track and resume partial `tillandsias --init` builds, with debug logging for failed images and content-based runtime asset staleness.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Incremental build state tracking
  2. Final Containerfile instruction layers are squashed
  3. Debug flag for init command
  4. Failed build log display

Read full pinned spec ↗

specinstall-progressactive0 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specknowledge-source-of-truthstatus: active13 requirements

This spec is the **epistemology** of Tillandsias: it defines what counts as authoritative knowledge, how artefacts converge over time, and how spec-vs-code divergence is resolved. Every other spec is interpreted *through* this spec. Observability surfaces (dashboards, signatures, evidence bundles) are downstream projections of the truth contract defined here

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (13)
  1. Knowledge directory structure
  2. Cheatsheet format
  3. XML collection index
  4. Version tracking and freshness
  5. Project agnosticism
  6. External debug source fetching
  7. Authority hierarchy across artefact classes
  8. CRDT-inspired monotonic convergence
  9. Spec-vs-code divergence resolution
  10. Staleness detection via timestamp and content hash
  11. Convergence evidence bundles
  12. Provenance is mandatory for every authoritative artefact
  13. Integration with downstream observability surfaces

Read full pinned spec ↗

speclayered-tools-overlayobsolete0 requirements

Historical overlay capability retained for traceability only. The live tool lifecycle is now owned by `default-image`, `forge-hot-cold-split`, and `tools-overlay-fast-reuse`.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speclinux-native-portable-executableactive4 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Headless Linux launcher is portable across common distros
  2. Linux binary carries runtime image contexts
  3. Linux release artifact is the musl binary
  4. Native tray builds may use host UI libraries

Read full pinned spec ↗

speclitmus-frameworkactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Litmus bindings remain falsifiable

Read full pinned spec ↗

speclocal-web-previewstatus: draft version: v1 id: enclave.web.local-preview@v1 verification: S0 (design; executable coverage and live evidence pending) previous: none; additive runtime profile over enclave-service-catalog6 requirements

No purpose recorded in this spec.

Document status: status: draft version: v1 id: enclave.web.local-preview@v1 verification: S0 (design; executable coverage and live evidence pending) previous: none; additive runtime profile over enclave-service-catalog. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Session-bound live source
  2. Shared source preserves the bounded HOT storage contract
  3. Allowlisted local runtime profiles
  4. Existing MCP lifecycle contract
  5. HTTPS routing and honest trust
  6. Opt-in toolchain sessions

Read full pinned spec ↗

speclog-aggregationactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Runtime logs aggregate without dropping source metadata

Read full pinned spec ↗

speclog-schema-versioningactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Log entries carry schema version

Read full pinned spec ↗

speclogging-accountabilityobsolete0 requirements

Historical logging-accountability umbrella retained for traceability only. The live logging contracts now live in `runtime-logging`, `logging-levels`, and the diagnostics/runtime specs.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speclogging-levelsobsolete0 requirements

This specification is a historical tombstone for the retired embedded `external-logs.yaml` configuration model. The live behavior is now owned by:

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specmacos-native-trayactive phase: 56 requirements

Define the contract for the macOS-native tray binary `tillandsias-macos-tray` (produces `tillandsias-tray.app`). The binary runs on the host as an OS-native AppKit application, owns an `NSStatusItem` (system menu bar icon) constructed via `objc2-app-kit`, and delegates portable logic to `tillandsias-host-shell`. The binary is responsible for driving the macO

Document status: active phase: 5. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. AppKit `NSStatusItem` is the only tray surface
  2. Menu items match the host-shell parity contract
  3. Virtualization.framework guest lifecycle is owned by this binary
  4. Terminal attach runs the tray's own attach client in the window
  5. GUI passthrough is explicitly deferred to v2
  6. `--diagnose` CLI mode emits a stable bundled health report

Read full pinned spec ↗

specmcp-on-demandactive7 requirements

MCP servers (filesystem, resource tools, inference) are started on-demand when first invoked by an agent, not eagerly at container startup. Reduces boot time, memory footprint, and startup complexity. Servers are destroyed on container shutdown (ephemeral lifecycle).

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. On-demand MCP server startup
  2. Multiple MCP servers coexist
  3. Health check and readiness
  4. Ephemeral lifecycle — servers destroyed on shutdown
  5. Browser tool variant — on-demand CLI instead of daemon
  6. MCP server communication channel
  7. Litmus test — on-demand MCP lifecycle

Read full pinned spec ↗

specmcp-tool-socketstatus: active6 requirements

Define the per-lane Unix-domain socket that carries MCP JSON-RPC between a forge lane and the host tray, and the attribution model that makes a request answerable *on behalf of a specific project*.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Each lane is served by its own socket
  2. Attribution is derived from the accepting listener
  3. NDJSON framing with a bounded per-line payload
  4. One request in flight per connection
  5. The socket carries both host-services and browser tools
  6. Read-only Cloudflare tools hold the sign-in on the host

Read full pinned spec ↗

specmenu-action-error-handlingactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Tray menu action failures are visible and bounded

Read full pinned spec ↗

specmeta-orchestrationactive5 requirements

The unattended top-level loop: how a host runs one work cycle — heartbeat, drain, verify, attest — without a human watching. The runbook lives in `skills/meta-orchestration/SKILL.md`; THIS document pins the invariants that runbook may never lose, each falsifiable by the litmus tests bound in `openspec/litmus-bindings.yaml` (spec_id `meta-orchestration`). Thi

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. A full cycle proves its own exit
  2. The startup boundary is inviolable
  3. One checkout, one cycle
  4. Guards run before work, and fail loud
  5. Every mode names its verdict

Read full pinned spec ↗

specmethodology-accountabilityactive5 requirements

Make the methodology itself auditable. The project already requires specs, cheatsheets, traces, litmus tests, and evidence bundles for implementation work. This spec extends the same discipline to methodology claims, unknown-event intake, and correctness-proximity scoring.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Methodology claims cite provenance
  2. Unknown events are first-class artifacts
  3. Correctness proximity uses CentiColons
  4. Existing convergence score remains coarse
  5. Mathematical convergence claims are bounded

Read full pinned spec ↗

specnative-secrets-storeactive3 requirements

Store and retrieve the GitHub OAuth token in the host OS's platform-native secret service. The host Rust process is the sole consumer of the keyring; containers never call any keyring API. This is the source of truth for GitHub credentials — no plaintext credential files live on persistent disk.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Platform-native keyring backend
  2. Single keyring entry for the GitHub token
  3. Host-only keyring API surface

Read full pinned spec ↗

specnetwork-scenariosactive2 requirements

Name every network posture a Tillandsias-managed container can run in, so an operation declares which one it is using rather than assembling `--network` arguments ad hoc. Published from order 245 §5 P5; every posture below was verified against the tree on 2026-08-30 rather than carried over from the draft catalog.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (2)
  1. Every container declares a network posture
  2. S0 is not a scenario, and S4 is retired

Read full pinned spec ↗

specnix-builderactive3 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Build-time Nix only
  2. Git-tracked files for flake builds
  3. dockerTools builder/attribute pairing

Read full pinned spec ↗

specnix-cache-serviceactive5 requirements

Serve the persistent host nix store (order 795-h8er) to the enclave as a nix binary cache, so an ephemeral forge lands on a warm cache without any host path being mounted into it.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. The cache is a first-class enclave service
  2. The cache hostname bypasses the enclave proxy
  3. Transport trust and content trust are BOTH enforced
  4. An unreachable cache degrades to silence, not to failure
  5. A running but untrusted cache is a violation, not a skip

Read full pinned spec ↗

specno-terminal-flickeractive0 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specobservability-convergenceactive11 requirements

Track alignment between OpenSpec specs and runtime implementation via instrumented metrics. Measure code coverage of spec requirements, spec→code→trace traceability, and convergence toward spec intent. Enable data-driven decisions about spec completeness and implementation gaps.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (11)
  1. Spec coverage metrics
  2. Trace cardinality and completeness
  3. Requirement↔Litmus Test binding
  4. Implementation latency — time from spec to code
  5. Spec debt and staleness
  6. Convergence scoring
  7. Dashboard is a read-only projection
  8. Dashboard alert thresholds
  9. Dashboard trend metrics
  10. Litmus test — observability instrumentation
  11. Test requirement

Read full pinned spec ↗

specobservability-metricsactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Runtime metrics expose current system health

Read full pinned spec ↗

specobservatorium-dashboardactive10 requirements

Provide the operator-requested public metrics dashboard at `observatorium.tillandsias.org`, implemented under `observatorium/`. CentiColon convergence describes named, verifiable implementation obligations. The legacy private trace viewer remains governed by `clickable-trace-index`.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (10)
  1. Flutter and Flame presentation
  2. Cheap collection and precomputed publication
  3. Honest measurement history
  4. Inspectable implementation obligations
  5. Validated grade provenance
  6. Scope and simplification context
  7. Cloudflare deployment and local artifact
  8. Durable release observations
  9. Release and current evidence remain distinct
  10. Managed forge build and browser loading

Read full pinned spec ↗

specopencode-web-sessionobsolete9 requirements

How Tillandsias runs a persistent OpenCode Web server per project, maps it to a local-only host port, and renders it in an embedded Tauri webview — including multi-session reattach semantics and shutdown guarantees.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Requirements (9)
  1. OpenCode Web is the default session agent
  2. Per-project persistent web container
  3. Host port bound to 127.0.0.1 only
  4. Unique host port per concurrent web container
  5. WebviewWindow launch contract
  6. Stop tears down the web container
  7. Shutdown stops all web containers and closes all webviews
  8. OpenCode Web defaults to dark theme
  9. Webview close does not terminate the tray

Read full pinned spec ↗

specopencode-web-session-otpactive promoted-from: openspec/changes/archive/2026-04-27-opencode-web-session-otp/ annotation-count: 296 requirements

Provide application-layer authentication for OpenCode Web sessions launched from the tray. Generates a one-time-password (OTP) per browser window, communicates it to the router via a control channel, and enforces session-cookie validation on all requests. This defence-in-depth mechanism complements the loopback-only network bind to close per-host-user attack

Document status: active promoted-from: openspec/changes/archive/2026-04-27-opencode-web-session-otp/ annotation-count: 29. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Per-Attach OTP Generation and Delivery
  2. Router-Side OTP Validation and Cookie Issuance
  3. Cookie Shape and Lifetime
  4. Multi-Window Reattachment
  5. Secrets Management Integration
  6. Authenticated Readiness Probe

Read full pinned spec ↗

specoverlay-mount-cacheobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specpersistent-git-serviceobsolete0 requirements

This per-project git-service lifetime contract has been retired. The live lifecycle is now described by `git-mirror-service`, which starts the git service on first attach for a project and stops it when the project's last forge exits.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specpodman-container-handleactive3 requirements

Define the lightweight handle returned by the Podman wrapper after a launch request succeeds. The handle SHALL carry the launch spec snapshot and the runtime identity needed for stop, inspect, and event correlation.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Handle retains launch identity
  2. Handle creation remains unit-testable
  3. Event stream maps back to handle identity

Read full pinned spec ↗

specpodman-container-specactive3 requirements

Define the typed Rust container-spec layer used by Tillandsias to build Podman launch arguments without shell strings. This layer is the isolated, unit-testable boundary between launch intent and runtime execution.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Launch specs are typed and directly serialized
  2. Security defaults are immutable
  3. Spec layer supports launch-shape composition

Read full pinned spec ↗

specpodman-idiomatic-patternsactive9 requirements

Canonical idiomatic patterns for Podman usage in Tillandsias: event-driven container observation, security-mandatory flags, per-project storage isolation, ephemeral secret mounting, categorized error handling, and rootless-first execution. These patterns govern every layer that touches Podman — Rust crates, shell scripts, and Containerfiles.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (9)
  1. Event-driven container observation, never polling
  2. Non-negotiable security flags on every container
  3. Per-project storage isolation (enclave model)
  4. Ephemeral secret mounting, never env vars or image layers
  5. Categorized error handling with retry discrimination
  6. Rootless-first execution with keep-id mapping
  7. Enclave network with internal DNS and dynamic IPAM
  8. Observed launch helpers produce actionable failures
  9. Tillandsias-owned podman configuration is explicit on every invocation and wins over system defaults

Read full pinned spec ↗

specpodman-image-evictionactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Image eviction is targeted and explainable

Read full pinned spec ↗

specpodman-orchestrationactive8 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. Rootless user-session ownership
  2. Security-hardened container defaults
  3. FUSE file descriptor sanitization before container launch
  4. Rootless networking backend
  5. Volume mount strategy
  6. Detached web-mode launch profile
  7. Deterministic forge-container name
  8. Launchers build argv directly

Read full pinned spec ↗

specpodman-registries-configunspecified1 requirements

No purpose recorded in this spec.

Document status: unspecified. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. The user runtime reads a Tillandsias-owned registries.conf

Read full pinned spec ↗

specpodman-secrets-integrationactive8 requirements

Tillandsias SHALL use podman's native secret mechanism (`podman secret`) as the transport for credential material that must cross the host-container boundary (CA certificates, Vault unseal key, per-container Vault tokens, and the git mirror's launch-scoped AppRole material). Secrets are created at tray startup as ephemeral tmpfs-backed artifacts, mounted int

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. Ephemeral secrets created at tray startup
  2. Secrets mounted via `--secret` flag, never bind mounts
  3. Secrets readable at `/run/secrets/<name>` inside containers
  4. Secrets hidden from `podman inspect` and process environment
  5. All secrets cleaned up on tray shutdown
  6. Secrets creation uses podman filedriver (default)
  7. Accountability logging for all secret operations
  8. CA certificate generation is ephemeral and per-session

Read full pinned spec ↗

specproject-bootstrap-readmeactive7 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. README MUST have two sections: FOR HUMANS and FOR ROBOTS
  2. README MUST be auto-generated from source manifests
  3. README validation ensures structure and freshness
  4. Pre-push hook auto-regenerates README
  5. Traces ledger records agent observations
  6. Bootstrap skills guide workflow
  7. A new project is onboarded with specs, a plan folder and a recommended stack

Read full pinned spec ↗

specproject-managementactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Project selection is stable across tray operations

Read full pinned spec ↗

specproject-summarizersactive4 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Summarizers MUST parse manifest files deterministically
  2. Summarizers MUST output structured, consistent data
  3. Summarizers MUST be embeddable as shell scripts
  4. README dispatcher orchestrates all summarizers

Read full pinned spec ↗

specproxy-containeractive17 requirements

Caching HTTP/HTTPS forward proxy with a domain allowlist that mediates external traffic from forge containers. It is Alpine-based with Squid and a bounded 4GiB disk cache. HTTPS interception is deliberately exceptional: the proxy bumps only the exact GitHub release-asset CDN hostname and splices all other TLS traffic end to end.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (17)
  1. Caching HTTP/HTTPS proxy with ssl-bump MITM architecture
  2. Ephemeral CA chain (per-launch generation)
  3. Dual-port architecture
  4. Image builds bypass the proxy
  5. CA chain injection into forge containers
  6. Generous developer-focused domain allowlist
  7. SSL bump policy is step-aware and narrowly selective
  8. Release-asset caching preserves origin semantics
  9. Upstream TLS verification
  10. Proxy container image versioned and built via pipeline
  11. Proxy container lifecycle management
  12. Proxy request telemetry
  13. Privacy and identity
  14. Allowlist covers OpenCode's default egress footprint
  15. Allowlist entries follow Squid 6.x single-entry rule
  16. Forward proxy recognises `*.localhost` as enclave-internal
  17. NO_PROXY exemptions and the Node egress contract

Read full pinned spec ↗

specremote-projectsactive5 requirements

TBD - created by archiving change remote-project-clone. Update Purpose after archive.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Fetch remote repository list
  2. Filter against local projects
  3. Cache remote repository list
  4. Clone remote project
  5. Loading state

Read full pinned spec ↗

specresource-metric-collectionactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Resource samplers collect bounded measurements

Read full pinned spec ↗

specreverse-proxy-internalactive7 requirements

Internal reverse proxy for routing enclave traffic with SSL termination, request/response logging, and caching. Sits between agents/web clients and enclave-internal services (forge containers, inference, git mirror). Certificates are ephemeral (tmpfs-based CA). Zero persistence, all state destroyed on shutdown.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Enclave-internal routing
  2. SSL termination with ephemeral CA
  3. Request/response logging
  4. Caching for performance
  5. Ephemeral cache and state
  6. Access control and authentication
  7. Litmus test — reverse-proxy-internal lifecycle

Read full pinned spec ↗

specruntime-diagnosticsactive6 requirements

Capture and surface container failure diagnostics (exit code, stderr, resource exhaustion, timeout) in a structured format that enables root-cause analysis without exposing raw container internals to the user. Emphasize ephemeral failure data: diagnostics are collected only for the container's lifetime, then discarded.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Capture exit code and termination signal
  2. Capture container stderr during execution
  3. Detect resource exhaustion conditions
  4. Analyze stderr for common failure patterns
  5. Ephemeral diagnostics lifecycle
  6. Litmus test — runtime diagnostics capture and lifecycle

Read full pinned spec ↗

specruntime-diagnostics-streamactive6 requirements

Live streaming of container events and diagnostics to the user's terminal when running with `--debug` flag. Events flow from container (exit, signal, resource events) → podman → tray → terminal in near-real-time. Stream is ephemeral and scoped to the current session; no persistence across runs.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Stream activation on --debug flag
  2. Event structure and formatting
  3. Event filtering and control
  4. Ephemeral stream lifecycle
  5. Backpressure and flow control
  6. Litmus test — diagnostic streaming activation and lifecycle

Read full pinned spec ↗

specruntime-loggingactive17 requirements

Structured logging system with compact formatting, accountability windows for sensitive operations, and spec traceability via `@trace` links.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (17)
  1. Logging is observational only
  2. Logging transport is non-blocking and failure-tolerant
  3. Terminal log output when launched from CLI
  4. File log output always
  5. Modular log filtering via environment variable
  6. Container lifecycle logging
  7. Spec trace links in all accountability events
  8. Accountability metadata excluded from inline fields
  9. No PII in logs
  10. Proxy accountability window
  11. Enclave accountability window
  12. Git accountability window
  13. All enclave accountability windows emit real events
  14. External-tier logging
  15. Schema versioning for log entry evolution
  16. Secret rotation event logging (OBS-021)
  17. Image build event logging (OBS-022)

Read full pinned spec ↗

specsecret-rotationobsolete0 requirements

This spec used to describe tmpfs token files and `GIT_ASKPASS` delivery. That model has been superseded by `podman-secrets-integration`, which delivers the GitHub token as a podman secret mounted at `/run/secrets/tillandsias-github-token` inside the git service container.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specsecrets-managementobsolete (removed in v0.3 — see `tillandsias-vault` spec)0 requirements

(This spec is obsolete. See `tillandsias-vault` for the current credential architecture.)

Document status: obsolete (removed in v0.3 — see `tillandsias-vault` spec). This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specsecurity-privacy-isolationactive5 requirements

Define the zero-tolerance security, privacy, and isolation boundaries that govern all current Tillandsias runtime paths. This spec is the top-level entrypoint for agent work that needs a current answer to: "what must never leak, escape, or silently weaken?"

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Zero-tolerance credential boundary
  2. Zero-tolerance runtime leakage boundary
  3. Zero-tolerance network boundary
  4. Zero-tolerance shell-wrapper boundary
  5. Security hardening defaults are immutable

Read full pinned spec ↗

specsignal-handlingactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Headless runtime shuts down on process signals

Read full pinned spec ↗

specsimplified-tray-uxactive promoted-from: openspec/changes/archive/2026-04-25-simplified-tray-ux/ annotation-count: 178 requirements

Replace the accreted, multi-submenu tray design with a minimal, static menu structure that mirrors the user's mental model: launch a project, optionally drop into a terminal, quit. Eliminates per-project action duplication, visible menu flicker, and the "Seedlings" agent-picker submenu (converged on OpenCode Web as the sole runtime). Pre-builds all menu item

Document status: active promoted-from: openspec/changes/archive/2026-04-25-simplified-tray-ux/ annotation-count: 17. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. Startup Menu Structure
  2. Five-Stage Static Menu Structure
  3. Home Menu ([<home> ~/src/ >])
  4. Cloud Menu ([<cloud> Cloud >])
  5. GitHub Login Menu ([<key> GitHub Login])
  6. Cloudflare Login Menu ([Cloudflare Login])
  7. Single Container Per Project
  8. CLI Behavior Unchanged

Read full pinned spec ↗

specsingleton-guardactive4 requirements

TBD - created by archiving change singleton-instance. Update Purpose after archive.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Single instance enforcement
  2. Lock file location
  3. Lock file cleanup on exit
  4. Stale lock detection

Read full pinned spec ↗

specsocket-container-orchestrationactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Socket-based orchestration uses explicit readiness

Read full pinned spec ↗

specspec-trace-coverage-thresholdactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Trace coverage checks report threshold status

Read full pinned spec ↗

specspec-traceabilityactive7 requirements

Enable bidirectional navigation between code and specs through `@trace` annotations and structured logging fields. When a spec is applied, the implementation SHALL reference the spec with `// @trace spec:<name>` comments in code and `spec = "<name>"` fields in log events. This creates a queryable graph: code → spec, spec → code, log → code → spec. Developers

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Code annotations link to specs and knowledge
  2. Structured spec field in tracing logs
  3. CRDT-like reference semantics
  4. Specs SHOULD prefer non-DRAFT cheatsheets in Sources of Truth
  5. @cheatsheet path annotation in code is a peer of @trace spec:
  6. Litmus chain references are explicit in updated specs
  7. Rust source relevance queries gate opt-in litmus tests

Read full pinned spec ↗

specsquid-proxy-integrationactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Runtime traffic uses the Squid proxy policy

Read full pinned spec ↗

specstructured-query-languageobsolete0 requirements

This spec and its only implementation, the Loki-style log query language in `crates/tillandsias-logging/src/query.rs` (parser, executor, 16 unit tests) and the `scripts/query-traces.sh` CLI that re-implemented the same grammar in jq, were retired together under packet 781-hseq (`log-query-language-has-zero-consumers`) on 2026-10-12.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specsubdomain-naming-flipactive promoted-from: openspec/changes/archive/2026-04-26-subdomain-naming-flip/ annotation-count: 53 requirements

Reorganize subdomain URL structure from `<project>.opencode.localhost` to `opencode.<project>.localhost`, enabling hierarchical service discovery (`*.opencode.localhost`, `*.flutter.localhost`, etc.) and simplifying browser-MCP allowlists.

Document status: active promoted-from: openspec/changes/archive/2026-04-26-subdomain-naming-flip/ annotation-count: 5. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Subdomain ordering inversion
  2. Router configuration update
  3. Loopback resolution unchanged

Read full pinned spec ↗

specsubdomain-routing-via-reverse-proxyactive promoted-from: openspec/changes/archive/2026-04-25-subdomain-routing-via-reverse-proxy/ annotation-count: 116 requirements

Enable stable, port-agnostic URLs for all web services spawned in the enclave via a reverse-proxy listener that maps `<service>.<project>.localhost` hostnames to internal container ports, while maintaining RFC 6761 loopback-only binding.

Document status: active promoted-from: openspec/changes/archive/2026-04-25-subdomain-routing-via-reverse-proxy/ annotation-count: 11. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Reverse-proxy container and binding
  2. Dynamic routing table from Caddyfile
  3. Forward-proxy integration
  4. No container port publication
  5. Caddyfile reload via the in-container reload script
  6. Agent instructions for service binding

Read full pinned spec ↗

spectestingactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Local CI maps checks to traceable specs

Read full pinned spec ↗

spectillandsias-vaultactive phase: 6.610 requirements

Run a HashiCorp Vault container as the default and ONLY Linux secrets backend for Tillandsias. Vault stores long-lived external credentials, starting with the GitHub token at `secret/github/token`, and issues short-lived per-container tokens scoped by fine-grained ACL policies.

Document status: active phase: 6.6. This describes the spec’s lifecycle, not verified implementation.

Requirements (10)
  1. Vault container runs inside the secrets boundary with persistent storage
  2. Auto-unseal key securely stored in host native keychain with versioning
  3. Vault is the ONLY secret store (Legacy Fallback Removed)
  4. Policy taxonomy enforces least privilege per container kind
  5. Per-container Vault authentication is short-lived and revocable
  6. Forge Vault access is optional and provider-scoped
  7. Default-image Vault requests verify TLS and keep live tokens off argv
  8. The Vault store and its unseal material survive every destructive reset; only uninstall removes them
  9. Every harness credential document has a Vault home and is restored without a prompt
  10. Codex device sign-in offers a code-free terminal QR

Read full pinned spec ↗

spectools-overlay-fast-reuseobsolete0 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

spectransparent-https-cachingactive4 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. HTTPS cache interception uses generated CA material
  2. The intermediate CA private key is never world-readable
  3. Runtime containers trust the Tillandsias proxy CA
  4. HTTPS interception is exact-host, step-aware, and fail-closed

Read full pinned spec ↗

spectransparent-mode-detectionactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Launcher mode is detected from invocation context

Read full pinned spec ↗

spectray-appactive5 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. First-launch readiness feedback
  2. Native tray behavior by platform wrapper
  3. Seedlings submenu exposes OpenCode Web
  4. Per-project Stop action for running web containers
  5. Attach Here branches on selected agent

Read full pinned spec ↗

spectray-cli-coexistenceactive4 requirements

Contract for running the Tillandsias tray icon concurrently with CLI modes, detecting whether the host has a graphical session, and tolerating broken stdout/stderr without taking the process down.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (4)
  1. Detect graphical session for tray-aware CLI
  2. CLI modes spawn the tray when a graphical session is available
  3. Tray remains running after CLI session ends
  4. Broken stdout/stderr does not terminate the process

Read full pinned spec ↗

spectray-concurrencyactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Tray operations are concurrency-safe

Read full pinned spec ↗

spectray-host-control-socketactive promoted-from: openspec/changes/archive/2026-04-26-tray-host-control-socket/ annotation-count: 205 requirements

Establish a Unix-socket control plane for out-of-band communication between the tray process and tray-spawned containers (router, browser MCP, future control-plane consumers). Provides a single reviewable wire protocol, framed message format, and capability-based routing mechanism instead of ad-hoc communication channels for each new feature.

Document status: active promoted-from: openspec/changes/archive/2026-04-26-tray-host-control-socket/ annotation-count: 20. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Socket Creation and Lifecycle
  2. Wire Format and Framing
  3. Message Types and Capability-Based Routing
  4. Error Handling and Reliability
  5. Login completion is a notification, not a poll

Read full pinned spec ↗

spectray-icon-lifecycleactive5 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Five-state tray icon lifecycle
  2. Build-time tray icon rendering for all 5 states
  3. compute_icon_state reflects full lifecycle
  4. TrayIconState enum variants renamed
  5. Initial tray state is Pup

Read full pinned spec ↗

spectray-menuactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Tray menu reflects current runtime state

Read full pinned spec ↗

spectray-minimal-uxactive0 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

spectray-progress-and-icon-statesactive promoted-from: openspec/changes/archive/2026-04-26-tray-progress-and-icon-states/ annotation-count: 55 requirements

Replace the comma-joined, fragmentary status chip with an additive, emoji-based progress indicator that grows as each subsystem comes online. Give the user at-a-glance visibility into "checklist in progress, X done so far" and add an `Unhealthy` stage that collapses the menu when any subsystem fails instead of showing a disabled menu full of items.

Document status: active promoted-from: openspec/changes/archive/2026-04-26-tray-progress-and-icon-states/ annotation-count: 5. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Additive Progress Chip
  2. Unhealthy Stage
  3. Cold-Start Baseline
  4. Locale Keys for Chip
  5. Sign-In Label Rename

Read full pinned spec ↗

spectray-projects-renameactive promoted-from: openspec/changes/archive/2026-04-27-tray-projects-rename/ annotation-count: 55 requirements

Improve project submenu labels with explicit source cues and temporarily disable the broken i18n menu until translation infrastructure is repaired. Rename generic "Projects" and "Remote Projects" labels to emoji-prefixed, filesystem-aware labels that tell users at a glance where each list comes from.

Document status: active promoted-from: openspec/changes/archive/2026-04-27-tray-projects-rename/ annotation-count: 5. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. Project List Label Updates
  2. Language Submenu Removal
  3. Code Hygiene — Dormant Infrastructure
  4. Tombstone Annotation
  5. Documentation Update

Read full pinned spec ↗

spectray-subprocess-managementactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Tray-owned subprocesses are tracked and cleaned up

Read full pinned spec ↗

spectray-ui-integrationactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Native tray integration matches platform expectations

Read full pinned spec ↗

spectray-uxstatus: active6 requirements

Define the minimalistic tray UX flow for Tillandsias, showing only essential elements at each stage of the application lifecycle.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. UX curation governance — Tlatoāni approval is MANDATORY for every UX change
  2. First-launch minimal tray
  3. Dynamic environment verification status
  4. Post-initialization menu items
  5. Per-project action buttons
  6. Stale container cleanup

Read full pinned spec ↗

specupdate-systemobsolete1 requirements

No purpose recorded in this spec.

Document status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Platform-appropriate artifact selection

Read full pinned spec ↗

specuser-runtime-lifecycleactive2 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (2)
  1. User runtime images are reproducible release artifacts
  2. Host prerequisites are explicit

Read full pinned spec ↗

specversioningstatus: active8 requirements

Define the artifact version scheme and its single source of truth. The scheme is epoch-anchored CalVer (operator ruling 2026-08-31): versions encode WHEN an artifact was built, never WHAT milestone it contains — milestone membership lives in the plan ledger (`desired_release` ordered buckets) and the README release row's milestone clause.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (8)
  1. Epoch-anchored version scheme
  2. VERSION file as source of truth
  3. Monotonic version increments
  4. Milestones are decoupled from versions
  5. Cargo semver derivation
  6. Immutable version tags
  7. Rolling channel tags
  8. Automated version bump script

Read full pinned spec ↗

specvm-idiomatic-layeractive phase: 25 requirements

Define the contract for the `tillandsias-vm-layer` crate: a single Rust API that abstracts host↔VM operations behind a `VmRuntime` trait with two backends. On Windows, the `WslRuntime` backend drives WSL2 distros via the `wsl.exe` command-line and (where available) the `Win32_HCS_HostComputeSystem` COM surface. On macOS, the `VzRuntime` backend drives Virtua

Document status: active phase: 2. This describes the spec’s lifecycle, not verified implementation.

Requirements (5)
  1. `VmRuntime` trait is the only public surface
  2. `WslRuntime` is the only place `wsl.exe` is invoked
  3. `VzRuntime` is the only place Virtualization.framework symbols are touched
  4. `exec` propagates stdio and exit code with TTY support
  5. `wait_ready` is event-driven, not polling

Read full pinned spec ↗

specvm-provisioning-lifecycleactive6 requirements

Specify the first-run provisioning, steady-state startup, and shutdown contract for the Fedora 44 Core VM that hosts the in-VM `tillandsias-headless` and the podman enclave on non-Linux hosts. On first launch, the host downloads the Fedora 44 rootfs from the official Fedora mirror and the matching `tillandsias-linux-x86_64` binary from our GitHub release. Th

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. First-run provisioning downloads Fedora rootfs and tillandsias binary
  2. Provisioning surfaces as a single condensed status line
  3. Provisioning is idempotent
  4. Tray exit triggers graceful drain
  5. Provisioning log is captured for diagnostics
  6. Launch failures are graceful — bounded, classified, actionable

Read full pinned spec ↗

specvsock-exec-authzactive0 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specvsock-transportactive phase: 27 requirements

Extend `tillandsias-control-wire` with a virtio-vsock transport so the Windows and macOS host-side trays can talk to the in-VM `tillandsias-headless` process. Today the control wire only listens on a Unix domain socket at `$XDG_RUNTIME_DIR/tillandsias/control.sock`; that path is Linux-only and does not survive an OS-VM boundary. vsock is the host↔guest IPC p

Document status: active phase: 2. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. CID allocation contract
  2. Stable port `42420`
  3. Framing and handshake are identical to the Unix-socket transport
  4. New `ControlMessage` variants for VM lifecycle and remote enumeration
  5. Error model surfaces transport failures explicitly
  6. Silent guest work remains live through capability-gated heartbeats
  7. Platform transport matrix — the host side differs, the guest side does not

Read full pinned spec ↗

specweb-imagestatus: active6 requirements

TBD - created by archiving change web-image. Update Purpose after archive.

Document status: status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Minimal Alpine-based container image
  2. Static file serving via busybox httpd
  3. Port 8080 exposed
  4. Entrypoint displays banner and serving URL
  5. Graceful shutdown
  6. Document root at /var/www

Read full pinned spec ↗

specwindow-lifecycle-concurrencyactive1 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Requirements (1)
  1. Window lifecycle updates are token-scoped

Read full pinned spec ↗

specwindows-cross-buildstatus: obsolete0 requirements

No purpose recorded in this spec.

Document status: status: obsolete. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specwindows-event-loggingstatus: active platform: windows-only build-status: active (reactivated for the native Win32 tray, 2026-07-18) derived-from: operator directive 2026-07-18 (relay ALL INFO/WARN/ERROR) + archive windows-etw-logging-completed-2026-04-27 last-updated: 2026-07-186 requirements

Defines Windows-specific diagnostic relay to the Windows Event Log. The Tillandsias tray writes every INFO, WARN, and ERROR tracing event to the Application Event Log where a power user can discover them in Event Viewer > Windows Logs > Application > Source "Tillandsias" — including provisioning failures on machines where the tray UI itself is failing (the 2

Document status: status: active platform: windows-only build-status: active (reactivated for the native Win32 tray, 2026-07-18) derived-from: operator directive 2026-07-18 (relay ALL INFO/WARN/ERROR) + archive windows-etw-logging-completed-2026-04-27 last-updated: 2026-07-18. This describes the spec’s lifecycle, not verified implementation.

Requirements (6)
  1. Windows Event Log Integration
  2. Event Type Mapping
  3. Metadata Preservation in Event Log
  4. Graceful Degradation
  5. Layer Implementation
  6. Cross-Platform Cost

Read full pinned spec ↗

specwindows-native-trayactive phase: 47 requirements

Define the contract for the Windows-native tray binary `tillandsias-windows-tray` (produces `tillandsias-tray.exe`). The binary runs on the host as an OS-native Win32 application, registers a NotifyIcon (system tray icon) via the `Shell_NotifyIcon` API surface (wrapped by the `tray-icon` crate, with raw `windows-rs` fallbacks for parity with the Linux tray),

Document status: active phase: 4. This describes the spec’s lifecycle, not verified implementation.

Requirements (7)
  1. Win32 NotifyIcon is the only tray surface
  2. Menu items match the host-shell parity contract
  3. WSL2 distro lifecycle is owned by this binary
  4. WSLg passthrough enables in-VM Chromium on supported hosts
  5. Terminal attach routes through Windows Terminal via `vm-exec`
  6. `--diagnose` CLI mode emits a stable bundled health report
  7. The taskbar approval follows the tray across executable paths

Read full pinned spec ↗

specwsl-daemon-orchestrationactive0 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

specwsl-runtimeactive0 requirements

No purpose recorded in this spec.

Document status: active. This describes the spec’s lifecycle, not verified implementation.

Read full pinned spec ↗

speczen-default-with-ollama-analysis-poolstatus: active promoted-from: openspec/changes/archive/2026-04-25-zen-default-with-ollama-analysis-pool/ annotation-count: 33 requirements

Split agent responsibilities between cloud-based Zen models (for tool-calling and plan execution) and local ollama models (for analysis-only tasks like summarization, error classification, and commit message generation). Default to Zen; reserve ollama for privacy-critical analysis workloads.

Document status: status: active promoted-from: openspec/changes/archive/2026-04-25-zen-default-with-ollama-analysis-pool/ annotation-count: 3. This describes the spec’s lifecycle, not verified implementation.

Requirements (3)
  1. Default model routing
  2. Tier-tagged tool-capable model pre-pulls
  3. Squid proxy workaround for ollama manifests

Read full pinned spec ↗

Completed change checklists 115

Every recorded task was checked before archive. Current runtime behavior still needs separate evidence.

changeAuto updaterChecklist complete34 checked · 0 open

Without automatic updates, users must manually check GitHub Releases, download the correct platform artifact, verify its integrity, and replace the running binary. Most users will never do this. They will run outdated versions with known bugs and missing security patches indefinitely. For a privacy-focused application, running stale software is a direct secu

A checked archival task list records work, not proof that every goal-state behavior still works now.

34 of 34 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCli modeChecklist complete11 checked · 0 open

Tillandsias currently only works as a system tray application. Users who prefer the terminal or want to script container launches have no way to attach to a project from the command line. Adding a CLI mode lets users run `tillandsias ~/src/my-project` to launch an interactive container directly, with user-friendly output instead of raw tracing logs.

A checked archival task list records work, not proof that every goal-state behavior still works now.

11 of 11 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDev build scriptChecklist complete16 checked · 0 open

Developers need a single `./build.sh` entry point that handles the full local development lifecycle — build, install, clean, remove, wipe — without knowing about toolbox internals, Tauri CLI setup, or system dependency installation. The script must auto-create the tillandsias toolbox if it doesn't exist, so a fresh checkout on any Fedora Silverblue machine w

A checked archival task list records work, not proof that every goal-state behavior still works now.

16 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix opencode configChecklist complete7 checked · 0 open

The default container image ships an `opencode.json` that defines a custom provider and model (`opencode/big-pickle`) which does not exist in any released version of OpenCode. When the container starts, OpenCode attempts to resolve this model, fails with "agent coder not found", and exits immediately. This makes the container completely unusable on first lau

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeNix builder toolboxChecklist complete19 checked · 0 open

Container images built with `podman build` have no staleness detection — once cached, they're never rebuilt even when the Containerfile, entrypoint, or opencode config changes. Users must manually `podman rmi` to force a rebuild. This is broken UX.

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRemove terminal detectionChecklist complete5 checked · 0 open

Terminal emulator detection is brittle, platform-specific, and a non-goal. Supporting ptyxis, gnome-terminal, konsole, alacritty, kitty, foot, xterm, etc. across Linux/macOS/Windows is impossible to maintain. The container has bash. `podman run -it` provides a TTY. That's all we need.

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSrc attachment pointChecklist complete4 checked · 0 open

Users need a way to start working even before any projects exist. The `~/src` directory itself should always appear as an "Attach Here" point — this is where users create new projects, clone repos, and start from scratch. Without this, a fresh install shows an empty menu with no actionable entry.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTillandsias bootstrapChecklist complete69 checked · 0 open

There is no lightweight, opinionated way for non-technical users to go from an idea to a running application without understanding containers, runtimes, git, or cloud infrastructure. Existing tools either expose too much complexity (Docker Desktop, VS Code Dev Containers) or lock users into proprietary platforms (Replit, GitHub Codespaces). Tillandsias fills

A checked archival task list records work, not proof that every goal-state behavior still works now.

69 of 69 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray app fixesChecklist complete10 checked · 0 open

The tray app runs but is non-functional: menu clicks do nothing (including Quit), projects aren't visible despite being detected by the scanner, and the menu never updates. Five bugs identified via code audit.

A checked archival task list records work, not proof that every goal-state behavior still works now.

10 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeVersion schemeChecklist complete16 checked · 0 open

The project needs a versioning scheme that is monotonically increasing, tied to the spec-driven development workflow, and supports rolling release tags for container images and binary distribution. Standard semver alone doesn't capture the relationship between OpenSpec changes and build iterations.

A checked archival task list records work, not proof that every goal-state behavior still works now.

16 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeWeb imageChecklist complete8 checked · 0 open

99% of projects Tillandsias creates will eventually need to serve static files over HTTP: an HTML page, some JavaScript, CSS, images. Today, if a user generates a web project and clicks "Run," there is no lightweight runtime image to serve it. The default forge image is ~1GB and includes dev tooling (Nix, OpenCode, Node, git) that a running web app does not

A checked archival task list records work, not proof that every goal-state behavior still works now.

8 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix attach here terminalChecklist complete4 checked · 0 open

"Attach Here" is completely broken. The tray app spawns a detached container that the user can't interact with. The whole point of Tillandsias is: click Attach Here → OpenCode appears. Zero manual steps. No "podman exec" commands.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDynamic port allocationChecklist complete12 checked · 0 open

Stale containers (from unclean terminal closures) hold port ranges. The current allocator only checks in-memory state, not actual podman containers, so new containers collide with ports held by orphans. The 100-port ranges (3000-3099, 3100-3199) are wasteful — most environments need only a handful of ports. Smaller ranges with real podman-backed conflict det

A checked archival task list records work, not proof that every goal-state behavior still works now.

12 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeEmbed scripts in binaryChecklist complete14 checked · 0 open

Currently `gh-auth-login.sh`, `build-image.sh`, `ensure-builder.sh`, and image source files (entrypoint, configs, flake) are copied to `~/.local/share/tillandsias/` at install time and executed from there. This is a supply chain risk: any process or user with write access to `~/.local/share/` can tamper with these scripts, and the signed Tillandsias binary w

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix versioning and ciChecklist complete4 checked · 0 open

2026-03-24-fix-versioning-and-ci

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge bash skillsChecklist complete6 checked · 0 open

The forge container runs OpenCode as the primary interface, but users sometimes need to drop into a raw bash shell — to run build commands, inspect files, install tools, or debug issues that OpenCode can't handle. There is currently no way to do this without detaching from the container and running `podman exec` manually.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge gh authChecklist complete7 checked · 0 open

The forge container has `gh` installed but no way for users to authenticate with GitHub. Without authentication, users cannot push/pull from private repos, create PRs, or interact with GitHub APIs. The authentication flow involves a one-time code and token that must remain private from AI agents.

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge shell toolsChecklist complete10 checked · 0 open

The forge container's bash mode drops users into a bare-bones shell with only the essentials. Power users troubleshooting inside the container need a healthy set of terminal tools — alternative shells, file managers, editors, and modern CLI sugar. AJ doesn't need to know these exist, but developers who use `--bash` or the Terminal menu should feel at home.

A checked archival task list records work, not proof that every goal-state behavior still works now.

10 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge welcome fish defaultChecklist complete20 checked · 0 open

Users land in a bare bash prompt with no context about what they're looking at. They don't know what's mounted, what tools are available, or even what shell they're in. The terminal should feel welcoming, informative, and opinionated — like Kitty on Hyprland, but showing the host-to-guest mapping that makes Tillandsias unique.

A checked archival task list records work, not proof that every goal-state behavior still works now.

20 of 20 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeGit credential mountChecklist complete4 checked · 0 open

Forge containers are ephemeral — every `git push`, `gh pr create`, or authenticated GitHub API call fails because credentials don't survive container recreation. Users must re-authenticate on every Attach Here, which breaks the "just works" promise.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Icon Generation, Menu Cleanup, Security AuditChecklist complete21 checked · 0 open

The tray/app icons were 104-byte solid green squares -- placeholder files that gave no visual identity. The "Start" menu item was a duplicate of "Attach Here" (identical handler), adding confusion. "Terminal" is developer jargon that Average Joe users won't understand. Container security flags needed formal documentation.

A checked archival task list records work, not proof that every goal-state behavior still works now.

21 of 21 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeIconography gh auth bash modeChecklist complete15 checked · 0 open

Four UX gaps need fixing before v0.1: (1) "Attach Here" menu items have no visual state — you can't tell if an environment is idle or active without scanning the running list. (2) GitHub auth from the tray launches a terminal but the interactive flow doesn't work properly, blocking first-time setup. (3) CLI mode has no `--bash` flag for dropping into a troub

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeInit prebuilt imagesChecklist complete15 checked · 0 open

First-time users hit a multi-minute wait when they click "Attach Here" because the forge image needs to build (Nix download + podman load). This kills the "just works" promise. A `tillandsias init` command that pre-builds images in the background — triggered automatically by the installer — means images are ready before the user ever opens the tray.

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLanding readme installChecklist complete5 checked · 0 open

The current README.md is an architecture document. A visitor landing on the GitHub page sees workspace structure, container naming conventions, and genus tables before they ever learn how to install or run the app. For a project that targets Average Joe, the first screen should answer one question: "How do I use this?"

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLocal cross platform buildsChecklist complete15 checked · 0 open

The release pipeline fails on every `workflow_dispatch` run because the tag validation step receives `main` instead of a version tag. Meanwhile, GitHub Actions warns that Node.js 20 actions will be **force-migrated to Node.js 24 on June 2, 2026** — breaking builds if not addressed proactively. Debugging these CI failures burns slow, expensive, rate-limited c

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMove github login to settingsChecklist complete6 checked · 0 open

GitHub Login currently sits as a top-level tray menu item, cluttering the main menu alongside project actions. It's a one-time setup action that belongs under Settings. The Settings menu item already exists but is a stub — this change makes it a submenu and moves GitHub Login into it.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRemote project cloneChecklist complete19 checked · 0 open

After GitHub authentication, Tillandsias knows who the user is but offers no way to pull down their remote repositories. Users must manually `gh repo clone` from a terminal. Adding a "Remote Projects" submenu that lists GitHub repos not already cloned locally turns Tillandsias into a one-click project bootstrapper — discover, clone, and start developing.

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSingleton instanceChecklist complete10 checked · 0 open

Running `tillandsias` multiple times (e.g. from desktop launcher, autostart, and terminal) spawns duplicate tray icons with independent state. Each instance starts its own scanner, podman event stream, and event loop — wasting resources and confusing the user with multiple identical menus.

A checked archival task list records work, not proof that every goal-state behavior still works now.

10 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray github loginChecklist complete7 checked · 0 open

2026-03-24-tray-github-login

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeGraceful restartChecklist complete4 checked · 0 open

When the tray app crashes or is restarted, the user's development environments keep running inside their containers — but the tray menu shows nothing. Every project appears idle. The user must manually inspect state or wait for a podman event to trigger.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeNative secrets storeChecklist complete15 checked · 0 open

Tillandsias currently stores the GitHub OAuth token in plain text at `~/.cache/tillandsias/secrets/gh/hosts.yml`. This file is mounted read-only into forge containers, but on the host it sits unencrypted on disk. Any process running as the user can read it. On a shared machine or compromised host, this is a credential leak.

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRelease verificationChecklist complete14 checked · 0 open

The release-pipeline and cosign-signing changes were archived with incomplete verification tasks. The implementation is done but has never been tested against a real CI run. These 9 tasks require pushing a tag, observing the GitHub Actions workflow, and manually verifying artifacts — they cannot be completed without a configured CI environment with signing s

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRuntime podman onlyChecklist complete14 checked · 0 open

The runtime image build pipeline (`build-image.sh` + `ensure-builder.sh`) uses `toolbox create` and `toolbox run` to manage a persistent `tillandsias-builder` toolbox containing Nix. Toolbox is Fedora-specific. Users on macOS, Windows, Ubuntu, Arch, or any non-Fedora Linux cannot run the installed Tillandsias binary because `toolbox` does not exist on their

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray version creditChecklist complete6 checked · 0 open

Users have no way to tell which version of Tillandsias is running from the tray menu. Adding version and credit lines makes the app feel polished and helps with troubleshooting ("which version are you on?"). Also serves as a visual trigger to test the Tauri auto-update system — bumping the minor version will let installed instances detect the update.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAgent selection claudeChecklist complete19 checked · 0 open

Tillandsias currently hardcodes OpenCode as the sole AI coding agent launched in forge containers. Some users prefer Claude Code. Rather than forcing a single tool, the tray should let the user choose which agent starts when they click "Attach Here". The selection must persist across restarts and be passed into containers so the entrypoint launches the corre

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMenu display active containers inlineChecklist complete8 checked · 0 open

The tray menu currently segregates running projects into a "Running Environments" submenu. This is tech jargon — users don't think in terms of "environments". More importantly, it buries active projects one click deeper behind an abstraction layer. When a project is running, users want to act on it immediately without hunting through submenus.

A checked archival task list records work, not proof that every goal-state behavior still works now.

8 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRoot terminal launcherChecklist complete11 checked · 0 open

Power users and maintainers need a fast path to a bash terminal at the root `~/src/` directory — the same scope as the top-level "Attach Here" entry. Today the only way to get there is to use a per-project Maintenance terminal, which scopes the container to a single project directory. A global root terminal opens in the full `~/src/` mount, useful for cross-

A checked archival task list records work, not proof that every goal-state behavior still works now.

11 of 11 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSettings github submenuChecklist complete6 checked · 0 open

The Settings submenu mixes GitHub authentication and Remote Projects at the same level as version/credit metadata. As more GitHub-related items are added, this flat structure will become cluttered. Grouping them under a single "GitHub" submenu improves discoverability and provides a clear home for future GitHub features.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeUser friendly errorsChecklist complete16 checked · 0 open

Error messages returned by Tillandsias handlers currently contain developer-facing text: internal script names (`build-image.sh`), image tags (`tillandsias-forge:latest`), shell invocations (`./build.sh --install`), and exit codes. These strings leak implementation details to users who have no context for them and cannot act on them.

A checked archival task list records work, not proof that every goal-state behavior still works now.

16 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeVersioned forge imagesChecklist complete23 checked · 0 open

The forge image is tagged `tillandsias-forge:latest`. When the app auto-updates to a new version with a different entrypoint or config, the old image is reused because the staleness detection hash file lives in a temp dir that does not persist across restarts. Users get stale containers with old entrypoints, causing silent breakage that is difficult to diagn

A checked archival task list records work, not proof that every goal-state behavior still works now.

23 of 23 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeClaude login refresh labelChecklist complete2 checked · 0 open

Authenticated Claude shows "Claude (authenticated)" which is inconsistent with GitHub's "GitHub Login Refresh" pattern. Should match.

A checked archival task list records work, not proof that every goal-state behavior still works now.

2 of 2 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix dual tray iconChecklist complete3 checked · 0 open

On Windows and macOS, two tray icons appear: a white static one (from tauri.conf.json `trayIcon` config) and a colorful tillandsia one (from programmatic `TrayIconBuilder` in main.rs). Only the programmatic one is clickable. The static one is a non-functional duplicate.

A checked archival task list records work, not proof that every goal-state behavior still works now.

3 of 3 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: fix-entrypoint-agent-launchChecklist complete7 checked · 0 open

The default agent was recently switched from opencode to claude in both the Rust config (`SelectedAgent::default() -> Claude`) and the entrypoint script (`AGENT="${TILLANDSIAS_AGENT:-claude}"`). However, the entrypoint still unconditionally defines opencode paths and the tar extraction creates a doubled `opencode/opencode` path that fails to execute.

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix gh auth macos pathChecklist complete6 checked · 0 open

`gh-auth-login.sh` hardcodes `CACHE_DIR="${HOME}/.cache/tillandsias"`. On macOS, the Rust code uses `~/Library/Caches/tillandsias` (via `dirs::cache_dir()`). This means auth credentials get written to the wrong directory and containers mount empty dirs, breaking GitHub authentication on macOS.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix GitHub Login PermissionsChecklist complete5 checked · 0 open

When a user runs `gh auth login` inside the forge container, the git config directory is mounted read-only (`:ro`). This causes the operation to fail with:

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Fix macOS Finder Launch PATH IssueChecklist complete9 checked · 0 open

When Tillandsias is launched from Finder/Launchpad on macOS, the forge image build fails because `podman` is not in PATH. Finder-launched apps get a minimal PATH (`/usr/bin:/bin:/usr/sbin:/sbin`) that does not include Homebrew (`/opt/homebrew/bin`) or other common tool locations.

A checked archival task list records work, not proof that every goal-state behavior still works now.

9 of 9 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix macOS OS DetectionChecklist complete3 checked · 0 open

When launching a forge environment on macOS, the welcome message shows "Unknown OS" because `detect_host_os()` only reads `/etc/os-release`, which does not exist on macOS. This produces a confusing `TILLANDSIAS_HOST_OS='Unknown OS'` env var passed to the forge.

A checked archival task list records work, not proof that every goal-state behavior still works now.

3 of 3 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix macOS Platform DefaultsChecklist complete8 checked · 0 open

Several source files use Linux-only system calls, paths, or CLI flags that fail on macOS:

A checked archival task list records work, not proof that every goal-state behavior still works now.

8 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix macos updater symlinkChecklist complete3 checked · 0 open

The Tauri auto-updater fails on macOS when the app is launched via a symlink at `~/.local/bin/tillandsias` pointing to `~/Applications/Tillandsias.app/Contents/MacOS/tillandsias-tray`. Tauri's `StartingBinary` (from `tauri-utils`) uses a `#[ctor]` static that calls `std::env::current_exe()` and rejects any path containing a symlink on macOS, producing:

A checked archival task list records work, not proof that every goal-state behavior still works now.

3 of 3 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix opencode tar extractionChecklist complete2 checked · 0 open

The opencode tarball from GitHub releases contains a single bare file `opencode` (no directory wrapper). The entrypoint used `tar xzf ... --strip-components=1` which stripped the filename itself, extracting nothing. Result: `chmod` fails on the missing binary and the container exits.

A checked archival task list records work, not proof that every goal-state behavior still works now.

2 of 2 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Complete --wipe cleanupChecklist complete7 checked · 0 open

1. **`scripts/uninstall.sh`** -- add comprehensive path removal in `--wipe` block 2. **`scripts/install.sh`** -- make the uninstall fallback message platform-aware

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge version labelingChecklist complete10 checked · 0 open

When debugging issues from logs alone, there is no way to tell which version of Tillandsias or which forge image tag is running. Version information is critical for triage: knowing "this was v0.1.89" vs "v0.1.88" immediately narrows the regression window.

A checked archival task list records work, not proof that every goal-state behavior still works now.

10 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMacos native buildChecklist complete21 checked · 0 open

Tillandsias now needs to build and run on macOS (Apple Silicon). The existing `build.sh` uses Fedora toolbox which doesn't exist on macOS, and `build-windows.sh` cross-compiles from Linux. There's no native macOS build path. Additionally, `install.sh` downloads a `.dmg` on macOS but never mounts/extracts it — it prints "Installed to ~/.local/bin/tillandsias"

A checked archival task list records work, not proof that every goal-state behavior still works now.

21 of 21 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Auto-start podman machine at launchChecklist complete4 checked · 0 open

On macOS and Windows, podman requires a Linux VM (`podman machine`) to function. Currently, if the VM is not running when Tillandsias launches, the app drops into decay state and the user must manually start the machine via CLI. This is a poor experience -- most users expect the app to "just work" when podman is installed.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changePodman resilienceChecklist complete9 checked · 0 open

When podman is unavailable (not installed, or machine not running on macOS/Windows), the podman events listener enters a tight ~1s retry loop that spams logs, wastes CPU, and on Windows makes the tray icon flicker so fast users can't click "Quit" and must kill via Task Manager. Three root causes:

A checked archival task list records work, not proof that every goal-state behavior still works now.

9 of 9 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix install running appimageChecklist complete4 checked · 0 open

`curl -fsSL .../install.sh | bash` fails with `curl: (23) client returned ERROR on write` when the AppImage is already running. `curl -o` can't write directly to a running executable ("Text file busy").

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix entrypoint regressionChecklist complete14 checked · 0 open

Container entrypoints crash silently on launch due to `set -e` (errexit) interactions with bash function return codes. Two bugs:

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCondense log formatChecklist complete11 checked · 0 open

The default tracing-subscriber `.pretty()` formatter dumps accountability metadata (`accountability: true, category: "secrets", safety: "...", spec: "..."`) inline with the human message, then repeats it in the span context section. This makes logs hard to read and buries the safety/trace information that power users need to feel confident about how their da

A checked archival task list records work, not proof that every goal-state behavior still works now.

11 of 11 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix nix build staleness divergenceChecklist complete6 checked · 0 open

The `_compute_hash()` function in `build-image.sh` uses `find` to traverse image source directories, which sees all working tree files including untracked ones. But Nix flake builds only see git-tracked files. This divergence means: (1) staleness check triggers on untracked files that Nix ignores, causing unnecessary rebuilds, and (2) staleness check can rep

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix seccomp spec accuracyChecklist complete5 checked · 0 open

The podman-orchestration spec contains a misleading "Seccomp profile compatibility" scenario that documents *awareness* of a problem rather than a solution. It says "the application is aware that the default profile blocks approximately 130 syscalls" and "logs should indicate seccomp as a possible cause." This is a hack — a try-catch mentality instead of a p

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Windows Full SupportChecklist complete25 checked · 0 open

Tillandsias had no working Windows support despite having cross-compilation and CI builds for Windows. Multiple issues prevented the app from functioning on a Windows host:

A checked archival task list records work, not proof that every goal-state behavior still works now.

25 of 25 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Windows Runtime FixesChecklist complete19 checked · 0 open

After the initial `windows-full-support` change shipped, seven runtime issues surfaced during real-world testing on Windows 11:

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAdd knowledge source of truthChecklist complete16 checked · 0 open

OpenSpec guarantees implementation converges to spec. But what keeps the spec itself correct? Today, agents infer best practices from training data — non-deterministic and uncitable. A local, cached source of truth for the tech stack means agents cite verified facts instead of guessing. This completes the convergence chain: implementation → spec → ground tru

A checked archival task list records work, not proof that every goal-state behavior still works now.

16 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCli version and init flagsChecklist complete14 checked · 0 open

The CLI has no `--version` flag, making it impossible to check the installed version from the command line. Additionally, `init` is the only subcommand-style argument — all others use `--flag` style (`--update`, `--stats`, `--clean`). This inconsistency is confusing.

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDefault agent opencodeChecklist complete1 checked · 0 open

2026-04-05-default-agent-opencode

A checked archival task list records work, not proof that every goal-state behavior still works now.

1 of 1 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFedora 43 baseChecklist complete2 checked · 0 open

2026-04-05-fedora-43-base

A checked archival task list records work, not proof that every goal-state behavior still works now.

2 of 2 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix appimage fuse fd leakChecklist complete7 checked · 0 open

When Tillandsias runs as an AppImage on Linux, the squashfuse FUSE mount creates file descriptors that are inherited by child processes. When `podman run` is invoked, crun (the OCI runtime) iterates `/proc/self/fd/` and attempts to stat each FD. The kernel denies access to the FUSE FDs across the user namespace boundary, producing:

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix embedded localesChecklist complete6 checked · 0 open

The i18n infrastructure commit added `images/default/locales/` (en.sh, es.sh) and updated `flake.nix` to reference them, but `embedded.rs` was never updated to embed and extract those locale files. When the installed binary runs `build-image.sh`, the extracted temp dir is missing `images/default/locales/`, causing `nix build` to fail with: `error: path '...-

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix macos terminal fallbackChecklist complete12 checked · 0 open

When a user installs a non-default terminal emulator on macOS (Ghostty, Kitty, etc.), clicking "Attach Here" from the system tray silently fails. The terminal window never opens, but the tray reports success. The log shows:

A checked archival task list records work, not proof that every goal-state behavior still works now.

12 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix opencode installerChecklist complete7 checked · 0 open

The OpenCode entrypoint manually downloads a tar.gz from GitHub releases, extracts with --strip-components=1, and chmod's the binary. This is fragile — the tar structure has broken before, and the update logic duplicates download/extract code. OpenCode has an official installer at `curl -fsSL https://opencode.ai/install | bash` that handles both install and

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix spec audit warningsChecklist complete19 checked · 0 open

A knowledge audit against the spec corpus found 6 WARNING-level inaccuracies and 8 SUGGESTION-level gaps where specs omit relevant operational details. Left unfixed, warnings cause implementation drift — developers trusting the spec will make wrong decisions about platform backends, library names, SELinux interactions, Nix build behavior, and CI environment

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLanguage menu and localeChecklist complete20 checked · 0 open

2026-04-05-language-menu-and-locale

A checked archival task list records work, not proof that every goal-state behavior still works now.

20 of 20 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeOpenspec in terminalsChecklist complete5 checked · 0 open

2026-04-05-openspec-in-terminals

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRemove api key auth and fix welcomeChecklist complete26 checked · 0 open

The Claude API key authentication flow is a legacy artifact from before OAuth via `~/.claude/` was implemented. It adds complexity (keyring storage, embedded prompt script, polling loop, env var injection) for a path that most users don't need — Max/Pro subscribers authenticate via `claude login` on the host, and the resulting `~/.claude/` directory is alrea

A checked archival task list records work, not proof that every goal-state behavior still works now.

26 of 26 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTerminal tooling cheatsheetChecklist complete6 checked · 0 open

2026-04-05-terminal-tooling-cheatsheet

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeWelcome screen cleanupChecklist complete7 checked · 0 open

2026-04-05-welcome-screen-cleanup

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Add Missing @trace AnnotationsChecklist complete12 checked · 0 open

A methodology audit found that 6 Rust source files have zero `@trace` annotations despite implementing specs that exist. This breaks the bidirectional link between specs and implementation that OpenSpec requires for monotonic convergence.

A checked archival task list records work, not proof that every goal-state behavior still works now.

12 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeConfig overlay and inferenceChecklist complete17 checked · 0 open

Forge containers launch with a bare OpenCode config that has no development methodology, no Flutter-first recommendations, and a hardcoded model tier that ignores the host GPU. Agents inside forges start from zero context every time -- they don't know about OpenSpec, monotonic convergence, CRDT patterns, or that we prefer Flutter for cross-platform apps. Mea

A checked archival task list records work, not proof that every goal-state behavior still works now.

17 of 17 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeContainer Process HardeningChecklist complete15 checked · 0 open

Security audit found that containers lack process limits and per-container process restrictions. All containers share default seccomp but have no `--pids-limit` (fork bomb risk) and no filesystem immutability enforcement for service containers.

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCreate secret management specChecklist complete10 checked · 0 open

An audit found 16 `@trace spec:secret-management` annotations across the codebase but no formal spec exists in `openspec/specs/`. The traces span `gh-auth-login.sh` (9 lines), `container_profile.rs`, `handlers.rs`, `launch.rs`, `runner.rs`, and `docs/cheatsheets/github-credential-tools.md`. Without a spec, the trace annotations are orphans — they reference a

A checked archival task list records work, not proof that every goal-state behavior still works now.

10 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDead code cleanupChecklist complete6 checked · 0 open

An audit of `#[allow(dead_code)]` annotations across `src-tauri/src/` found a mix of genuinely orphaned code, incorrectly suppressed warnings on used items, and planned-feature code that should remain. Left unchecked, dead code suppression masks real warnings and makes the codebase harder to audit.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix forge staleness and pruningChecklist complete12 checked · 0 open

The forge image staleness check has three issues: (1) the hash file is unversioned (`.last-build-forge.sha256`) but image tags are versioned (`tillandsias-forge:v0.1.97`), so the hash carries over across version bumps creating false "up to date" results; (2) the tray path skips the build script entirely when `image_exists` returns true, never checking for so

A checked archival task list records work, not proof that every goal-state behavior still works now.

12 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Fix Ghost Trace AnnotationsChecklist complete14 checked · 0 open

A methodology audit found 8 `@trace spec:X` annotations in code referencing specs that don't exist in `openspec/specs/`. These "ghost traces" break the bidirectional link between specs and implementation that OpenSpec depends on.

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Fix proxy-container spec security boundaryChecklist complete9 checked · 0 open

The proxy-container spec is materially wrong about a security-critical boundary. The spec claims:

A checked archival task list records work, not proof that every goal-state behavior still works now.

9 of 9 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Fix tray first-launch orderingChecklist complete7 checked · 0 open

On a fresh install, the user can launch the tray app before infrastructure (proxy, forge images) is built. The `install.sh` script runs `--init` as a background `nohup` process, so the tray may start before images exist.

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMethodology optimization cheatsheetChecklist complete5 checked · 0 open

A thorough audit of the OpenSpec methodology across the Tillandsias codebase revealed both strengths (273 traces, 142 changes archived in ~14 days, excellent enclave spec alignment) and systematic weaknesses (35% of main specs with zero traces, 8 ghost trace names, 4 unsynced archives, TRACES.md line-number drift). There is no single reference document that

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changePre-Commit Trace WarningsChecklist complete8 checked · 0 open

An audit found 8 ghost traces (referencing non-existent specs), 12 specs with zero code traces (35% disconnected), and no automated staleness detection for active changes. These gaps accumulate silently because nothing surfaces them during normal development workflow.

A checked archival task list records work, not proof that every goal-state behavior still works now.

8 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeScanner graceful degradationChecklist complete5 checked · 0 open

2026-04-08-scanner-graceful-degradation

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChange: async-inference-launchChecklist complete16 checked · 0 open

`ensure_enclave_ready()` in `src-tauri/src/handlers.rs` currently awaits `ensure_inference_running()` synchronously (handlers.rs:1498). The inference container is the slowest enclave service to come up: ollama takes 15–30 s to initialize, plus a 10-attempt exponential-backoff health check that can extend the launch path by another 1–55 s. Inference is also e

A checked archival task list records work, not proof that every goal-state behavior still works now.

16 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCi node24 audit and fixChecklist complete24 checked · 0 open

2026-04-16-ci-node24-audit-and-fix

A checked archival task list records work, not proof that every goal-state behavior still works now.

24 of 24 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChange: fix-podman-machine-host-aliasesChecklist complete16 checked · 0 open

User-reported failure on Windows 11 + podman 5.8 / WSL machine:

A checked archival task list records work, not proof that every goal-state behavior still works now.

16 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChange: fix-windows-extended-pathChecklist complete22 checked · 0 open

User-reported failure on Windows when attaching to a project from a relative path:

A checked archival task list records work, not proof that every goal-state behavior still works now.

22 of 22 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChange: fix-windows-image-routingChecklist complete18 checked · 0 open

On Windows, `run_build_image_script()` in `src-tauri/src/handlers.rs` hardcodes the build context to `images/default/Containerfile` regardless of which image is requested (`forge`, `proxy`, `git`, `inference`). The function correctly computes a versioned tag per image type, but it then runs `podman build` against the forge sources for every call. The result

A checked archival task list records work, not proof that every goal-state behavior still works now.

18 of 18 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeKeyring on host credential migrationChecklist complete98 checked · 0 open

Tillandsias's credential pipeline previously relied on bind-mounting the host D-Bus session bus socket into the git-service container so `gh` could reach the host's Secret Service. That pattern (a) didn't work on Windows or macOS where there is no D-Bus on the host, (b) leaked the *entire* host keyring (browser passwords, SSH passphrases, WiFi PSKs) into a c

A checked archival task list records work, not proof that every goal-state behavior still works now.

98 of 98 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChange: overlay-mount-cacheChecklist complete6 checked · 0 open

`tools-overlay-fast-reuse` (shipped previous commit) added a process-lifetime snapshot cache for the overlay path lookup in `ensure_tools_overlay()`. The remaining per-launch work happens in `src-tauri/src/launch.rs:337-348` inside `resolve_mount_source()`, which still does an `exists()` syscall on every `MountSource::ToolsOverlay` mount construction. Every

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChange: persistent-git-serviceChecklist complete11 checked · 0 open

The per-project git-service container (`tillandsias-git-<project>`) is currently torn down by the event-loop trigger in `src-tauri/src/event_loop.rs:608-616` whenever the last forge or maintenance container for a project exits. The next "Attach Here" for the same project then has to rebuild the git-service from scratch — image staleness check (~3 s on Window

A checked archival task list records work, not proof that every goal-state behavior still works now.

11 of 11 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChange: tools-overlay-fast-reuseChecklist complete21 checked · 0 open

Once the tools overlay is built (~234 MB containing claude/opencode/openspec), every "Attach Here" still pays a non-trivial overhead to *check* it before mounting:

A checked archival task list records work, not proof that every goal-state behavior still works now.

21 of 21 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeClamp small model to baked tiersChecklist complete4 checked · 0 open

The prior change (`zen-default-with-ollama-analysis-pool`) tier-tagged `small_model` by host capability — Ultra hosts got `ollama/qwen2.5:14b`, Mid/High got `ollama/qwen2.5-coder:7b`, etc. But only T0 (`qwen2.5:0.5b`) and T1 (`llama3.2:3b`) are baked into the inference image at build time. T2+ models pull at runtime through the Squid SSL-bump proxy and hit t

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTombstone tools overlayChecklist complete18 checked · 0 open

The move to a "full battery" hard-installed forge image in the `zen-default-with-ollama-analysis-pool` change superseded the runtime tools overlay for claude/opencode/openspec. Agents now live at `/usr/local/bin/` inside the forge image, symlinked from `/opt/agents/`, baked at `podman build` time with deterministic versions.

A checked archival task list records work, not proof that every goal-state behavior still works now.

18 of 18 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeZen default with ollama analysis poolChecklist complete10 checked · 0 open

The 0.5B local model can talk to opencode but can't follow opencode's tool-calling protocol reliably enough to drive a real coding session end-to-end. Larger local tool-capable models (qwen2.5-coder:7b+) are multi-GB downloads and struggle through the Squid proxy's SSL bump (empirically: `max retries exceeded: EOF` on manifest pulls).

A checked archival task list records work, not proof that every goal-state behavior still works now.

10 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCheatsheet methodology evolutionChecklist complete71 checked · 0 open

The `agent-source-of-truth` change shipped 60 cheatsheets and a methodology that mandated `## Sources of Truth` in specs. **It did not mandate provenance in the cheatsheets themselves.** I (the agent) generated all 60 cheatsheets from training-data memory — the user caught this and rejected the result, twice. Cheatsheets without provenance are worse than no

A checked archival task list records work, not proof that every goal-state behavior still works now.

71 of 71 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDirect podman callsChecklist complete59 checked · 0 open

Tillandsias uses bash scripts as middleware between Rust code and podman/gh CLI calls on the host. This worked well on Linux where bash is a first-class citizen, but it creates a fundamental problem on Windows: Git Bash (MSYS2) does not initialize properly when `bash.exe` is launched from a native Windows process. The project has accumulated workarounds (dri

A checked archival task list records work, not proof that every goal-state behavior still works now.

59 of 59 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge opencode methodology overhaulChecklist complete8 checked · 0 open

Today's `images/default/config-overlay/opencode/instructions/methodology.md` is 36 lines of generalities. The model in `~/src/java/ENVIRONMENT_REPORT.md` never even tried `which java` because the methodology never told it to discover what was on PATH. Per `~/src/java/container_enhancements.md`: opencode's instruction surface needs an action-first methodology

A checked archival task list records work, not proof that every goal-state behavior still works now.

8 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeOpencode web session otpChecklist complete33 checked · 0 open

OpenCode Web today is reachable by **any** local process or browser tab on the user's machine — anything that knows the URL `http://<project>.opencode.localhost:8080/` can connect. While the listener is loopback-only (rejected from LAN/Internet), there's no defence against:

A checked archival task list records work, not proof that every goal-state behavior still works now.

33 of 33 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray projects renameChecklist complete18 checked · 0 open

Two separate UX issues from local testing of `0.1.169.227`:

A checked archival task list records work, not proof that every goal-state behavior still works now.

18 of 18 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeInit incremental buildsChecklist complete25 checked · 0 open

The `tillandsias --init` command rebuilds all images from scratch on failure, wasting time when only one image fails. Users need incremental progress saving so failed builds can be retried without rebuilding successful images. Debug mode should also show failed build logs for troubleshooting.

A checked archival task list records work, not proof that every goal-state behavior still works now.

25 of 25 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeBrowser daemon trackingChecklist complete27 checked · 0 open

The browser isolation feature (wave 3) introduced ephemeral Chromium containers but left critical lifecycle gaps: no debouncing (rapid-fire window spawns), no tracking in `TrayState`, no user feedback on failure, and no cleanup on shutdown. The MCP server is currently a persistent daemon inside the forge container, but agents just need an on-demand tool — no

A checked archival task list records work, not proof that every goal-state behavior still works now.

27 of 27 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeStale secrets cleanupChecklist complete19 checked · 0 open

Unclean tray shutdowns (crashes, forced termination) leave stale podman secrets in the system. On next startup, secret creation fails with "secret name in use", blocking container launches. This requires manual cleanup (`podman secret rm tillandsias-ca-*`) before the tray can function again.

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLinux native portable executableChecklist complete47 checked · 0 open

Tauri introduces cross-platform complexity (WebKit, multi-platform build, auto-update) that doesn't align with Tillandsias' multi-platform strategy. With dedicated thin wrappers for macOS (Virtualization.framework) and Windows (WSL2), the Linux tillandsias headless launcher should be the lifecycle source of truth: launch -> resources live -> exit -> full cle

A checked archival task list records work, not proof that every goal-state behavior still works now.

47 of 47 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAgent source of truth completed 2026 04 27Checklist complete79 checked · 0 open

Coding agents (Claude Code, OpenCode, OpenSpec) running inside the forge container today have no curated reference for what tools the forge ships, what the runtime constraints are (Fedora minimal, immutable image layers, ephemeral mutable overlay), or how to use the tools idiomatically. Agents either guess (wrong defaults, deprecated flags, missing batteries

A checked archival task list records work, not proof that every goal-state behavior still works now.

79 of 79 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: expert-serve — one grounded pipeline, two front-endsChecklist complete27 checked · 0 open

Commit 687eb6d57 ("wire local expert system") shipped a facade, and the audit of it is the direct provenance of this change (order 920-pxg6). What the audit found, span by span:

A checked archival task list records work, not proof that every goal-state behavior still works now.

27 of 27 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge bake nix completed 2026 04 27Checklist complete19 checked · 0 open

Per the user's directive: "make sure nix tools are available in the forge. We will recommend in the methodology that any new projects SHOULD be built using nix tools." Plus `forge-cache-architecture` chose nix as the single shared-cache entry point — that only works if nix is actually present in the forge.

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeInference host side pull completed 2026 04 27Checklist complete14 checked · 0 open

Per `~/src/java/local_model_testrun.md`: agents need higher-tier models (T2-T5) for analysis tasks but the in-container ollama pull goes through Squid which `project_squid_ollama_eof.md` documents EOFs hard on big manifests. Plus the user's directive: "Models lazily load on the background automatically on first launch, only the correct sizes for the supporte

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProject bootstrap readme applied 2026 04 27Checklist complete42 checked · 0 open

In-forge agents (OpenCode, Claude, OpenSpec) currently land in a fresh container cold: they spend the first prompt scanning `flake.nix`, `Cargo.toml`, `pubspec.yaml`, `.tillandsias/`, and the OpenSpec tree before producing any user-visible work. Every project owns the perfect surface to short-circuit this — `README.md` — but only if it is treated as the agen

A checked archival task list records work, not proof that every goal-state behavior still works now.

42 of 42 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeShutdown conmon stragglers completed 2026 04 27Checklist complete19 checked · 0 open

After `Quit Tillandsias`, observed `tillandsias-*` containers occasionally remain running, kept alive by their `conmon` processes. `shutdown_all` calls `launcher.stop()` (graceful SIGTERM with 12s timeout, then SIGKILL via `podman kill`) followed by `client.remove_container()` (`podman rm -f`), and an orphan sweep with prefix `tillandsias-` — but it never **

A checked archival task list records work, not proof that every goal-state behavior still works now.

19 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

Other archived changes 104

Open tasks or no checklist were recorded. These records remain part of the source history.

changeAttach here mvpOpen tasks13 checked · 1 open

The tray app detects projects and shows menus, but "Attach Here" doesn't actually do anything yet. The MVP needs to launch a real development environment — a Fedora Minimal container with OpenCode, OpenSpec, and Nix pre-installed — so a user can click one button and start working on any project.

A checked archival task list records work, not proof that every goal-state behavior still works now.

13 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCosign signingOpen tasks15 checked · 5 open

SHA256 checksums (Phase 1) prove that a downloaded file matches what was uploaded, but they don't prove who uploaded it. If an attacker compromises the GitHub Release assets or performs a MITM attack, they can replace both the binary and its checksum. Users have no way to distinguish a legitimate release from a tampered one.

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 20 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRelease pipelineOpen tasks18 checked · 4 open

Tillandsias targets three platforms (Linux, macOS, Windows) and must be distributed as a single download-and-run binary per platform. Without automated release builds, every release requires manual compilation on three operating systems, manual checksum generation, and manual upload to GitHub Releases. This is error-prone, time-consuming, and blocks the rele

A checked archival task list records work, not proof that every goal-state behavior still works now.

18 of 22 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRuntime loggingOpen tasks8 checked · 3 open

The tray app currently has debug-only tracing behind `#[cfg(debug_assertions)]`. In release builds, there's zero observability — container launches, stops, failures, scanner events all happen silently. Developers need structured logs to diagnose issues, and the foundation should support OpenTelemetry for future production telemetry.

A checked archival task list records work, not proof that every goal-state behavior still works now.

8 of 11 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge init and mountOpen tasks7 checked · 2 open

The forge entrypoint installs OpenCode and OpenSpec every run because npm globals go to the read-only Nix store and OpenSpec isn't cached. Also, projects mount at `/home/forge/src` flat instead of `/home/forge/src/<project>/`, breaking OpenCode's status bar which should show `src/<project>:main`.

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 9 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDesktop launchersOpen tasks22 checked · 1 open

Tillandsias targets non-technical users who do not know what a "terminal" is. Today, launching the application requires opening a terminal emulator and running a binary by name, or double-clicking an unmarked executable in a file manager. Neither path is discoverable, and both feel alien to users who expect applications to appear in their operating system's

A checked archival task list records work, not proof that every goal-state behavior still works now.

22 of 23 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeApp cleanup flagsOpen tasks16 checked · 2 open

Disk usage from Tillandsias artifacts accumulates silently. Podman images (forge, macuahuitl), dangling build layers, stopped containers, Nix build outputs, and Cargo registry caches can grow to several gigabytes without any visibility. Users have no way to inspect or reclaim this space without knowing internal paths and commands.

A checked archival task list records work, not proof that every goal-state behavior still works now.

16 of 18 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAppimage desktop integrationOpen tasks0 checked · 10 open

When users download and run the Tillandsias AppImage directly (`./Tillandsias.AppImage`), GNOME shows a generic blue gear icon in the taskbar and dock because there is no `.desktop` file or icon installed on the host. The `install.sh` script handles desktop integration for `--install` users, but AppImage users who simply download and run the binary never exe

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAppimage update flagOpen tasks14 checked · 2 open

The tray app silently checks for updates in the background, but there is no way for a user or script to trigger an update check from the command line. Power users and automation scripts need a way to invoke `tillandsias --update` and get a clear, machine-readable report of what happened: current version, whether an update is available, and whether the update

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDynamic tray iconsOpen tasks0 checked · 24 open

The tray icon is a static PNG set once at startup and never updated. This means users get no ambient feedback about what the application is doing. A build in progress, a broken podman installation, and an idle ready state all look identical. The icon is the only always-visible surface of the app — making it dynamic is the highest-leverage UX improvement poss

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 24 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFedora repo install docsOpen tasks0 checked · 3 open

The README has Fedora COPR instructions using `sudo dnf`, but on Fedora Silverblue (the primary development platform) `dnf` is not available on the host — the immutable base uses `rpm-ostree`. Users on Silverblue need explicit instructions to add the COPR repo and layer the RPM so it auto-updates with system updates.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 3 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeGithub login iconsOpen tasks0 checked · 4 open

The GitHub Login menu items in the Settings submenu are plain text with no visual differentiation. Adding emoji icons gives users an immediate visual cue about the action's nature — the key emoji signals "entering/unlocking" for initial login, and the closed lock signals "re-authenticating" for a refresh when already logged in. This matches the emoji-first v

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeImmutable os installOpen tasks6 checked · 1 open

On Fedora Silverblue (and other immutable/ostree systems like Kinoite and uBlue), the install script currently silently falls through to AppImage only when piped from curl — because `HAS_SUDO=false` gates the entire RPM section. This is the right outcome, but it happens for the wrong reason. If a user ever runs the script interactively on Silverblue with sud

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLinux appimage onlyOpen tasks0 checked · 17 open

Tillandsias currently supports three Linux distribution formats: .deb (via APT repository), .rpm (via COPR and direct download), and AppImage. Maintaining all three creates significant complexity:

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 17 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLocal appimage buildOpen tasks0 checked · 8 open

AppImages can only be built in CI (GitHub Actions on Ubuntu with FUSE). Local development on Fedora Silverblue cannot produce AppImages because Tauri's linuxdeploy bundler requires FUSE, which is unavailable inside the toolbox. Developers need to push to CI and wait for a full release just to test an AppImage locally.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMenu progress uxOpen tasks0 checked · 24 open

The tray menu gives no feedback during image builds or container creation — the user sees a static menu with no indication that work is in progress. On Linux/libappindicator, periodic menu rebuilds to animate a spinner or show elapsed time would cause visible flicker if the user has the menu open. The tray icon (from the dynamic-tray-icons change) provides c

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 24 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMenu ux polishOpen tasks0 checked · 14 open

The project submenu has several rough UX edges: "Attach Here" shows no plant emoji when idle, remains clickable when the container is already running, the Maintenance icon is a generic wrench instead of a garden tool, the per-project container listing duplicates information already conveyed by the "Attach Here" / "Blooming" state, and the project label in th

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeNamed terminalsOpen tasks0 checked · 23 open

Terminal windows launched by "Attach Here" and "Maintenance" show meaningless titles like "ptyxis" or a raw `podman run ...` command string. When a user has multiple projects running simultaneously, there is no way to tell which terminal window belongs to which project. Additionally, clicking "Attach Here" on a project that already has a running container si

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 23 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeOpenspec auto initOpen tasks0 checked · 3 open

When a user launches a Tillandsias environment for the first time, OpenSpec is installed inside the container but `openspec init` is never called for the project. This means OpenCode's OpenSpec commands (e.g., `/opsx:new`, `/opsx:onboard`) fail with "Run `openspec init` first". The init step is missing from the container entrypoint.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 3 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeReadme simplifyOpen tasks0 checked · 8 open

The current Install section uses nested `<details>` blocks to show per-platform options, including APT repository setup, Fedora COPR, Silverblue rpm-ostree layering, RPM/DEB downloads, and an `.app.tar.gz` bundle. This creates visual noise and maintenance surface: three distro-specific package manager flows, two Linux download formats (AppImage + RPM + DEB),

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSvg icon pipelineOpen tasks0 checked · 10 open

The 32 SVG assets at `assets/icons/<genus>/<state>.svg` are currently dead design documentation. The system tray displays a single static PNG (`src-tauri/icons/tray-icon.png`) generated by `scripts/generate-icons.py` as a procedural rosette — it does not use the per-genus, per-lifecycle SVGs at all. This means the visual identity the SVGs represent (8 distin

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTerminal in tauriOpen tasks0 checked · 45 open

Terminals are currently spawned as external processes (ptyxis, gnome-terminal, konsole, xterm) via `open_terminal()` in handlers.rs. Once spawned, the application loses all control. It cannot focus the window when the user clicks "Attach Here" a second time. It cannot detect when the user closes the terminal — only inferring exit indirectly from podman conta

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 45 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTerminal lifecycleOpen tasks0 checked · 30 open

After `terminal-in-tauri` lands, terminals run inside Tauri webview windows that Tillandsias owns. The tray menu has no way to know whether a window is open, so "Attach Here" always launches a new terminal — destroying the user's existing session (open editor, uncommitted history, running processes). There is also no cleanup path: if the user closes the wind

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 30 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeEmoji ux guidelinesOpen tasks0 checked · 22 open

The emoji system is inconsistent: Maintenance terminals show flower emojis (from the genus) instead of tool emojis, breaking the visual link between the menu item and the window title. The core UX principle — every window "plants" its emoji on the menu item that launched it — is undermined when emoji families don't match intent.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 22 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeExpand genus poolOpen tasks0 checked · 13 open

The genus pool has only 8 entries. With terminals now receiving their own genus names (separate from forge containers), a single project can easily consume 2+ genera. Users working on 4-5 projects simultaneously will exhaust the pool and hit `None` from the allocator, leaving new environments unnamed.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 13 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix appimage autoupdateOpen tasks0 checked · 9 open

The AppImage auto-update system is completely broken — neither the Tauri built-in updater nor the `--update` CLI flag can find or apply updates. Five root causes identified through release workflow log analysis and code inspection.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 9 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix build script initOpen tasks4 checked · 1 open

`./build.sh --appimage` uses `podman run --rm` (ephemeral container) so Rust, tauri-cli, and system deps are reinstalled from scratch every build (~10 min). Only the cargo registry was cached. Subsequent builds should be fast.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix container death detectionOpen tasks0 checked · 19 open

Container death is completely invisible. When a `--rm` container dies, the tray never updates because three independent bugs in `events.rs` conspire to suppress all death events. The event stream silently produces zero events, the fallback polling never notices disappearances, and the user sees a permanently "running" ghost entry.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix file permissionsOpen tasks0 checked · 10 open

Files created inside Tillandsias forge containers (via `--userns=keep-id`) can end up with restrictive permissions on the host bind mount. Specifically, files created by container processes under `~/src/<project>/` — such as `.bash-private`, `.opencode/`, `openspec/`, `.claude/` — may have no write permission for the host user, requiring `chmod` or `sudo rm`

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix gh auth setup gitOpen tasks2 checked · 2 open

`git push` inside forge containers prompts for username/password because git doesn't know about gh's OAuth token. The `hosts.yml` with the token is mounted, but git's `credential.helper` is not configured to use `gh`. Running `gh auth setup-git` bridges this gap.

A checked archival task list records work, not proof that every goal-state behavior still works now.

2 of 4 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix root mount and terminalOpen tasks0 checked · 7 open

The root-level "Attach Here" and root terminal (top of tray menu, scoped to the entire `~/src/` watch path) have two bugs:

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix terminal root landing uxOpen tasks1 checked · 1 open

Root terminal welcome message shows "Project: unknown" and "Mount: /home/forge/src/unknown" because `TILLANDSIAS_PROJECT` env var is not passed to the root terminal container. The welcome script (`forge-welcome.sh`) defaults to "unknown" when the var is absent.

A checked archival task list records work, not proof that every goal-state behavior still works now.

1 of 2 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix update no curlOpen tasks8 checked · 2 open

`tillandsias --update` shells out to the system `curl` binary to fetch `latest.json` and download the update archive. Inside an AppImage, this causes a library symbol mismatch:

A checked archival task list records work, not proof that every goal-state behavior still works now.

8 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge build ux polishOpen tasks17 checked · 4 open

When Tillandsias starts and the forge image is absent or stale, the user can click "Attach Here" or "Maintenance" before the build finishes. Those actions fail silently or produce confusing errors because the forge image is not ready yet. Additionally, the build chip always shows "Building forge..." regardless of whether this is a first-time install or a rou

A checked archival task list records work, not proof that every goal-state behavior still works now.

17 of 21 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeGenus named terminalsOpen tasks0 checked · 20 open

Terminal (Maintenance) containers are the only container type that does not use the genus naming convention. They use a fixed name `tillandsias-{project}-terminal` instead of `tillandsias-{project}-{genus}`. This causes three bugs:

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 20 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeGh login no forgeOpen tasks10 checked · 1 open

When a user clicks "GitHub Login" from the tray on a fresh installation, the forge image has not been built yet. The `gh-auth-login.sh` script is extracted from the binary and run directly in a terminal — but it fails immediately:

A checked archival task list records work, not proof that every goal-state behavior still works now.

10 of 11 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMove entrypoint from homeOpen tasks4 checked · 2 open

The forge container's `entrypoint.sh` is placed at `/home/forge/entrypoint.sh` — directly inside the user's home directory. When a user browses their home with `ls ~`, a file manager, or any shell, they see a stray `entrypoint.sh` file that has nothing to do with their work. This is confusing and pollutes the home directory.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changePin nix builder imageOpen tasks0 checked · 2 open

The `nixos/nix:latest` tag can change at any time. A breaking Nix release could silently break the forge image build for all users. Since we already pin Nix inputs via `flake.lock`, pinning the builder image version completes the reproducibility picture.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 2 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeRemote project to launchOpen tasks0 checked · 10 open

When a user clones a remote project, they are expressing intent to work on it immediately. Today, after a successful clone, the user must find the new project in the tray and click "Attach Here" manually. This is unnecessary friction — the clone is the signal that the user wants to start working.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray menu fanoutOpen tasks0 checked · 20 open

The tray menu has grown incrementally and now has too many top-level items separated by dividers. With multiple projects, running environments, build chips, settings, and version info all laid out inline, the menu is long, hard to scan, and requires scrolling on small screens. Every new feature adds yet another divider and item to an already crowded top leve

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 20 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeWebview menus explorationNo checklist0 checked · 0 open

The Settings submenu is currently three levels deep: Settings > GitHub > Remote Projects, and Settings > Seedlings > agent selection. Native tray menus are OS widgets with zero styling capability -- no custom icons, no color, no layout control. Each platform renders them differently. Navigating nested submenus on Linux (libappindicator) is particularly clunk

No task record was present in this archived change.

Read source record ↗

changeClaude api key loginOpen tasks22 checked · 1 open

Claude Code authenticates via an Anthropic API key (`ANTHROPIC_API_KEY`). Today, users who select Claude as their agent must manually export this environment variable or rely on Claude's own interactive login flow each time a container starts. There is no persistent, secure storage for the API key in Tillandsias — unlike the GitHub token which lives in the O

A checked archival task list records work, not proof that every goal-state behavior still works now.

22 of 23 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix forge agent claudeOpen tasks15 checked · 29 open

Three bugs make the forge environment broken or unusable for Claude Code users:

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 44 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Fix Forge Image Persistence on macOSOpen tasks5 checked · 7 open

1. `main.rs` (line 296-297): Calls `forge_image_tag()` which returns `tillandsias-forge:v{CARGO_PKG_VERSION}` (currently `tillandsias-forge:v0.1.81`) 2. `main.rs` (line 297): Calls `forge_client.image_exists(&tag).await` 3. `client.rs` (line 64-70): Runs `podman image exists <tag>` and checks exit code 4. If the image is absent, triggers `run_build_image_scr

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix update artifactsOpen tasks0 checked · 8 open

The AppImage update system has two misaligned assumptions introduced during the initial auto-updater implementation.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeConfig driven launchOpen tasks0 checked · 32 open

The Rust code that builds `podman run` arguments is duplicated across three locations with subtle differences:

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 32 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix forge launch regressionOpen tasks0 checked · 22 open

The current forge entrypoint (`images/default/entrypoint.sh`) fails to launch Claude Code correctly inside containers. The monolithic entrypoint handles all agent types (OpenCode, Claude, unknown) in a single script, and the Claude Code install path (`npm install -g --prefix`) does not reliably produce a working `claude` binary. When `install_claude()` fails

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 22 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix ux string bugsOpen tasks0 checked · 24 open

A thorough audit of all user-facing strings across the Rust tray app, CLI commands, shell scripts, and installer reveals multiple bugs and inconsistencies. The most severe is a word-doubling bug where tray menu build chips display "Building Building Forge..." because `build_chip_label()` prepends "Building" to image names that already contain the word "Build

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 24 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeI18n infrastructureOpen tasks0 checked · 34 open

Tillandsias serves Average Joe (AJ), who may not speak English. The project is authored by a bilingual developer (English/Spanish) and targets a global audience. Currently, every user-facing string is hardcoded in English across Rust source files and shell scripts. There is no mechanism to localize the application.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 34 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeModular entrypointsOpen tasks0 checked · 27 open

The current forge image has a single monolithic entrypoint (`images/default/entrypoint.sh`, 144 lines) that handles every container type: OpenCode forges, Claude forges, maintenance terminals, and (eventually) web servers. This is a growing source of fragility:

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 27 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeWeb container supportOpen tasks0 checked · 25 open

The `tillandsias-web` image was designed and built (see archived `2026-03-22-web-image` change) but is not yet wired into the tray app or CLI. A user cannot launch a web container from the tray menu or via `tillandsias --web <path>`. The image exists in `flake.nix`, the entrypoint exists at `images/web/entrypoint.sh`, but there is no UI surface to use it.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 25 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCli welcome help redesignOpen tasks0 checked · 14 open

Tillandsias currently has a minimal CLI interface: a static `USAGE` string in `cli.rs` with a flat list of flags. As the application gains accountability windows (`--log-secret-management`, `--log-image-management`, `--log-update-cycle`), per-module log control (`--log=...`), and maintenance commands (`init`, `--stats`, `--clean`, `--update`), the help text

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeClickable trace indexOpen tasks0 checked · 15 open

`@trace spec:<name>` comments in source files create a navigational chain from code back to the specification that justified a decision. But the chain breaks on GitHub: code comments are not hyperlinks, so a reader sees `@trace spec:podman-orchestration` and has no direct path to the spec file.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLogging accountability frameworkOpen tasks0 checked · 26 open

Tillandsias currently has a single-layer logging system: `tracing` with an `EnvFilter` that reads from `TILLANDSIAS_LOG` or `RUST_LOG`, defaulting to `tillandsias=info`. There is no CLI flag for controlling log levels, no per-module granularity from the user's perspective, and no way to inspect specific subsystems (like secret handling or container lifecycle

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 26 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLogging cheatsheetsOpen tasks0 checked · 9 open

The `logging-accountability-framework` and `secret-rotation-tokens` changes create a system where runtime log output links to spec names and cheatsheet paths. But those cheatsheet files do not exist yet. Without them, the accountability window's "Cheatsheet:" lines are dead references.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 9 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSecret rotation tokensOpen tasks0 checked · 32 open

Today, Tillandsias injects GitHub credentials into containers by writing the user's full OAuth token to `~/.cache/tillandsias/secrets/gh/hosts.yml` and bind-mounting it read-only at `/home/forge/.config/gh/`. This token has `repo`, `read:org`, and `gist` scopes -- full read/write access to every repository the user owns, plus organization data.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 32 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray icon lifecycleOpen tasks0 checked · 10 open

The tray icon currently uses 3 states (`Base`, `Building`, `Decay`) that map to Ionantha `bud.svg`, `bloom.svg`, and `dried.svg`. This mapping is incomplete and semantically wrong: the "base" state uses a bud icon (which suggests something new and growing), while it should represent a healthy, mature plant at rest. The 4 SVG lifecycle stages per genus (pup,

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 10 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray icon resize visibilityOpen tasks17 checked · 2 open

The 32 tray icon SVGs (8 genera x 4 lifecycle states) have a 64x64 viewBox but the plant graphics are tiny within it. At typical system tray sizes (22-32px), the plants are barely visible:

A checked archival task list records work, not proof that every goal-state behavior still works now.

17 of 19 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeUpdate audit loggingOpen tasks18 checked · 4 open

When `--update` runs or the background auto-updater applies a patch, there is no record of what happened. Users and developers have no way to confirm whether an update succeeded, what version was installed, or when the last check occurred. After the process exits, all context is gone. A persistent audit log closes this gap — it's a plain text file that accum

A checked archival task list records work, not proof that every goal-state behavior still works now.

18 of 22 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAdd german i18nNo checklist0 checked · 0 open

2026-04-03-add-german-i18n

No task record was present in this archived change.

Read source record ↗

changeEnclave proxy networkOpen tasks0 checked · 39 open

Coding containers currently have unrestricted network access and receive GitHub tokens via bind mounts. AI agents running inside can read secrets and exfiltrate data to any endpoint. This is the most critical security gap in the architecture. Phase 1 lays the foundation by creating an internal podman network (enclave) and a caching proxy container that becom

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 39 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeEnclave telemetry polishOpen tasks0 checked · 15 open

Phases 1-4 built the full enclave architecture: proxy, git mirror, offline forge, and inference. The accountability windows (`--log-proxy`, `--log-enclave`, `--log-git`) were added but need real telemetry events populated throughout the codebase. Documentation needs final updates to reflect the complete architecture.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge offline isolationOpen tasks0 checked · 28 open

Phases 1-2 established the enclave network, proxy, and git mirror. But forge containers still have credential mounts (GitHub token, hosts.yml, Claude dir) and the project directory is still mounted directly. Phase 3 completes the isolation: forge containers become fully offline with zero credentials. Code comes exclusively from the git mirror, packages throu

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 28 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeGit mirror serviceOpen tasks0 checked · 35 open

Phase 1 introduced the enclave network and proxy. But credentials (GitHub token, hosts.yml) are still mounted directly into forge containers. The git mirror service eliminates this by isolating all git/GitHub operations into a dedicated container that owns the credentials. Forge containers clone from the mirror — they never touch credentials or the remote.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 35 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeInference containerOpen tasks0 checked · 15 open

The enclave architecture isolates forge containers from the network and credentials. For AI-assisted development with local models, we need a dedicated inference container running ollama. This container is offline (enclave-only), shares models via a persistent volume, and requests model downloads through the host app.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAdd spec traceability refsOpen tasks36 checked · 3 open

No way to trace backwards from code or logs to the spec that justified a decision. Troubleshooting agents guess which spec applies. When code is modified, no way to know which specs are impacted. Adding lightweight `@trace` references and structured `spec` log fields completes the observability chain: logs → code → spec → ground truth.

A checked archival task list records work, not proof that every goal-state behavior still works now.

36 of 39 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAppimage icon polishOpen tasks7 checked · 1 open

The AppImage shows a generic blue gear icon in file managers instead of our tillandsia. While the tray icon is now properly embedded (v0.1.37), the AppImage file itself and its desktop integration need polish to show the correct icon everywhere.

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 8 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAudit remediationOpen tasks18 checked · 2 open

Full spec-vs-code audit revealed 5 critical divergences, 9 warnings, 7 orphaned code areas, and 6 forgotten specs. Trivial fixes applied immediately; this change tracks the remaining non-trivial items that need design decisions.

A checked archival task list records work, not proof that every goal-state behavior still works now.

18 of 20 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSecrets architectureOpen tasks15 checked · 2 open

Tillandsias forge containers need access to credentials (GitHub tokens, SSH keys, git identity) to perform useful work — cloning private repos, pushing commits, authenticating with APIs. Today there is no defined strategy for how secrets enter containers, persist between runs, or stay protected from the AI agent and host-level exposure.

A checked archival task list records work, not proof that every goal-state behavior still works now.

15 of 17 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix shell quote env varsOpen tasks4 checked · 2 open

Container launches from the tray fail on macOS 26+ because the `TILLANDSIAS_HOST_OS` env var value contains a space (`macOS 26.4`). When podman args are joined into a shell command string for `open_terminal()`, the space splits the value — podman interprets `26.4` as a separate image name argument and tries to pull `docker.io/library/26.4:latest`.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeLayered tools overlayOpen tasks18 checked · 4 open

Every forge container launch downloads and installs OpenCode and OpenSpec from the internet before the user can begin work. On the Claude entrypoint, this means `npm install -g @anthropic-ai/claude-code` (large npm tree) plus `npm install -g @fission-ai/openspec`. On the OpenCode entrypoint, this means `curl | bash` for OpenCode plus the same npm install for

A checked archival task list records work, not proof that every goal-state behavior still works now.

18 of 22 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeAdd opencode web agentOpen tasks25 checked · 4 open

Terminal-based session launch is brittle across platforms: the recent Windows fix that suppressed stray console windows also disabled the Linux terminal path, leaving users without a working default. Rather than chase terminal emulator quirks across Linux, macOS, and Windows, we pivot to OpenCode's built-in web server and display it in an embedded Tauri webv

A checked archival task list records work, not proof that every goal-state behavior still works now.

25 of 29 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray aware cli and webview lifecycleOpen tasks0 checked · 15 open

The v0.1.159.x attach flow works but several UX edges jar the user experience:

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeDeprecate tauri webview for native browserOpen tasks1 checked · 23 open

Every problem we've hit with OpenCode Web in the last cycle came from the Tauri/WebKit2GTK webview layer:

A checked archival task list records work, not proof that every goal-state behavior still works now.

1 of 24 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix tray exit webview lifecycleOpen tasks14 checked · 7 open

Regression discovered after `tray-aware-cli-and-webview-lifecycle` landed: the tray quit path and the webview close path both leave containers running and the enclave network attached, contrary to the specs already in `openspec/specs/app-lifecycle/` and `openspec/specs/opencode-web-session/`.

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 21 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeMirror to host auto syncOpen tasks21 checked · 7 open

Right now the enclave mirror is the authoritative recipient of every forge push — commits land there first, then the git-service container's post-receive + startup retry-push forwards them to GitHub. But the user's host working copy at `<watch_path>/<project>` never hears about those commits unless the user manually `git pull`s. Side effects:

A checked archival task list records work, not proof that every goal-state behavior still works now.

21 of 28 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeOpencode web config isolationOpen tasks7 checked · 9 open

Live-session diagnosis of OpenCode Web running inside a forge container showed multiple violations of the spec's per-container isolation contract. The first draft of this change over-corrected by restricting providers to ollama only; the user revised the direction to **additive**: preserve every default OpenCode capability, add ollama + enclave-specific conf

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 16 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeOpencode web proxy transparentOpen tasks11 checked · 4 open

OpenCode Web hangs after one or two prompts inside a forge container. Proxy logs captured from a live session show three independent failure modes all traced back to proxy configuration:

A checked archival task list records work, not proof that every goal-state behavior still works now.

11 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSimplified tray uxOpen tasks25 checked · 14 open

The current tray menu has accreted complexity that no longer matches how Tillandsias is actually used:

A checked archival task list records work, not proof that every goal-state behavior still works now.

25 of 39 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSubdomain routing via reverse proxyOpen tasks17 checked · 5 open

When a coding agent inside the forge launches a dev server (e.g., `flutter run -d web-server`, `vite`, `next dev`, `opencode serve`), it prints something like `Server running at http://localhost:8080`. That URL is meaningless to the human:

A checked archival task list records work, not proof that every goal-state behavior still works now.

17 of 22 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray responsiveness and startup gating supersededOpen tasks1 checked · 28 open

Live issue report (2026-04-24, v0.1.160.212): after a cold launch, the tray became fully unresponsive. Root cause: a `build-tools-overlay.sh` subprocess spawned a podman container to compile the forge tools overlay, and the entire event loop waited on its completion. Symptoms:

A checked archival task list records work, not proof that every goal-state behavior still works now.

1 of 29 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeappimage-builder-source-slimOpen tasks0 checked · 12 open

The AppImage build script (`build.sh --install`) copies the entire workspace into the Ubuntu builder container before invoking `cargo tauri build`. On this developer's machine the copy step takes ~5 minutes, dominated by a **47 GB `target/` directory** and a **1.5 GB `.git/` directory** that the builder never reads — its own `target/` is built fresh from sou

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCheatsheet tooling and mcpOpen tasks0 checked · 24 open

`cheatsheet-shortcomings.md` enumerated 10 first-hand gaps in the v2 cheatsheet system. 7 of them are tooling absences:

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 24 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeFix router loopback portOpen tasks0 checked · 12 open

The router container is published at `127.0.0.1:80:80` (`src-tauri/src/handlers.rs:902`). Rootless podman cannot bind ports below `net.ipv4.ip_unprivileged_port_start` (default `1024` on Fedora and most distros), so the router's port-80 publish silently fails when the tray runs as a non-privileged user. The browser opens `http://<project>.opencode.localhost/

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge cache architectureOpen tasks0 checked · 26 open

The forge container today exports `CARGO_HOME=~/.cache/tillandsias/cargo`, `GOPATH=~/.cache/tillandsias/go`, etc. — but the directory those env vars point at is **never bind-mounted**. Every container restart re-downloads every package the agent installs. Maven, Gradle, Flutter pub-cache, yarn, pnpm, and uv don't even have env vars set, so they default to ep

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 26 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge environment discoverabilityNo checklist0 checked · 0 open

Per `~/src/java/ENVIRONMENT_REPORT.md`: a model attempting to use the forge concluded "Java/JDK: Not installed in PATH … Maven Central URLs return 404/403 … RxJava JAR must be committed to repository." The forge actually ships `java-21-openjdk-devel`, `maven`, `gradle`, `flutter`, `cargo`, `rustup`, `go`, `python3`, `nodejs`, `pipx`, and 30+ utilities — but

No task record was present in this archived change.

Read source record ↗

changeForge headless browsersOpen tasks0 checked · 7 open

The forge image today ships no browsers. Agents trying to run Selenium / Playwright / WebDriver tests inside the forge fail at the first `chromedriver: program not found` step. Per `cheatsheets/test/selenium.md` (DRAFT) and `cheatsheets/test/playwright.md` (DRAFT) the workaround was either per-project install (heavy) or a sidecar container — both of which br

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeHost chromium on demandOpen tasks0 checked · 38 open

Per the user's directive: "include its installer in userspace with our curl installer, and assume from the binary that it is installed. Our installer shall remain tiny and free of dependencies." Plus: "Use some ephemeral --user-dir [...] incognito mode and some random empty user dir only if required to override default. Our browsers should also run in isolat

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 38 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeSubdomain naming flipOpen tasks0 checked · 12 open

Today's URL pattern `<project>.opencode.localhost:8080` puts the project name first and the service second. As soon as Tillandsias adds a second service per project (`web` for Flutter dev server, `dashboard` for an agent UI, `www` for a static preview), the URL space becomes hard to scan: `java.opencode.localhost`, `python.opencode.localhost`, `java.web.loca

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 12 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray host control socketOpen tasks30 checked · 13 open

Multiple in-flight changes need an out-of-band channel between tray-side processes and tray-spawned containers (router for OTP transport per `opencode-web-session-otp`; future host MCP server for browser-open allowlist; future cross-process events). Each ad-hoc design adds attack surface. One reviewable Unix-socket control plane is the smallest defensible sh

A checked archival task list records work, not proof that every goal-state behavior still works now.

30 of 43 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray no disabled itemsOpen tasks0 checked · 40 open

The tray menu's current rendering of the five-stage state machine fills the menu with disabled placeholder items (`Building [...]`, `Ready`, `Sign in to GitHub` when not in NoAuth, `(GitHub unreachable…)`, `No local projects`, `No remote projects`, separate `version` and `— by Tlatoāni` lines). Users read disabled rows as broken UI. The `Include remote` chec

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 40 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeTray progress and icon statesOpen tasks0 checked · 11 open

Today's status chip is a comma-joined fragment list (`Building Forge … · GitHub unreachable …`). Users at launch see only `Quit Tillandsias` + a disabled signature line for several seconds while infrastructure spins up — the menu reads "broken" until the chip first appears. Then individual fragments come and go without any visual sense of cumulative progress

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 11 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCheatsheet source layerOpen tasks25 checked · 1 open

Tillandsias's cheatsheet provenance discipline ALREADY mandates citing high-authority source URLs. But there's no enforcement that the cited URL was actually fetched at authoring time vs. plausibly invented. The recent `cheatsheets/runtime/local-inference.md` was a textbook example: three plausible URLs cited, none actually fetched, and one of the documented

A checked archival task list records work, not proof that every goal-state behavior still works now.

25 of 26 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeExternal logs layerOpen tasks0 checked · 32 open

A forge container that launches sibling service containers (e.g. a postgres + nginx pair to test the production stack the agent is editing) currently has **no observability over those siblings**. The existing per-container `/var/log/tillandsias/` mount is single-tier and per-container — sibling forges cannot read each other's logs, and there is no boundary b

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 32 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge hot cold splitOpen tasks0 checked · 35 open

Tillandsias's forge container today runs every read off the host's overlayfs storage driver (`~/.local/share/containers/storage/`). That includes paths the agent reads dozens of times per prompt — `/opt/cheatsheets/`, `/home/forge/src/<project>/` — alongside paths the agent writes once and reads at most once (build artefacts, downloaded archives, log noise).

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 35 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge hot cold split tmpfs laneOpen tasks3 checked · 4 open

The `cheatsheets-license-tiered` design introduces a `pull-on-demand` cache at `~/.cache/tillandsias/cheatsheets-pulled/<project>/` with a tiered RAMDISK budget (64 MB modest / 128 MB normal / 1024 MB plentiful) that auto-spills to disk past the cap. This pattern is neither pure HOT (kernel tmpfs with hard ENOSPC) nor pure COLD (disk-only) — it is a third pa

A checked archival task list records work, not proof that every goal-state behavior still works now.

3 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeHost browser mcpOpen tasks32 checked · 33 open

Per the user's directive: agents inside forge containers need to drive browser windows for web-app testing / inspection / agentic control. The mechanism: an MCP server on the host exposing `browser.open` / `browser.click` / `browser.type` / `browser.screenshot` etc., backed by CDP against the bundled Chromium.

A checked archival task list records work, not proof that every goal-state behavior still works now.

32 of 65 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeCodex tray launcherOpen tasks7 checked · 24 open

Codex is a powerful agent for code analysis, understanding, and generation. Currently, users access it only through secondary flows. Adding 🏗 Codex as a first-class tray menu button (alongside Claude and OpenCode) provides direct access and reinforces the agent-centric design philosophy of the platform. This brings Codex to parity with other agents in the UI

A checked archival task list records work, not proof that every goal-state behavior still works now.

7 of 31 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeend-to-end-diagnostics-cli Change ProposalOpen tasks0 checked · 49 open

Current initialization and debugging workflows lack visibility into container lifecycle and health. Users running `tillandsias --init` cannot verify all images build (including new browser isolation containers), and there's no straightforward way to inspect live logs for troubleshooting. We need end-to-end observability: a predictable initialization flow tha

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 49 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeProposal: Tray Minimal UXOpen tasks26 checked · 1 open

1. **Launch state**: Show only: - `<Checklist> Verifying environment ...` - Divider - `Version + Attribution` - `Quit Tillandsias`

A checked archival task list records work, not proof that every goal-state behavior still works now.

26 of 27 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeChromium browser isolationNo checklist0 checked · 0 open

Tillandsias' tray app currently lacks hardened browser isolation for displaying project UIs and agent web sessions. Browser windows inherit host credentials, network access, and filesystem privileges — creating attack surface and data leakage vectors. We need a two-tier containerized Chromium architecture that isolates browser processes completely from the h

No task record was present in this archived change.

Read source record ↗

changeCheatsheets license tieredOpen tasks58 checked · 9 open

The just-archived `cheatsheet-source-layer` mandates verbatim source storage in the repo, which (a) bloats git for material we have no clear redistribution rights to, (b) creates a license-compliance trap for any source NOT explicitly allowlisted, and (c) makes refresh a manual chore that drifts over time. We need a model where license-friendly material is b

A checked archival task list records work, not proof that every goal-state behavior still works now.

58 of 67 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeForge build observability and diagnosticsOpen tasks0 checked · 58 open

Forge image builds take 2+ hours due to re-downloading ~6.5GB of packages on every build, with no visibility into which steps are slowest or largest. Developers can't identify optimization opportunities or diagnose failures in the build or runtime. Container health issues surface only after minutes of silent operation, requiring manual inspection.

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 58 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

changeWindows etw logging completed 2026 04 27Open tasks0 checked · 27 open

Tillandsias on Windows runs as a system tray application with no visible console window. When something goes wrong — a podman machine fails to start, a container launch errors out, or a forge image build fails — the user has no way to discover the problem through the standard Windows diagnostic tool: Event Viewer. All log output goes to a file in `%LOCALAPPD

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 27 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

Website changes 13

Local site contracts and their task records, including this page’s change.

siteadd-container-frameworkOpen tasks0 checked · 15 open

tillandsias.org hosts a mostly-static HTML website (the apex `tillandsias.org` is the canonical host — the shortest URL for users — and `www.tillandsias.org` redirects to it) on infrastructure that may live anywhere in the world, on a host whose public IP can change. The site must be served over HTTPS with automatic certificate issuance and renewal, and the

A checked archival task list records work, not proof that every goal-state behavior still works now.

0 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteadd-crdt-and-lamport-pagesOpen tasks5 checked · 1 open

The site explains CentiColons, the measurement, but says nothing about the other pillar the measurement stands on: the shared plan is an append-only pile of evidence that many agents on many hosts write to without conflicts, folded into one readable view. A reader who asks "how can that many agents share one plan?" currently has no page to be sent to, and th

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteadd-level-page-specsOpen tasks25 checked · 7 open

The site's five explanation pages (`docs/matrix/level-1..5-*.md`, rendered into the single `var/html/index.html`) are the project's front door, and each is a long-lived contract of its own: a distinct audience, a distinct honesty frame (GREEN/RED/PATH), and a set of claims that must stay true against the release tag the level pins. Today nothing declares wha

A checked archival task list records work, not proof that every goal-state behavior still works now.

25 of 32 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteExplain repository refinement as an empirical modelChecklist complete6 checked · 0 open

Add a presentation slide between the uncertainty and CRDT slides, a shared figure, and matched explanations at all five audience levels. The new material presents a repository-plus-prompt as selected model context, a proposed patch as a refinement, and checked acceptance as the transition into the shared record.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 6 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

sitei-want-it-quickstartOpen tasks30 checked · 5 open

The **I want it!** page (`#install`, landed in d73d0e5) shows the three one-line installers and, once screenshots exist, a desktop gallery. It does not show what happens *after* the line is pasted. The owner's ask is to make the smallness of the ramp-up visible rather than to document it: after a curl install, someone points a phone at a QR code in the termi

A checked archival task list records work, not proof that every goal-state behavior still works now.

30 of 35 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteSeparate a measurable residual from a convergence guaranteeOpen tasks6 checked · 1 open

Scoped follow-up to the automated stable v56.9.27.2 audit (website b3f3091). The operator requested a challenge to Level 5's missing metric, using the Lua runtime and CentiColons. Keep the stable pin and all unrelated sections. This proposal records website deltas, not authority to change runtime policy. Leave it unarchived for operator review.

A checked archival task list records work, not proof that every goal-state behavior still works now.

6 of 7 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

sitelevel-5-stable-pin-deltasOpen tasks11 checked · 2 open

Level 5 (`docs/matrix/level-5-phd.md`) stays pinned to the stable release v56.9.2.1. Its mathematics and its RED flags are accurate at that pin, and the page moves only in tiny, individually justified deltas. The 2026-09-05 re-verification against the newest daily (v56.9.5.1) found: one statement that was false even at the stable pin (an associativity test t

A checked archival task list records work, not proof that every goal-state behavior still works now.

11 of 13 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteRefresh level 5 citations for stable v56.10.9.1Open tasks4 checked · 1 open

Stable `v56.10.9.1` changed eight of the files level 5 cites: `crates/tillandsias-plan/src/fragments.rs`, `crates/tillandsias-plan/src/lua_predicate.rs`, `methodology/convergence.yaml`, `methodology/proximity.yaml`, `methodology/versioning.yaml`, `scripts/check-centicolon-ratchet.sh`, `scripts/check-requirement-ids.sh` and `scripts/local-ci.sh`. Sixteen foot

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteRefresh level 5 against stable v56.9.12.2Checklist complete3 checked · 0 open

Preserve the argument and manual additions while applying only the individually justified deltas below. Tests on a committed model are not a theorem about live validators; withdrawal of a mathematical citation is an improvement in accuracy.

A checked archival task list records work, not proof that every goal-state behavior still works now.

3 of 3 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteRefresh level 5 citations for stable v56.9.27.2Open tasks4 checked · 1 open

Level 5's argument remains supported at the new stable release. Move its pin from `v56.9.21.1` to `v56.9.27.2` only after the checked build resolves every footnote. The page edits authorized by this change are the six citation range corrections below and one added paragraph; all other prose and every quoted line stay as they are.

A checked archival task list records work, not proof that every goal-state behavior still works now.

4 of 5 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteA complete, inspectable website ledger and system mapOpen tasks14 checked · 1 open

Live progress currently renders only findings and gives each finding a short face. Readers cannot discover the canonical runtime specs, completed archived change checklists, or the evidence behind a finding from the page. Slide 6 deliberately compresses architecture into a presentation frame; a security reviewer needs the fuller target topology, its current

A checked archival task list records work, not proof that every goal-state behavior still works now.

14 of 15 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteReader-first claimsOpen tasks13 checked · 1 open

The explanation pages were written while the site's own rules were being worked out, and the rules leaked onto the pages. A five-year-old's page told its reader about a vocabulary rule the product's screens obey and about a machine that enforces it. The power-user page ended on the project's CI budget and "a standing obligation to run the local gate". Footno

A checked archival task list records work, not proof that every goal-state behavior still works now.

13 of 14 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

siteslides-presentationOpen tasks5 checked · 13 open

The site tells the Tillandsias story in five written levels, but there is no way to *present* it: no deck, no walk-through, nothing to stand in front of a room with. A live talk needs a fourth page — **Slides** — reachable from the same menu, carrying a short deck that tells the project's story in a handful of slides: a **containerized workflow**, **Linux se

A checked archival task list records work, not proof that every goal-state behavior still works now.

5 of 18 recorded tasks are checked; the full checklist is in the source record.

Read source record ↗

...wait, WHAT?!

How measuring chess pawns inspired how we measure software convergence.

Chess engines like Stockfish combine bounded search with an evaluation of promising positions. They do not solve the whole game from move one. The familiar centipawn label is now a calibrated scale: Stockfish’s official WDL model relates evaluation and remaining material to self-play outcomes under its test conditions. It is not a literal pawn count, a monotonically improving score, or a guarantee of victory.

Software engineering borrows the useful idea of an explicit evaluation, not chess’s outcome model. Tests provide evidence about declared behavior; a score can make remaining obligations visible. Measuring that remainder is one task. Ensuring accepted changes do not regress is another. Guaranteeing that the remainder eventually reaches zero additionally requires progress.

CentiColons (cc) are evidence-based accounting for declared obligations. The intended contract binds each credited assertion to the obligation, implementation inputs and required platform it actually tested. A green test file alone is not enough. Under a fixed scope and scoring policy, the residual (R) records what has not reached its evidence bar. Zero means those declared bars are met, not that every possible defect is absent.

At stable v56.10.20.1: Lua extracts scenario/requirement obligations and grades recorded positive-test results. The grader still has assertion, implementation-provenance and platform-attribution gaps; its residual is a raw count, not the full weighted policy below. The regression check is advisory, not enforced. Lua makes the metric executable; it does not by itself complete the methodology’s guarantee.

What a CentiColon counts

The following is a simplified binary weighted model, not a claim that the entire policy is shipped. Its base weights come from the methodology, which also declares modifiers, partial evidence credits, caps and penalties omitted from this example. Every auditable thing the project commits to—a must requirement, a systemic invariant, a positive or negative litmus signal, a runtime trace, a provenance binding, an environment assumption—enters the model as a named obligation with a stable identifier and a weight. The weights are declared policy rather than measurement: a must requirement is worth 100, a systemic invariant 120, a passing positive litmus 80, a passing negative litmus 100, a runtime trace 60, a should requirement 40. Add up every weight in the fixed scope and you have the budget. New scope or changed policy starts a different comparison; ordinary evidence improvement does not change that budget.

In this binary model, an obligation whose applicable evidence has reached its bar contributes its weight to the earned column; one that has not contributes nothing. Three numbers come out, and they are always reported together:

  • earned — the weights that reached their bar;
  • budget — every weight the spec declared;
  • residual — budget − earned, the distance still to travel.

This example has one property worth saying out loud: closing an obligation moves the score by exactly that obligation’s weight, and the residual by exactly the same amount. Nothing is averaged, smoothed or estimated. Close the 80-point positive litmus and the score moves 80 and the residual moves 80, and a reader can check it by hand. But a rising total can hide a lost obligation behind another gain. Non-regression therefore compares each retained obligation, with the same policy and applicable evidence, not just the total. That accounting rule is not a property of Stockfish’s evaluations.

A worked example, with declared weights

Take a five-obligation example spec, invented for this page: a Submit button, an invariant about how its payload is serialised, a positive litmus, a negative litmus, and a runtime trace. All declared, none yet tested, the budget is 460 and the earned is 0. Now play the position.

abcd4321NPPPNb1–c3 A POSITIONAL MOVE · one quiet developing step +0.18 → +0.42 +24 centipawns · the engine’s own unit THE SAME SHAPE OF MOVE, IN THE LEDGER 0/460 → 80/460 one positive litmus reaches its bar → +80 cc residual 460 → 380 · denominator unchanged
A positional gain, counted twice. Left: files a–d, ranks 1–4, Nb1–c3. The engine’s number rises by 24 centipawns; the ledger’s number rises by 80 CentiColons because one positive litmus reaches its bar. The example spec is the five-obligation one the page works through; the board is a fragment, not a game.

A quiet move. One positive litmus goes green and nothing else happens: no material changes hands, no feature ships. In chess terms it is Nb1–c3 — the piece simply got to a better square, and the number improved because the position improved. The score goes 0 → 80 of 460, the residual goes 460 → 380, and the budget does not move, because closing a claim is not deleting one.

defgh8765KRNNf7+ A TACTICAL MOVE · one blow that wins material +0.35 → +5.40 +505 centipawns · the knight forks king and rook THE SAME SHAPE OF MOVE, IN THE LEDGER 80/460 → 340/460 one change closes three obligations → +260 cc residual 380 → 120 · denominator unchanged
A tactical gain, counted twice. Left: files d–h, ranks 8–5, Nf7+, with the two dashed lines showing what the knight attacks on arrival. The fork is worth 505 centipawns; the same change closes three obligations and is worth 260 CentiColons. The example spec is the five-obligation one the page works through.

A combination. One change ships the button end to end: the requirement is positively tested, its negative case passes, and a runtime trace records the click. That is the knight fork — three things fall at once, and the number says so by the size of the jump. 80 → 340 of 460, residual 380 → 120, +260 in a single move. Notice what the two moves share: the size of the jump is exactly the declared weight of what closed. An engine’s number moves because the position changed; this one moves because evidence arrived, and by an amount somebody wrote down in advance rather than one an evaluation function invented on the spot.

From a sentence in a spec to a button on the screen

Which raises the only question that matters: what does it mean for “the Submit button stays disabled until the form is valid” to be closed? It means there is an arrow from that sentence to something that can actually be executed, and that applicable execution evidence was recorded. The assertion must discriminate the intended behavior; recording an unrelated pass cannot close it. The arrow is the validator, and it is the same shape every time: take the obligation the spec names, take the element the spec is about, and run the check that decides whether the clause holds.

spec: the Submit button staysdisabled until the form is validvalidate(spec, form.button())form.button()enabled ⇆ disabled+100 ccmust requirementspec: a duplicate submit isrefused while one is in flightvalidate(spec, form.button())form.button()second click ignored+100 ccnegative litmusspec: an accepted click isrecorded as a runtime tracevalidate(spec, form.button())trace: submit@clickseen at runtime+60 ccruntime trace three arrows, one change → +260 cc, residual 380 → 120 the denominator moves only when the obligation set changes, not when one closes
The arrow is the validator. Each row is one obligation of the five-obligation example spec: a clause on the left, the thing that can actually be executed in the middle, and the CentiColons the ledger gains when that execution is recorded. The three rows together are the tactical move above: +260 cc, residual 380 to 120.

Each row is a clause, an executable witness, and a price. The three rows are three different kinds of evidence for three different clauses — a must requirement at 100, a negative litmus at 100, a runtime trace at 60 — landing in one ledger, which is why the tactical move above is worth +260 and not an average of three opinions.

The proposed submetrics also cover work outside feature behavior. Systems engineering does not live in user-facing specifications alone. A large share of the real work is systemic invariants — deterministic JSON sorting, sandboxed execution, no fragile shell pipes — and transient platform preconditions, the Darwin bash quirks, the Windows CRLF line endings, the container tmpfs bounds. If the metric only measured feature specs, all of that would read as zero progress or be omitted from the evidence contract. Internal invariants can live in specifications too. The proposed common model treats a scenario, invariant and platform precondition as a named obligation with applicable evidence and a bar, without counting the same obligation twice. The ./plan ledger links a transient fix to the durable obligation and test that prevent recurrence; closing a work item is not itself correctness credit. Stable Lua extraction lists invariants separately, but the grading path does not yet consume them. Cacheable predicates restrict observation capabilities; observing predicates acquire external evidence. Neither label proves an assertion adequate or its cost negligible.

What would make the reduction monotonic?

Fix the obligation set, weights and evidence policy; bind results to the exact assertions, inputs and required platforms; keep a durable accepted baseline; and preserve every retained obligation’s credit. Under those conditions the residual cannot increase across accepted implementation changes. A new counterexample must still invalidate bad credit: honest measurement can rise when we learn that an earlier claim was wrong. Retractions are disclosed breaks in that monotone comparison, not exceptions silently counted as non-regression. New scope and corrected knowledge must remain visible.

Even perfect non-regression permits a plateau. To guarantee eventual zero, require a further progress premise: while a fixed nonnegative integer residual is positive, it decreases by at least one within every next K cycles. Then completion takes at most K × R0 cycles. That is a conditional bound, not a guarantee delivered by the current runtime. Level 5 states the assumptions and the remaining evidence gap.

What the number does not say

A number this clean is exactly the number people will over-read, so here are the four things it does not carry:

  • It is a ranking, not a probability. 340/460 is not a 74% chance the software is correct, and two scores cannot be combined by Bayes. It orders states; it does not measure belief.
  • It answers the question it was asked. Of what you declared, how much is closed. A requirement nobody wrote down is invisible to it, not free.
  • Effort is not in it. A hard obligation and an easy one are worth what their weights say they are worth. The score records closure, never the cost of getting there.
  • A percentage can be improved by throwing obligations away. The weighted Rust scorer reports a broken comparison regime when an obligation is tombstoned; removing an obligation changes the budget and may remove earned credit. This comparison warning prevents reading deletion as progress. The separate Lua advisory reports retirements without refusing the run.

The worked arithmetic is deliberately simpler than the full declared policy, and the shipped measurements are narrower still. Level 5 separates those three layers. CentiColons help operationalize the promise: they make declared evidence debt inspectable. They do not yet complete an enforced non-regression guarantee, and a measurement alone cannot supply the strict-progress premise of a termination proof.

Where the score stands at v56.10.20.1

100 of 990 obligations were still open at the last test run that reported a score, on 9 August 2026. Between runs the score went up as well as down, and no run has reported one since. 595 shell scripts are still to be rewritten in Lua, down from 677 when the count began.

How this section has improved
  • Its links to the source, and the summary that closes it, now update with each release.
  • The page explained the scoring idea with a chess analogy and a worked example.
  • It now separates what the score measures from what the score can promise. An earlier version claimed too much.

CRDT ...what?

Why many agents on many machines can all write to one shared plan and never have to argue about it.

Three friends and one notebook

Imagine three friends keeping one notebook about a shared garden. There is only one notebook, so they pass it around, and whoever has it writes. Then two of them go travelling, each takes a photocopy, and for a month all three write in their own copy. When they meet again they hold three notebooks that disagree.

Now the trouble starts. One crossed out “water the ferns on Tuesdays” and wrote “Thursdays”. Another crossed out the same line and wrote “Mondays”. Which copy is right? Nobody can say from the notebooks alone. Someone has to sit down, compare the three, and decide. That sitting-down is a merge conflict, and it is the thing every team of people, and every team of programs, eventually dreads.

The rule that makes the dread go away

Here is a different way to keep the notebook. Nobody ever crosses anything out. Each friend only adds dated, signed lines: “10 June, Ana: I watered the ferns.” “12 June, Ben: the ferns look dry; I think Thursdays is wrong.” When the three copies come back together you simply put every line from every copy into one list and sort them. No line fights another line, because no line was ever replaced. Three copies, one pile, zero arguments.

That is the whole idea behind a conflict-free replicated data type, or CRDT. Replicated: many copies. Conflict-free: the copies can be combined by a rule that never needs a referee. The rule has to have three everyday properties, and the notebook already has them:

  • Order does not matter. Add Ana’s lines then Ben’s, or Ben’s then Ana’s: same pile.
  • Grouping does not matter. Combine two copies first and the third later, or all three at once: same pile.
  • Repeating does not matter. Add the same copy twice by mistake: still the same pile.

A pile you can only add to, with those three properties, is the simplest CRDT there is, and mathematicians call it a grow-only set. Everything on this page is a variation of it.

What git gives you for free

A git repository is a very good notebook. Every change becomes a commit, every commit is named by a fingerprint of its own contents, and every commit records which commit it came after. Any number of people can take a copy, work alone for a month, and bring their commits back; git keeps all of them, and it remembers exactly which commit knew about which. Branches, worktrees, forks and pull requests are just different ways of carrying commits from one copy to another.

But git, on its own, is not a CRDT, and this page would be lying if it said so. Two people who edit the same line of the same file still produce a conflict that a human has to settle. Git replicates perfectly and remembers perfectly; what it cannot do is decide. The notebook rule has to come from how you use it.

The happy pile

Tillandsias runs many AI agents on many computers: Linux, macOS and Windows hosts, some of them disposable containers that exist for an hour. They all need to read and write one shared plan: what is being worked on, what was found, what was proved, what was retracted. If that plan were one big file that everyone edited, every hour would end in the three-notebooks problem.

So the project applies the notebook rule to its own plan. Shared state is written as append-only evidence: a host that wants to record something writes a new file, in a folder of fragments, with a name only that host could have produced: the time in UTC, a random suffix, and the host’s own name. Once written, a fragment is never edited. If it turns out to be wrong, the correction is another fragment. Git then has nothing to argue about, because no two hosts ever touch the same file.

linux host macOS host windows host …-linux.yaml…-linux.yaml…-macos.yaml…-macos.yaml…-macos.yaml…-windows.yaml the pile — append only, never edited fold one readable view same files → same view arrival order: any duplicates: harmless edits in place: none
Nobody edits the pile. Each host adds a file only it could have named; the readable plan is computed from the pile, in an order the file names fix, so every host that holds the same files derives the same view.

Seen this way, the whole GitHub apparatus stops being a source of friction and becomes a set of conveyor belts. A work branch is a host’s private stack of fragments. A pull request is that stack arriving at the shared pile. A merge queue takes the stacks one at a time, checks each against the pile as it stands at that moment, and adds it or sends it back; the tree that gets checked is the tree that lands, never a slightly different one. Hooks refuse the few things that would break the rule, such as editing the compacted base in place. None of these parts needs to know about the others, because the only thing any of them ever does is dump information into a shared pile of evidence.

Semantic distillation

A pile of thousands of fragments is not something you want to read. What you read is a view, computed from the pile by a fixed recipe the project calls the fold: start from the last compacted base, then apply every fragment, in an order the file names fix, so two hosts holding the same files always compute the same view. Applying a fragment twice changes nothing. That recipe is the distillation, and it is where the real CRDT thinking lives, because different kinds of information need different rules:

  • Work items and events are a grow-only set. Two hosts adding different items both win; adding the same item twice yields one item. A fact, once recorded, is never lost by a merge.
  • Single-valued fields are a register. A status can hold only one value, so the fold picks a winner deterministically, by timestamp and host name, never by whichever copy happened to arrive first. The project is careful to say what this costs: a register keeps one value and drops the other, which is exactly why facts are kept as a set and never as a register.
  • Progress climbs a ladder. A status may move up (implemented, completed, verified, done) freely, and may move down only when an explicit falsification event says so. A later, lower claim cannot quietly undo a verified one; a disproof can.

Every so often a host that can afford the full checks folds the fragments into a new base and deletes exactly the files it folded, by name, never by pattern, so a fragment written by someone else during compaction survives untouched.

Rectified provenance

The pile has one more property the notebook had: every line is signed and dated. Each fragment records which host and which agent wrote it, when, and about which work item. Because nothing is ever edited, the pile also keeps every mistake next to its correction: an agent that cited the wrong evidence writes a fragment saying so, and a reader can see both the claim and the retraction, in that order, forever.

That combination, an append-only pile plus provenance on every entry plus a deterministic way to distil it, is what the project means when it says generated software should stand on evidence. Code written by many agents in many places is only trustworthy if you can later ask “who claimed this, from what, and did anyone disprove it?” and get an answer that does not depend on which copy of the notebook you happen to be holding.

What the pile does not promise

Honesty about the limits is part of the method, so here are the three things a CRDT cannot do:

  • It does not deliver. Copies that hold the same fragments agree; nothing makes a fragment arrive. Pushing, fetching and the merge queue are separate machinery.
  • It does not make a claim true. Three hosts agreeing on a pile of evidence agree about what was recorded. Whether a recorded test actually proves anything is a question for the CentiColon accounting, not for the merge rule.
  • It does not merge code. Source files are still edited in place and still conflict; they go through review and the full build. The pile is for the plan and its evidence, not for the program.

The project also retracts its own CRDT claims when they fail: one deduplication routine once described itself as a CRDT, and its header now says plainly that it is not, because first-wins depends on arrival order. A pile that keeps its retractions is a pile worth trusting.

One question left: who went first?

Sorting the pile “by date” hides a problem. Three computers have three clocks, and none of them agrees with the others to the millisecond. Whether one fragment came before another is not something a wristwatch can settle. The answer is that computers do not need wristwatches at all; they need to know what caused what, and git has been writing that down all along.

Computers don’t understand time

Why machines count instead of telling the time, and why a git history is already the clock you wanted.

Everyone’s watch is a little wrong

Ask three friends what time it is and you get three answers, a minute or two apart. Usually that is fine. It stops being fine the moment you ask a question like “did Ana write her note before Ben read the notebook?” and the two watches disagree by more than the gap between the two events. Then “before” has no answer. Computers live in that situation all day long.

Why a computer counts instead of flows

A river flows; a metronome ticks. Inside a computer, time is a metronome: a tiny crystal vibrates, a counter goes up by one on each vibration, and that count is the only clock the machine has. Between two ticks nothing happens at all. The computer cannot say “a bit after tick 400”; it can only say “tick 400” or “tick 401”. Time in a computer is discrete: a staircase, not a ramp.

This is not a defect. A count is something a machine can compare exactly, copy exactly and write down exactly. “Four hundred” is the same on every machine; “a moment ago” is not.

Why making clocks agree is so hard

Fine, you say, just set every computer’s counter from one trustworthy clock. Here is why that is finicky in practice:

  • The crystals drift. Two quartz crystals never vibrate at exactly the same rate; left alone, two machines drift apart by seconds a month. Warm one of them and it drifts faster.
  • Asking takes time. To learn the time from a server you send a question and wait for the answer. The answer is already stale by the time it arrives, and you cannot know by how much, because the trip out and the trip back need not take the same time.
  • The sleep problem. Laptops sleep, virtual machines pause, containers are frozen and thawed. When they wake, their counter is wrong by exactly the time they were asleep, and they do not know it yet. Tillandsias runs most of its work in exactly such machines.
  • Time itself is edited. Leap seconds, time-zone rules, daylight saving and a human typing the wrong date all change the number without anything having happened.

Protocols such as NTP work hard at this and get two machines on one network to within a few milliseconds. A few milliseconds is an eternity for a computer: thousands of events fit inside it. So two timestamps from two machines can never prove which event came first.

Letters crossing in the mail

Step back and ask what we actually wanted to know. Not the time of day. We wanted to know whether Ana’s note could have influenced Ben’s. If Ben read the notebook before Ana wrote in it, his note cannot depend on hers, no matter what either watch says. If Ana posted a letter and Ben replied to it, the reply came after the letter, even if Ben’s clock says otherwise. And if two letters crossed in the mail, neither came “first” in any sense that matters: they are simply concurrent.

This relation, “could have caused”, is called happens-before, and it is the only order a distributed system truly needs. Notice that it is a partial order: some pairs of events are ordered, and some are not, and that is the honest answer.

The trick: carry a counter in every letter

In 1978 Leslie Lamport showed that you can capture happens-before with nothing but counting. Each machine keeps one integer. The rule has three lines:

  1. Before you do anything worth recording, add one to your counter.
  2. When you send a message, write your counter on it.
  3. When you receive a message, set your counter to one more than the larger of your own and the number on the message.
host A host B 123max(3,6)+1 = 781max(1,3)+1 = 456 message stamped 3 message stamped 6 rule: tick before each event; on receipt, counter = max(mine, theirs) + 1
Each host counts its own events. A message carries the sender’s count, and the receiver jumps to one past the larger of the two. Nothing is synchronised and no one asks what time it is, yet every cause carries a smaller number than its effect.

No machine ever asks what time it is. Yet if one event could have caused another, the first always carries the smaller number, because the message that carried the influence also carried the count. The number is not a time; it is a logical clock, and it is exactly as discrete as the computer wanted it to be in the first place.

One honest caveat. A smaller number does not prove causation: two concurrent letters may carry 3 and 5 without either having influenced the other. Lamport’s counter is consistent with happens-before, not equal to it. Keeping one counter per machine (a vector clock) closes that gap, at a cost in size. For most of what a project needs, the single counter is the right tool: it is small, it never disagrees with causality, and a tie can be broken by the machine’s name.

Your git history already is this

Now look at a git repository with the above in mind. Each commit is named by a fingerprint of its contents, and that fingerprint covers the names of its parents. So a commit literally cannot exist before its parents, and anyone holding a commit can follow the parent names all the way back. The arrows in the graph are happens-before, written down.

0000a1b2c3d4e5f69c0d7a8bmergef1e2 branch: linux host branch: macOS host 1 2 3 4 3 4 5 6 green: generation = 1 + max(parents)
Every commit names its parents by hash. Follow the arrows and you get “happened before” with no clock at all: a1b2 is before both branches, e5f6 and 7a8b know nothing of each other, and the merge knows everything.

This is better than a Lamport clock, not worse: the graph is the causal order, and a Lamport clock is just the smallest counter that respects it. You can read one off the graph by giving each commit one more than the largest number among its parents. Git in fact computes that very number, which it calls a generation number, to speed up its own history searches. Git also records the author’s wall-clock date on every commit, but nothing about branching or merging depends on it; two commits with crossed dates merge exactly as well as any others.

So the punchline is a cheap one, and that is the point: back your project up in a git repository and you get happens-before for free. Every clone is a replica; every commit is an event with its causes attached; every merge is a letter arriving. The clocks on the machines can be wrong by hours, and the history still says, correctly, what came after what.

How Tillandsias uses it

The project keeps its shared plan as an append-only pile of fragments inside that git history, so every fragment inherits the causal order of the commit that carried it. Within the pile, two smaller choices are worth naming plainly:

  • Fragment names begin with the UTC time, so a plain alphabetical sort of the folder is also a chronological one and every host folds the pile in the same order. That is a wall-clock stamp, and the project knows it: nothing is lost if two hosts’ clocks disagree, because facts are kept as a set, where order does not matter at all.
  • Where one value must win, the fold breaks ties by timestamp and then by host name, so every host picks the same winner from the same files. That is the familiar “later watch wins” rule, chosen for a field where only one value can survive anyway, and never applied to the facts.

The methodology also describes Lamport-style counters for ordering an agent’s iterations across merges: tick locally, take the maximum on fetch, break ties by agent name. Where it matters most, though, the project does not reach for a clock at all. The merge queue checks a candidate against the shared branch at a particular commit, and just before pushing it reads the branch again: if the commit has changed, the green result described a tree nobody is shipping, and the candidate goes back in the queue. That is a happens-before check, done with git’s own graph, and it is the one that keeps the shipped tree equal to the tested tree.

Which is where this page and the last one meet. A pile you only add to needs no referee; a history that names its causes needs no synchronised clock. Together they are most of what a crowd of agents on a crowd of machines needs in order to agree.

Big Graph

The desired architecture of a tillandsias.org checkout inside a forge. Follow the main flows left to right; scroll to explore and select a component for its sources.

Reading key: solid green = implemented at the pinned release; amber dashed = partial; gray dotted = goal only. These are editorial assessments from the stable audit, not runtime telemetry. Edges show intended interactions; dotted shortcut edges pass through hidden layers and do not prove a direct connection or enforcement.

01 · Build and publishOperator → forge → git mirror → guarded exit → GitHub
02 · Preview and filesRuntime boundary → mounted output → web sibling → browser
03 · Internal servicesHost keys → control wire → enclave → secrets, inference and cache
04 · Security reviewCertificates, policy, provenance and evidence support the flows above
05 · LifecycleInstall → create → edit → publish → teardown → rebuild
06 · AccountabilityRequirements → obligations → checks → ledger → score → review

Tillandsias / Slides

tillandsias · in one line

A small cloud region on your own computer.

Disposable workspaces. Durable work. Boundaries you can inspect.

YOUR COMPUTER AI assistants disposable workspaces Apps + tools Shared services · one local region push Durable work upstream repository
Your tools live together locally. Commit and push the work you want to keep.

Tools, services and AI assistants share one local region. Rebuild a workspace when needed; commit and push to keep your work.

The software is free. Your hardware, electricity and any paid AI service you choose still have costs.

1 / 16