Cycle for Zcode 1.0.9
Pre-releaseCycle for Zcode 1.0.9
Governed delivery for ZCode: an architect plans, an executor implements inside an
isolated worktree, two independent reviewers judge the frozen candidate, and an
arbiter decides - with every gate result and verdict written to a hash-chained,
signature-checkpointed ledger.
1.0.9 changes no product code. It exists to stop shipping a security
statement that 1.0.8 carried and that direct observation refuted.
1.0.8's threat model said that because ZCode CLI 0.16.9 does not execute
plugin-provided agent components, /cycle:setup install writes five managed
profiles explicitly. It promised to re-establish that premise by observation
before any receipt asserted it. The observation was made, on Desktop
3.14.1.7714 / CLI 0.16.9, and it went the other way: the host does read
the agents/ directory this archive ships - by convention, with the manifest
declaring no agents key - and lists all five roles under Settings > Subagents
as dispatchable, their tool lists parsed, with a model and reasoning level of the
host's own. Assume the roles are reachable from any session, not only from a
governed run.
This is a documentation defect, not a hole, which is worth saying plainly
because it first looked like one. The definitions the host reads are the
managed role profiles, carrying the same bounded tools: lists, and the host's
built-in general-purpose subagent already holds every tool - so exposing
Cycle's roles adds nothing a session could not already do. What was wrong was the
stated reason for writing project profiles. The real one was already recorded
in the plugin's own hooks configuration: a dispatched role is bounded by the tool
list in its profile, because the host does not run PreToolUse inside a
dispatched agent. Hooks guard the main session; the profile guards the role.
The marketplace contract asserted the same falsehood as its rationale for keeping
agents out of the manifest. Keeping it out is still right - declaring it would
add a second, divergent source for the same definitions - and the assertion now
says that instead.
The seven fixes below are unchanged from 1.0.7 and 1.0.8. They were found by
running the thirteen-scenario live campaign against 1.0.6's published archive
on Windows and in WSL. Eleven scenarios passed; the two that failed are the first
two. The seventh was found in the release machinery while shipping them.
Fixed
A per-role model assignment is usable, not merely configurable. /cycle:models
accepted exactly three model references, all under one provider prefix, and reported
them as applied with no warning. All three failed at dispatch with
provider-not-found — on the provider prefix, not the model name — because the
host resolves providers under a namespace the plugin refused to accept. The only
setting that worked was inherit, which is the absence of the feature the product is
named for, and the failure surfaced at the review phase, after an architecture, an
execution and five verification gates had been paid for. A plugin cannot enumerate a
host's providers and has no business deciding which ones exist: Cycle now validates
the shape of a reference and leaves resolution to ZCode. What it owes the operator
instead is an early answer, so a pinned role is reported as dispatch_unverified and
the run protocol probes every pin before a workflow starts. An unresolvable provider
now costs seconds.
Every receipt written by 1.0.6 and earlier therefore records a run in which architect,
executor, both reviewers and the arbiter shared one model — independent prompts and
tool lists, one judgement. Those receipts do not imply the wider claim.
An abrupt stop no longer leaves a workflow unrecoverable and the project locked.
Recovery required a worktree to exist on disk if and only if a base revision had been
recorded. A session killed between those two writes broke the invariant, and recovery
refused outright. Refusing was wrong twice over: recovery only reads — the guards that
matter live on prepare_worktree and freeze, and they still hold — and the refusal
returned before the immutable request text was assembled, withholding the one thing an
interrupted operator cannot reconstruct exactly when it was needed. Meanwhile the
mutation lock kept the project read-only, including for an unrelated delivery already
sitting uncommitted. Recovery now names the inconsistency, returns a recovery action,
and hands back everything it knows.
One project directory can no longer carry two identities. project_key was
whatever the calling agent chose to pass, and the run protocol only described it as
"this workspace's stable project key". It was neither. The server now derives it
canonically from the directory and ignores what the caller sends.
/cycle:setup install no longer deadlocks the first governed run. It left managed
role profiles visible to git, so the first freeze saw a dirty tree it had itself
created. They are excluded now, and the report says so rather than leaving the
operator to discover it.
Risk routing sees the code a change touches. No path could raise Authentication,
Authorization, Cryptography or Secrets: only the request text could, and only by
literal marker. A change to auth.js therefore routed to quick — no independent
reviews — unless the author happened to write "authentication" in their request. The
campaign demonstrated exactly that, twice, on the same fixture. Paths now raise a
category on their own, matched as whole tokens so auth.js counts and authors.md
does not, with documentation taking precedence over everything else.
The uninstall documentation named the wrong directory. It said ZCode keeps the
marketplace's cached copy of the plugin. That cache is emptied completely. What
remains is ZCode's mirror of the marketplace source under
plugins/marketplaces/<marketplace>/, and it is larger than the installation was,
because it carries every platform's daemon rather than only yours. Someone following
the old wording would check the plugin cache, find it empty, and conclude the removal
was complete.
Fixed in the release machinery
The daemon version gate no longer passes the daemons it exists to reject.
Assembling this release copied the 1.0.5 daemons into the plugin and wrote a
manifest declaring them 1.0.9. They had been sitting in the untracked staging
directory since that release — nothing clears it — and the gate reported no problem.
That gate had already failed once on these same two files. Until 1.0.6 it scanned the
executable for the expected version as a substring, and in a 39 MB binary the sequence
turns up on its own, so the 1.0.5 Linux daemon was read as declaring 1.0.6. It was
rewritten to execute workflowd --version. But --version was added in 1.0.6, so
the one daemon that cannot answer is one older than the flag — precisely the case the
gate is for — and not answering was recorded as the same unverified state used for a
daemon built for another platform, which is a legitimate skip. Both callers blocked
only on a daemon that answered and answered wrong. The daemon that would not speak
walked past both.
What shipped as 1.0.6 is unaffected: the release workflow builds both daemons from
source on their own platforms before assembling, so the published archive carries
genuine binaries. The defect was in what a local assembly stages and what the merge
gate would have allowed into the repository — and since the native manifest takes its
product version from the plugin manifest rather than from the binary, the result
carried an honest digest beside a version nobody had read.
A result now records whether the machine could execute the file at all. Runnable and
silent is a failure; not runnable here is a skip, left to the CI job that can run it.
Assembly additionally refuses when a staged daemon is missing outright, because that
copy used to throw after the previous plugin directory had been removed.
Known ZCode limitations
Uninstalling the plugin empties its cache but leaves ZCode's mirror of the marketplace
source — roughly 76 MB, including a native daemon for every platform rather than only
yours — in your ZCode profile. This is host behaviour: the mirror and its registry
belong to ZCode, and a plugin reaching in to erase entries would be a worse fault than
the disk space it recovers. The retained copy is inert. Remove the marketplace itself
to reclaim the space. Your audit data is untouched by an uninstall, which is why the
control plane lives outside the plugin tree.
The Windows daemon is unsigned; authenticode.json records NotSigned. That is a
declared limitation of this release, not a failure, and the clean-install job verifies
it is unsigned rather than badly signed.
Schema
Unchanged at schema 18. A newer schema opens in the documented safe read-only mode
rather than refusing or migrating downward.
Verification
Rust 304 tests, MCP 46, qualification 27, clippy and cargo fmt clean. Built,
sealed, clean-installed on Ubuntu 22.04, Ubuntu 24.04 and Windows 11, and
attested with build provenance by release-candidate run 35649883755 from
6b6b72c191544bb8308d739183ed191df2996b47 - the commit this tag names, and one
on which both the release and the quality workflow are green.
| artifact | SHA-256 |
|---|---|
zcode-cycle-1.0.9.zip |
3c4d0abf4eceadb4aa7a544e05756d4e7491e1755428e5387a13dbaa30279744 |
zcode-cycle-native-linux-x64-1.0.9.tgz |
67b41fa2ee00a9759f2ac4fd11e16411659b363ac7b70be3cd60f1e5f5a382d9 |
zcode-cycle-native-win32-x64-1.0.9.tgz |
e61508681d3240a886c8c1147336fbd3c89b5214dd1ada02a0082add2b6280a9 |
One test changed on the way here, and no product code. Three Windows lifecycle
tests failed together on a loaded CI runner: the Windows health helper waited for
runtime/ipc.secret and then connected once. The credential is not a readiness
signal - the daemon writes it before opening its store and verifying the whole
hash chain, and binds its endpoint only afterwards. The shipped bridge always
handled this correctly, retrying against a deadline, as did the unix arm of the
same helper; only the Windows arm assumed. It now retries too.
Not yet claimed
This build has not been through the thirteen-scenario live campaign. Every fix
above is covered by tests and the whole release lane is green, but the live campaign
is what found six of the seven defects this release closes — in a 1.0.6 that was also
entirely green, and that had itself been sealed and attested. Until 1.0.9 has been
certified live against these exact bytes, this is a sealed and reproducible candidate
rather than a certified release, and it is marked as a pre-release for that reason.