Skip to content
Like what we’re building? Star on GitHub

Changelog ​

Generated from Conventional Commits at each release. Older majors are archived under docs/changelog/.

5.9.0 (2026-11-04) ​

Features ​

  • audit: every run launch joins the audit log (ADR-158) (#2998) (370e862)

    whyA run launch writes `run.launched` (and a fork `run.forked`) whatever started it — Studio, CLI and API, webhooks, schedules, the board dispatcher, pipeline admission — through one wrapper around the launcher, with the actor (the user, the webhook delivery, or the system), the workflow, the run's source kind and its delivery or schedule id, and the client IP when an HTTP request exists. A test enumerates the launch call sites and refuses one that bypasses the wrapper. ADR-158 records the choice…
  • cloud: a pod's run files are served by storage the driver keeps and drained at teardown (0526cd2), references #2668 #2668 #2668 #2668 #2690 #3024

    whyThe artifact-files panel was structurally empty on a cloud run: the run-files directory only ever reached the container as a host bind, a driver with no host filesystem dropped it, the variable naming it went with it, and whatever the pod produced died with the pod — the 1h45 run whose deliverables nobody could collect (#2668). ADR-143 wrote the contract; this lands its seam:

Bug Fixes ​

  • cloud: give every drain key site a witness and decline what the member causes (99939e5), references #2668 #2690

    whyRound 5 of the #2668 review loop. Each finding was first replayed as a probe on dba1f53bd; all four reproduced, none was refuted. The #2690 surface is untouched.
  • cloud: give the drain's directory guards one witness each and one key per entry (2535490), references #3024 #2668

    whyThe descriptor-held drain left guards that covered each other or had no witness, and its one-key promise held only where the filesystem names each entry once. This round closes that class: every guard of the directory mechanism is now seen red on its own, and a name reaches an entry only under exactly the spelling the directory lists.
  • cloud: judge and record every drain member where its write lands (7b71298), references #2668 #2690 #3024 #2668

    whyRound 4 of the #2668 review loop found one class in the cloud drain: it judged and recorded members by their LEXICAL path while every write lands at the RESOLVED one. Each finding was checked against the code and reproduced by a witness red on the unfixed tree; none was refuted. The #2690 surface is untouched.
  • cloud: land every drain member where its name spells, through no link (f346b00), references #3024 #2668

    whyThe drain kept producing new shapes of one class: it landed archive members into a host dir that may already hold entries (pre-drain files, dirs, links, a raced attempt's leftovers) and judged or recorded each member by a path other than the one its write reached. This replaces the mechanism instead of patching a seventh site.
  • cloud: the drain cures a type flip and its failure warns of a partial panel (e6d228a), references #2668 #2690 #2668 #3024

    whyRound 3 of the run-artifacts-download review (#2668): the two findings on the run-files seam, both confirmed and fixed.
  • cloud: the drain judges the host dir where it stands, stops an archiver it no longer reads, and calls one raced attempt no union (16dd820), references #2668 #2668 #3024

    whyRound 3 of the #2668 review loop: three findings on the drain, and the branch history's residue.
  • cloud: the drain lands whole members only, types its loss, and its teardown wiring has a witness (15d6beb), closes #2668, references #2668 #3024

    whyRound-1 fixes of the run-artifacts-bug review (#2668), six findings: four fixed at their class, one restored, one refuted with its proof.
  • cloud: the drain meters what the host holds, a declined member costs a counter, and only a draining driver gets the storage (86edea3), closes #2668 #2690, references #2668 #3024

    whyRound-2 fixes of the run-artifacts-bug review (#2668), after 3176d4c0a. Four findings: each was checked against the code and found real (none refuted), then fixed at its class. The #2690 surface is untouched.
  • cloud: the drain sweep judges containment by Rel (a46e9b0), references #2668 #2668 #3024

    whyRound 4 of the run-artifacts-download review (#2668): one finding confirmed and fixed, one refuted.
  • cloud: the drain sweep takes a raced attempt's emptied dirs too (f1a8e5a), references #2668 #3024

    whyThe sweep walked only regular files and symlinks, but a raced attempt's extract builds directories as readily as it writes files — the archive's dir members and the parents its file writes need. The file sweep emptied them and left the shells standing in the run file store's dir: a directory the settled archive never carried, listed where the panel lists the drained set.
  • cloud: the drain's declined members reach the run, and the run-files storage sits at its path (95eaa73), closes #2668 #2690, references kubernetes#60342 kubernetes#121294 #2668 #3024

    whyRound-1 fixes of the run-artifacts-bug review (#2668): five findings, each confirmed and fixed at its class. The #2690 surface is untouched.
  • cloud: the manifest comments state the bind-driver entry and the cap admits equality's witness (0122db7), references #2668 #2690 #2668 #3024

    whyRound 5 of the run-artifacts-download review (#2668), the run-files seam's share: one stale invariant corrected at both sites that stated it, one finding refuted with a boundary witness.
  • cloud: the read-back cap is the drain's meter with its escape hatches, and one declined member costs no other (e791819), closes #2668, references #2690 #2668 #3024

    whyRound-2 fixes of the run-artifacts-bug review (#2668), stabilized from the edits a fixer killed mid-round (usage credits) left uncommitted — the round's findings were never journaled, so each hunk was judged on its own: every one completes a defect of the bug half (the cap, the drain under a type flip, the drain-failed event's vocabulary) and none touches the #2690 surface. All were kept; the cap's was finished where it fell short.
  • cloud: the run-files drain drops what only a raced attempt landed (eb7d973), references #2668 #2690 #2668 #3024

    whyThe reviewer's round-2 findings on the run-files seam (#2668), each one tried for refutation and confirmed — three corrected here:
  • cloud: the run-files seam answers the review round's six findings (692e98c), references #2668 #2690 #2668 #3024

    whyThe reviewer's round-1 findings on the #2668 run-files seam, each one tried for refutation and confirmed — none refuted, six corrected here, every fix at the class that shares the defect:
  • sandbox: the drain's flip removal reads the pre-drain dirs too (9890ed8), references #2668 #3024

    whydropRacedDrainDir judged a flip's tree only on the files-and-links half of the before snapshot: a dirs-only tree the host already had — an empty pre-drain dir at a path the settled archive carries as a file — matched nothing, and RemoveAll took it, against the file's own contract (a dir the host already had is as untouchable as a file it already had) and the sweep's beforeDirs guard. The walk now judges dirs against the same pre-drain dirs half the sweep judges, the refusal names the tree's own…

5.8.0 (2026-11-04) ​

Features ​

  • dsl,docs: C331 warns where a script body names the minted grant (3019b77), references #2875 #2875

    whyA `{{vars.forge_publish_token}}` in a tool node's `script:` body is refused by the RUN (#2875), and nothing said so before it: `iterion validate` was clean and the preview rendered a tidy placeholder (pkg/dryrun passes a nil guard), so the author learned it mid-node, after the spend upstream. C331 says it at authoring time, and WARNS rather than rejects — the compiler reads the NAME while the run asks its guard, so a launch holding no grant renders the var's empty value and runs.
  • live: ledger rows name what served; one chokepoint arms every run (d33a22f), references #1423 #1423 #1422

    whyThe code half of the live re-proof (#1423, option A of fleet question pinned one, nor a target nobody paid for from one this machine cannot run, and the harness had no way to re-target a fixture onto a route the operator can fund.

Bug Fixes ​

  • bots: Vetty holds a bump only for a HIGH/CRITICAL it introduces (#3042) (1794b10), references buildkit-operator#38 #3006

    whyThe audit sorted nothing and held on any unresolved HIGH/CRITICAL on the new version, while also calling "safe" a bump that introduces none, and broke the tie toward "suspicious". Bumps that strictly improve the pins were held: buildkit-operator#55 resolved eleven advisories and the Go 1.26.10 security release, introduced none, and was held for nodejs CVEs the old pin carries too. The same diff got opposite verdicts across runs.
  • server: key the auth rate limiter on the client, not the socket (fc2f941), references #2804 #3022 #2990 #3000 #3000 #2673

    whyclientIP returned r.RemoteAddr with its ephemeral source port, so every fresh TCP connection of one client was a different client and bought a fresh rate-limit bucket: 60 requests over 60 fresh connections, zero 429 where a burst of 30 should have run out (#1668's r5a measurement, reproduced). TrustedProxyCIDRs — the field that decides whether a forwarded address may name the client — was declared and assigned nowhere.

5.7.0 (2026-11-04) ​

Features ​

  • auto-maintenance: read allow_auto_merge and the lane's verdict (8b622f1), references #1597 #1585

    whyThe preflight compares settings in three systems because nothing type-checks the agreement between them. Two of its own blind spots had the same shape, and hardening their reads hardened every read.
  • bots: the documentary campaigns deliver only the paths they own (75b2a79), references #1595 #2437 #1409 #2436 #2437

    whyscope_check is a LOOP condition: it asks the campaign to revert, and the loop-exhaustion exit (`gate -> mr_gate`) hands the banked series to the PR tail whatever it said. A docs-refresh or product-docs pass that never reverted its out-of-scope write was therefore PUSHED: a `.github/workflows/**` path died at the forge as a raw 403 whose stderr names no grant (#1595), and anything else (Go code, a script, another product's pages, the editorial charter) was delivered under the bot's identity.
  • bundlelint,runtime: a bundle preset's prompt body is held to the secret grants (b28b0df), references #2924 #2845

    whyA bundle's `presets/<name>.md` body is appended to every model task's system prompt at run time (Task.PresetFragment, the "## Focus" section) and rendered by the resolver that materialises `{{secrets.X}}` at the PROMPT sink — but the file is merged into the workflow AFTER compilation, so no compile walk ever read it. The resolver leaves the reference of a secret whose grants withhold `prompt` AS WRITTEN (the guard resolves nothing at an ungranted sink), so every model task the run builds read…
  • cloudpublisher: the frozen pair order names what it held out (48eb6dd), references #2005 #2914

    whyADR-131 §5 promises a frozen pre-upgrade routing list "replayed as stored with one named warning". The replay was already honest — the resume hands the run document's own snapshot back to the walk, and no consumer re-normalises a resolved policy — but the warning did not exist. Eight sites passed a credential over because the run's frozen pair order names no pair for its slot, every one a silent continue/break: the tenant key fill, the tenant forfait fill, the key restore, the forfait restore,…
  • credpool: stamp a node-to-credential-fingerprint plan in the RunMessage (#2288) (c0ff7d0), references #2279

    whyThe fine door-lease producer. The runner's route key names a CHANNEL (providerFingerprint's routing label), not a credential - the run's own OAuth and a donor's lent OAuth stamp the same label, so a fine table keyed on it joins disjoint spaces and books $0 on every door-served run (revi R30d246). The broker's fine consumer (Outcome.ByFingerprint + effectiveCharge, #2279) has been shipped and inert, waiting for it.
  • delegate: opencode speaks the 2.x argv, subcommand first (4cc1bcc), references #2915

    whyopencode 2.x resolves the subcommand before it knows any flag, so the 1.x argv iterion emitted was refused outright: measured on the host's 2.2.5, `opencode --format json -m … run` exits 1 with "Unrecognized flag: --format in command opencode" and prints its usage on stdout — the backend was inoperative on the whole 2.x line.
  • dsl: the compile walk reads an LLM node's images: paths (4c95fef), references #2845

    whyAn `images:` entry is a template the run renders through the same resolver a prompt body goes through — `{{secrets.X}}` materialises at the prompt sink (ResolveSecretRefAt) — and collectAllRefs did not walk it, so it was the one prompt-sink text no check read: a secret whose grants withhold `prompt` reached the harness as a resolvable placeholder, and a typo'd `{{outputs.gen.file}}` resolved to an empty path at the one moment the node needed the image. The walk adds the shared LLM field group…
  • engine: operator-bound context documents materialise into every run of their bot (6dd2b5b), references #1908

    whyThe context binding kind the store accepted since the kinded-binding slice (ADR-147 D2) had no materialisation: a launch now resolves each of the bot's context bindings against its pinned source, and folds the delivered documents INTO the launch's attachment-promote chain — the one seam every hand-off already honours (in-process engine, detached runner, cloud publisher), so no new wire: in cloud the write lands in the shared blob store and the runner reads it back through the attachment…
  • legal: legal pages from deployment configuration (ADR-160) (#3000) (a22dcc5)

    whyThe studio serves the operator's legal notice, privacy notice, terms and accessibility statement, plus a contact address, without a session: `GET /api/legal/{slug}` and `/legal/*` pages, linked from the sign-in, landing and home pages, the sidebar and the restricted shells — only for the documents a deployment configures. The content is deployment configuration, never in the bundle: `ITERION_LEGAL_DOCUMENTS_DIR`, `ITERION_LEGAL_CONTACT_EMAIL` and `ITERION_LEGAL_ACCESSIBILITY_*`, set from the…
  • permission: the closing verdicts read a path's folded spellings, and every gated node is refused the run's secret trees (9e7a44e), references iterion#2868 #1710

    whyTwo holes remained in the path-anchored gate after the spellings work:
  • platform-bots: a shadowing override is a gauge, not a hope (e560b0d), references #851 #2354

    whyThe two shadow reporters that landed with #851 are both PASSIVE: the admin inventory waits for an operator to call GET /api/admin/bots, and warnIfOverrideShadowsNewerBake fires at resolve time, so a bot nobody launches warns never — which is exactly the bot whose override rots. Three production overrides were measured older than their bake and nothing said so for a month.
  • runtime,backend: an untrusted run serves its skills through a pinned channel — allowlist for claw, --add-dir for claude_code (cbf45d7), references #1719

    whyOn a run whose workspace the operator does not control, every skill lookup root (.skills/, .claude/skills/, .agents/skills/, .claw/skills/, at every ancestor level) answers with text the checkout planted. The run now serves only the skills the mirror OWNS, re-carried into an engine-owned root (<workDir>/.claude/iterion-run-skills, reset on every mirror pass, in the .claude/skills layout):
  • store,cloudpublisher,server: a hard per-key concurrency cap parks the run on an atomic slot ledger (#2643) (c745ebc)

    why`max_concurrent_runs` was advisory: the walk's skip read the run documents before the run was written (two publisher replicas admit on one reading), a `key_overrides` pin bypassed it and the refused-key restore undid it.
  • studio: label the Source view's stale-content window with a badge (40b8c14), references #1735 #1662

    whyThe view offers Edit over text rendered from the document the store held BEFORE its last change: `bufferKey` carries the FILE axis only, the render effect debounces 500 ms and returns early while editing, and the only signal that the window exists is the Apply refusal that follows. Jo's decision (D45) keeps the window open — putting the document in the buffer's identity hides Edit for ~500 ms after every canvas keystroke — and labels it instead.
  • studio: the bot-bindings surface carries the context kind — typed spec, schema, and the bindings UI (dd19a9b), references #1908

    why1908.4: the spec side of the context kind (ADR-147). The bindings routes were already in the recorded-mux inventory; what was missing was the TYPED surface: routeSchemas() entries for the four bindings routes, so openapi.json gains the BotSecretBinding and ContextSource components — kind, the pinned source, and the degraded_reason/degraded_at readout the engine writes — plus the POST's refusal vocabulary, the 501 named by its stable code context_binding_not_materialised. openapi.json and…
  • triggers: a subscription's credential pins are writable on the routes (2eb5ca6), references #2644

    whyThe spine's pin conveyor was wired end to end — Subscription.KeyOverrides reaches the plan on both lanes and the plan reaches LaunchSpec — with no writer: triggerSubscriptionReq declared neither key_overrides nor secret_overrides, and applyTriggerReq copied neither, so a subscription that pins nothing read exactly like one whose pin was dropped. The routes now carry the two fields (on update, a request that omits a pin keeps the stored one, an empty map is the explicit clear), and the write…

Bug Fixes ​

  • adr: a pool's ceiling inherits the operator's global, not the default (6417b59), references #950

    whyRound 1 of the review loop on ADR-155: six findings, five real.
  • adr: ADR-153 names the probe, the unit and the comment class it corrects (f9214cb), references #1165

    whyThe absorption bullet listed `iterion contracts` as "the admission probe" between two public-contract markers; the command prints build, queue and store schema versions from the executable alone, reports `build_skew` unknown by design and reads no bot's contract (cmd/iterion/contracts.go, zero `PublicContract` or `ir.` reads). The bullet now names it a deployment-compatibility probe with those facts beside it, so the marker is not read as proof of a contract admission.
  • adr: the forge allowance says why git does not cross the pool proxy (7caf345), references #2029

    whyDecision 3 justified the forge's IP-layer allowance with "the clone guard needs no cluster egress rule", which reads against the sentence it serves: the policy must allow the forge precisely because that traffic leaves the pod. What the clone guard actually does is dial the repo host itself instead of chaining to the pool's proxy, which is why the forge cannot be enforced by NAME there — and the Consequences already name chaining as the follow-up that would change it.
  • adr: the pool boundary is two-sided, and its peers live where the install puts them (2a153bc), references #2029

    whyRound 2's review found five places where the record decided less than it claimed. They land together because they are one record and one derived golden; each is independent of the others.
  • adr: the pool gate is two sites, and the bot dimension owes its fold (d782c8d), references #950

    whyRound 3 of the review loop on ADR-155: eight findings, eight real, all in the record's prose — no production code changes.
  • adr: the post-increment count already carries the asker, and arming names its predicate (96d737f), references #950

    whyRound 2 of the review loop on ADR-155: eight findings, eight real, all in the record's prose — no production code changes.
  • adr: the record cites its own question and names the metrics peer (4337346), references #88 #97 #2029

    whyThree statements of ADR-154 were wrong or incomplete, each in text an earlier round of this loop wrote.
  • auth,server: the provisioning's writes tolerate, its reads answer (2f8f447), references #2655

    whyof its sites, at the defects the enumeration left: a cosmetic write that sank a complete account, a failed read that answered a fact, a provisioning that left an orphan, and a failed resolution reported as a configuration fact.
  • bundles: discover preset bundles and enable Nexie subagents (#3005) (6b44d77), references #2924 #2830

    whyShare the bundle-directory marker verdict across discovery and explicit main.bot paths, including presets-only bundles and excluding regular-file markers. Exercise preset execution, validation, malformed presets and registry schemas.
  • chart: images pinned by digest — in the docs, and enforced on render (#3003) (4540d03)

    whyThe ops runbook said production tracks the `:edge` tag; the GitOps values pin the server, runner, pools and the default sandbox image by digest (relevé of 2026-11-03), which is what the architecture document says. The runbook and every other page claiming `:edge` in production are aligned; deploying is documented as resolving the release digest, committing it, and `cosign verify` against the release workflow's identity.
  • chart: the deadline check reads the declaration go-yaml leaves last (#2780) (0b91bb2)

    whyThe extractor stopped at the first server.progressDeadlineSeconds line while go-yaml — the parser helm reads values with — merges duplicate keys and multiple documents last-wins: a values file whose merge conflict left two server blocks rendered 1200 through helm template while the guard printed a watch sized for 600 (proven on helm 4.1.1, both forms), the #2780 failure — helm giving up while the workload is still Progressing — with every gate green. The guard now follows the last declaration,…
  • chart: the deadline check resolves the integer forms go-yaml resolves (#2780) (779eb02)

    whyThe *[!0-9]* digit check refused values go-yaml — the parser helm reads values with — resolves as integers: progressDeadlineSeconds: +600 renders as 600 through helm template on values-dev.yaml while the helper exits "not an integer"; 1_800 and 0x… are the same class. The check now resolves the way go-yaml hands the value to the templates: sign and _ separators are cosmetic, 0x/0b/0o and a leading-0 form carry their radix (0600 is octal 384, not 600 — the decimal misread would size the watch…
  • chart: the deadline extractor strips CR from the top-key anchor (#2780) (5f74cf5)

    whyA values file with CRLF line endings — a file helm installs, since go-yaml accepts CRLF — was refused as "does not set server.progressDeadlineSeconds": on a `server:` line ending in CR, awk's $1 is `server:\r`, the `:$` strip misses, and `top` never equals "server", so the knob nested under it is never read. The CR strip on the value line was dead code for exactly that class: it only ran after the anchor had already matched. The anchor strips CR before the colon now, and the whitespace witness…
  • chart: the deadline extractor trims the whitespace go-yaml tolerates (#2780) (01084d1)

    whygo-yaml — the parser helm reads values with — accepts trailing spaces and tabs after the value, a tab between key and value, and a tab before a comment; the extractor refused all four forms as "not an integer" on values files helm itself installs. It trims them now, and a witness exercises each form on its own values file.
  • chart: the deadline guard reads a CRLF document boundary (#2780) (aaefdba)

    whyRound 8 of the review loop, finding 1 (medium): the reader stripped the CR of a CRLF ending at each site that read a line — the top-key anchor, the knob line, the block-scalar opener — but the document-boundary rule matched the raw record, so `---` followed by a CR was no boundary. The documents of a CRLF values file collapsed into one: a `server:` block re-opened in the later document read as a same-document repeat, the knob the earlier document held was dropped, and a file helm installs with…
  • chart: the deadline guard reads every value start, null spellings and a ... boundary (#2780) (a6c017a)

    whyRound-1 findings on the guard scripts/chart-kind-wait.sh derives the install's --timeout from, each probed on helm 3.20.2 (the devbox pin) with values-dev as base, before and after:
  • chart: the deadline guard reads the scoping go-yaml applies (#2780) (a51fd75)

    whyRound 7 of the review loop: all four findings confirmed by live measurement on helm 4.1.1, none refuted.
  • chart: the deadline guard refuses a flow that outlives its opening line (#2780) (e83dc77)

    whygo-yaml ignores indentation inside flow, so a column-0 line inside a flow map re-anchors the line reader and a knob one line down is read from a map it does not belong to: helm 4.1.1 renders that file clean with no server deadline — Kubernetes' 600 s default applies at install — while the guard printed a shorter watch and exited 0, the silent failure the header refuses. Refusing only that shape leaves the class alive: the layout that opens the flow under `server:` and leaves the knob interior…
  • chart: the deadline guard refuses a second document, tab indentation and a glued colon (#2780) (42dbf43)

    whyRound-2 findings on scripts/chart-kind-wait.sh, each probed on helm 3.20.2 (the devbox pin) through helm template over values-dev.yaml:
  • chart: the deadline guard refuses a value continued onto a deeper line (#2780) (8e266c3)

    whyRound 8 of the review loop, finding 3 (low): a plain scalar continues onto any deeper content line that follows it, and a line reader sees its first line alone — `progressDeadlineSeconds: 600` then ` 0` is the string "600 0" to go-yaml (yaml.v3, probe-measured; blank lines between them fold too), which helm renders verbatim and the API refuses, while the guard sized a 720 s watch from the 600 it read. A deeper `key: value` or content after a comment makes go-yaml refuse the file instead; the…
  • chart: the deadline guard strips only the enclosing quotes of a value (#2780) (03f9935)

    whyRound 8 of the review loop, finding 2 (medium): the value reader dropped every quote character from the knob's value, so a plain scalar that contains a quote — `progressDeadlineSeconds: 6"00`, the string go-yaml hands over, which helm renders verbatim and the API refuses — resolved to 600 and sized a 720 s watch for a Deployment the install never creates, where the header promises a "not an integer" refusal.
  • chart: the install clients watch the values deadline, not a fixed cap (#2780) (5559980)

    whyBoth helm install sites capped their own --wait with a literal timeout (5m in `task chart:kind`, 10m in cloud-e2e) while values-dev installs a 1800 s server deadline: the client gave up before the deadline it installed could judge, so a slow first pull failed the smoke on a server pod that was still Progressing. One derivation point now feeds both: scripts/chart-kind-wait.sh reads the values file's own server.progressDeadlineSeconds and prints deadline + 120 s (1920s for values-dev), refusing a…
  • chart: the install scripts hold nothing but their shape, and the wait names its one-pod bound (#2780) (61e34bb), references #981

    whyRound 3 of the review loop on the structural rewrite: six findings, all confirmed, none refuted.
  • chart: the install wait follows the rendered deadline, not a reading of the values file (#2780) (1ca8867)

    whyThree fresh adversarial rounds kept finding values forms the hand-written YAML reader of scripts/chart-kind-wait.sh mis-paired with their key (a `- |` block scalar holding a brace, a flow mapping after `---`, a continuation at the key's own indent, a %YAML directive, a quoted key holding a colon): a line reader of go-yaml's input loses to the next form — the class, not the cases. The reader is gone. The helper now renders the install — `helm template <release> <chart> [-f …] [--set …]`, the…
  • chart: the no-cluster-state guard reads every receiver, and the wait names what it does not follow (#2780) (227eaa1)

    whyRound-2 findings on the structural helper: two corrected at the class, one refuted.
  • chart: the tip pins the yq its gate requires and names the render's off-cluster condition (#2780) (4bc1db3)

    whyRound-1 findings on the kind-wait helper, each corrected at the class and bound to a witness.
  • chart: the top-key anchor resets on any column-0 key (#2780) (93875e0)

    whyA column-0 key go-yaml accepts but the anchor did not recognize — one starting with _ or a digit — left `top` at `server` across its block: server:\n replicas: 1\n_helpers:\n progressDeadlineSeconds: 300 yielded 420s, sizing the watch from a block that is not the server's while helm installs on Kubernetes' 600 s default — the silent wrong number the helper guards against. The anchor now resets on any column-0 key line (blanks, comments and indented lines keep the block), and the witness covers…
  • chart: the wait is sized from plain digits only, and the gate's yq is CI's (#2780) (aefed00)

    whyRound-1 findings on the structural helper, each corrected at the class.
  • chart: the wait masks a URL's query, stops at a flag it does not know, and probes its yq (#2780) (50c500d)

    whyRound 5 of the review loop: three LOWs, all confirmed, none refuted.
  • chart: the wait masks every userinfo of an argument, and its yq guard has a witness (#2780) (68bcb77), references #981

    whyRound 4 of the review loop: four LOWs.
  • ci: generate OpenAPI checks outside the checkout (#3043) (b9498ea), references #3037

    whyGenerate the spec and client types in a private directory through the same recipe as openapi:gen, then compare against the Git index. Dry checks never write or restore checkout files, including existing symbolic and hard links.
  • ci: size cloud-e2e's preamble budget on arc-runners (#2780) (ab86f31)

    whyThe restack kept main's arc-runners routing for cloud-e2e and the measured on ubuntu-latest (4.0-4.7 m). On arc-runners the preamble (job start to the Helm install) took 5.9-20.3 m over the 90 runs of 3-4 Nov 2026 that reached the install. Up to 14.8 m of that is the steps themselves. In 5 of the runs, Setup Go's cache restore also stalled until its 10 m segment timeout gave up.
  • cloudpublisher,store: a launch whose publish may have landed keeps its slot (#2643) (f83f06d)

    whySubmitLaunch gave its slots back on every exit where `launched` was false, including a publish that reported an error after its message landed — the case 0f6adcef3 fixed for SubmitResume and left here. Its rollback also ended the row with FailRunAtLaunch, an unconditional write: a runner that had already claimed the message (queued → running) executed a run whose document read failed/LAUNCH_FAILED, so even a kept slot was evicted by the next refusal's reconcile. Either way the next admission…
  • cloudpublisher: a park nothing can wake says so (#2643) (acdb7e9)

    whyparkRunOnCredSlot wrote "the run resumes by itself once a slot frees" onto the parked row before the arming ran, and on a store with no durable retry (store.RunRetryStore) armCredSlotRetry only logged a warning and returned nil: the persisted row promised a resume nothing would ever perform.
  • cloudpublisher: a parked launch promotes its attachments before it parks (#2643) (fa1d631)

    whySubmitLaunch returned the park — row saved failed_resumable, retry armed — before PromoteForHandoff, so a launch that named staged uploads and landed on a full key never promoted them. The park's resume is the retry sweeper's, minutes to hours later; the runner reads attachments off the run document alone, and staging may be reaped by then: the run would start without the files its launch named, and nothing would say so.
  • cloudpublisher: a resume refused on a full key says it was put back, not parked (#2643) (e22cc61)

    whySubmitResume answered every slot refusal "run X stays parked", and the revert wrote that onto the run's error: a paused run whose host event met a full key read as parked with a sweeper behind it, and the 429 mapping's comment promised the same. Only a run its launch parked has a retry armed. The refusal now says what happened — refused before publishing, the run put back as it was — and the 429 comment says who asks again: the sweeper for a parked run, the caller for any other.
  • cloudpublisher: a resume whose publish landed keeps its credential slot (#2643) (6e3c5f2)

    whySubmitResume gave its admitted slots back on every failed exit (a defer on !republished). A publish can report an error after the message landed — an ack timeout, a context cancelled past the send — and the runner then claims and executes the attempt while the rollback's CAS, finding the run no longer queued, rightly reverts nothing. The slot was released under a run that runs, and the next admission took it: the cap exceeded by one until the run ended.
  • cloudpublisher: a resume whose re-stamp fails holds no capped slot (#2643) (d0c1135)

    whySubmitResume re-stamps the run's credential fingerprints and then reserves on the newly resolved ones, but the stamp was best-effort: a failed write was a warning, "degrading the ceiling toward uncapped". The slot ledger's reconcile reads that stamp (store.CredSlotHolderStale). A run whose key rotated since its launch, resumed on the new key with the write lost, held a slot its document did not name: it read as a run that stopped spending the key, and the next refusal on another replica evicted…
  • cloudpublisher: the preview mirrors the launch's unfunded refusal (fdee502), references draft#run-fallback #2288 #2485 #2485

    whyThe credential preview is a read model over the launch's credential decision, and it contradicted that decision three ways: under `llm_fallback: none` it showed candidates and a generic "a route nothing of the team's own funds refuses the launch" where the launch refuses `openai (named by verify)`; under ITERION_CLOUD_REQUIRE_LLM_CREDENTIAL it said nothing of the refusal and printed "runner environment availability is unknown" about a run the publisher will not publish; and for a pool-mapped…
  • credentials: a create whose pin re-check fails keeps its 201 and the delete heal reports an identity outage (9e0523d), references #2644

    whyRound 1 of the review loop on leap/trigger-cred-pin (#2644), five findings, five confirmed — none refuted; the three code findings land here, the two comment/doc findings in the next commit.
  • credentials: a pin landing after the delete sweep is swept at its write route and a legacy name cannot dodge the clear (c88cb4e), references #2644

    whyRound 5 of the pin-sweep loop (#2644), two findings, both confirmed — none refuted.
  • credentials: a pin name the Mongo clear cannot address is refused at write (bad3a49), references #2644

    whyRound-3 review of the pin sweep (#2644), three findings, three verdicts.
  • credentials: deleting a pinned key or secret sweeps the pins that name it (c44a9e4), references #2644 #2644

    whyA pin validated at write outlived the record it named: the delete routes (handleDeleteApiKeyIn, handleDeleteGenericSecret) dropped the row and left every stored pin standing, and the resolver never says a word — OrderedAPIKeyCandidates silently skips an unresolvable pin id and the walk seals the team's default, the exact replacement the write-time validators exist to refuse. Webhooks carried the hole since the pin routes existed; the subscription routes gave it a second, longer-lived surface, a…
  • credentials: the memory store stops sharing pin maps and a swept pin cannot be written back (8d767ea), references #2644

    whyRound 4 of the pin-sweep loop (#2644), two findings, both confirmed — none refuted.
  • credentials: the pin sweep and its retry derive the tenant from the route's read scope, which heals the /api/me deletes too (db71203), references #2644 #2644 #2644

    whyRound 2 of the review loop on leap/trigger-cred-pin (#2644), three findings: two confirmed and landed as one class, one confirmed in part.
  • credentials: the pin sweep judges the row at write time and heals an interrupted sweep (3726cd9), references #2644 #2644

    whyRound 2 on the sweep (#2644), on the reviewer's three findings.
  • credpool,cloudpublisher: a key lent through the pool is admitted against its own cap (#2643) (0a2c54b)

    whyA capped API key lent through the credential pool was stamped for the meters (apiKeyFPs) by the pool tier and by the fallback door, but never noted for the admission: the borrower's run held no ledger slot. The donor's own launches reserved on a row that did not count the borrowers, and the borrowers were never refused — the key's max_concurrent_runs, the provider-side limit the cap exists for, was exceeded by every borrower on it.
  • credpool: a call the plan cannot place voids the fine table (#2288) (920dabb), references #2721

    whyReview round 1, three findings confirmed:
  • credpool: a chain fold spanning two providers voids the fine table (#2288) (014665e)

    whyA fallback chain books its failed routes' spend on the ONE delegate event of the route that ends it, and the credential plan keys one fingerprint per (node, provider). The review's probe: a hint-less codex primary burns $5 twice on openai, a claw route on anthropic serves for $0.10 - one anthropic line of $10.10, the openai donor booked $0, no plan miss.
  • credpool: book each folded chain slice on its own route (#2288) (4883e8e)

    whyA fallback chain folds the routes it abandoned into the one delegate event of the route that ends it. The runner booked that figure whole on the event's labels, which broke in three ways:
  • credpool: every delegate outcome names the provider its spend books on (#2288) (5647021)

    whyReview round 2, finding 2 (medium), confirmed and fixed at the class. emitReaskOutcome never set di.Provider, so the schema re-ask's own delegate_finished / delegate_error carried no `provider` word and the runner's join fell back to planProviderFor — the re-derivation that diverges on hint-steered routes: codex under `provider: openai` with a bare `gpt-*` id derives nothing (every re-ask a miss, the fine table voided), pi under `provider: openrouter` with `anthropic/claude-*` derives anthropic…
  • credpool: the claw loop names the slot its client was resolved from (#2288) (258bc2f)

    whyReview round 2: three findings confirmed, one refuted in part.
  • credpool: the fine table answers every observation the plan can name (#2288) (121a7c9)

    whyRound-3 review fixes on the credential plan's join, each with its own red:
  • credpool: the interaction generation plans under its runtime id (#2288) (c058276)

    whyReview round 2, finding 1 (medium), confirmed: the walk records an `interaction: llm|llm_or_human` generation at `<node>#interaction` and siteNode keyed its rows on `<node>`, but ExecuteHumanLLMForInteraction dispatches it as the synthetic human node `<node>_interaction` (applyHooks(node.ID)), so every llm_step_finished it fires joins `<node>_interaction|<prov>`, a key the plan never stamped. Any door-served run whose interaction fired counted a miss and fell back to the coarse whole-attempt…
  • credpool: the plan names the credential the route actually spends (#2288) (a959011)

    whyA shared tier seals a pinned key beside the provider's own forfait on purpose when a route pins the forfait's own provider — the fill's own test asserts the pair. There the delegate precedence decides which of the two a route spends: claude_code and codex read the forfait first, claw bills it as extra usage, pi has no bridge to it. The plan read the channels in key order and named the pinned key for every (node, provider) site, booking a claude_code node's forfait spend on an instrument nobody…
  • credpool: the serving word leaves folded, whatever the authored case (#2288) (0b70bf4)

    whyThe credential plan's keys hold the walk's lower-case words (hint(), providerFromModelPrefix), but two sites let the authored case through: servingProvider computed the folded hint and returned the raw task.ProviderHint anyway, and the authored-fallback chain read its `provider:` field without the fold parseProviderChain applies to the legacy field. A `fallbacks:` route whose `provider:` text is not lower-case — "ZAI", or a ${VAR} expanding to it, both accepted by the compiler's folded lookup —…
  • delegate: a CLI's own reason survives its non-zero exit (a084f22), references #2915

    whyAn agent CLI can report WHY on its STDOUT stream while exiting non-zero and leave stderr empty — opencode 2.x prints `{"type":"error",…,"error":{"type": "provider.no-route","message":"Model unavailable: …"}}` there and says nothing on stderr (measured on 2.2.5). The shared CLI-agent path returned before the parse whenever the process had failed, and only stderr was logged, so the node reported a bare `exit status 1` and the only copy of the sentence died with the dropped stdout.
  • delegate: a host dispatch of a backend no published image ships is refused by name (44c0f88), references #1643

    whykimi, grok and opencode have no CLI in any image iterion publishes, and the sandbox-side typed refusal (CAPABILITY_UNSUPPORTED at run start) cannot see the one plane where no image is involved: a HOST dispatch — sandbox: none, --sandbox none, ITERION_SANDBOX_OVERRIDE=none or the noop driver. There the node still died at exec: <cli>: not found, an explicit failure that named neither the cause nor the cure.
  • delegate: an agent CLI's PWD names the spawn's working directory (5295cb7), references #2915

    whyopencode 2.x resolves its PROJECT from the PWD environment variable, not from the process's real working directory (measured on 2.2.5). A host spawn set `cmd.Dir` to the node's workspace but handed the child the inherited environment, so PWD still named the ENGINE's cwd: the agent read the engine's own checkout — its AGENTS.md, its `.opencode/` resources, its files — while iterion's workspace screen guarded a tree the CLI never opened. That is the screen's premise, not a cosmetic mismatch.
  • delegate: one chokepoint sets an agent CLI's PWD, and a failing turn reports its spend (f6a61a3), references #2915

    whyTwo defects of the same class on the CLI-agent spawn path.
  • delegate: the chokepoint keeps the stream's typed error and the fold keeps an authored variant (c8d3b98), references #2915

    whyFive findings of the second review round, each verified before it was touched.
  • delegate: the hookless-CLI permission-gate refusal is typed, and the opencode policy seam is re-measured on 2.x (005002e), references #11628 #11628 #54 #1640

    whyTwo halves of #1640's current state:
  • delegate: the image-gap gate reads a pin by its source, not its value (39babc5), references #1643

    whyReview round on a21c2e82a, two LOW:
  • delegate: the opencode project screen walks past a git root, as 2.x does (81b93c0), references #2915

    whyThe screen's own contract is to mirror opencode's discovery walk, and this tip measured the walk change: 1.1.19 stopped at a `.git`, 2.2.5 climbs STRAIGHT PAST one and loads the ancestor's plugin. The screen kept the 1.x stop, so a resource one level above the checkout was accepted while the CLI imported and executed it inside the process holding the run's credentials. iterion's own `worktree: auto` puts a node's workspace in a linked worktree whose parent (`<repo>/.iterion/worktrees/`) an…
  • engine: bound contexts reach the board launch lane, and no skip is ever silent — adverse review r1 (62bf4e0), references #1908

    whyF1 (HIGH): the pipeline-board 'launch now' lane launched on a bare context.Background() — no store tenant stamp, so the cloud run doc inherited an empty tenant and the operator-bound contexts of ADR-147 silently skipped. launchTicketNow now launches on a detached (context.WithoutCancel — the runs_launch.go precedent), tenant-stamped ctx carrying the board's own team; the local admission loop's empty team correctly no-ops. Witness: a context-bound bot launched through the lane delivers the…
  • engine: pin the binding-merge lines field by field, gate before fetch, and stamp the board lane's machine actor — adverse review r2 (7bccc93), references #1908

    whyF6 (MEDIUM): five of the six lines of the memory twin's Update merge were green mutants — dropping kind/secret_id/secret_name_for_workflow/ allowed_hosts/updated_at one at a time reddened nothing, so a PATCH moving one of them would keep the old value on the memory twin while the route echoes the request's 200. TestBotBindingTwins_UpdateMovesThe MutablePayloadFieldByField moves each mutable field ONE at a time and reads it back on both twins; the six drop-proofs are replayed red (kind,…
  • envtrust: the opencode argv dialect is the operator's switch (6f15dbb), references #2915

    whyITERION_OPENCODE_ARGV (=1 speaks the 1.x argv to the host's opencode) configures the process against the binary the operator installed; a stored bot var must not choose it. TestEveryEngineEnvNameIsClassified went red on the T49 gate (writable as a bot var and unclassified) and is green with the name in envtrust's operator inventory.
  • forge: let the undo judge the carry's pin clear on the version it owns (#2644) (60d630d)

    whyreconcileCarriedPins cleared each dead record with the value-scoped clear, one write per record, then adopted the version of a fresh re-read. The provision's undo restores a retried migration's candidate row only while the row stands at the version the attempt wrote, and both ends of that judgment were wrong:
  • forge: re-check the pins the undo's restore lands (#2644) (54934bc)

    whyA failed provision's undo restores the row's previous settings: a snapshot read before the provision's own write, written at the version that write reached. A credential record's sweep removes only the pins a row names when it reaches it, and bumps the version only then. A sweep that passed the row while the provision's write did not name the record bumped nothing, so the restore landed the dead pin on a row nothing revisits. Two paths reach that state:
  • forge: the managed secrets the orchestrator deletes sweep their credential pins through the server's store hook (#2644) (2f014c3)

    whyThe forge orchestrator deletes the managed secrets it minted straight through the generic secret store (Deprovision, DeprovisionConnection, undoProjectBot) and never through handleDeleteGenericSecret, whose sweep is what keeps a pin from outliving its record. Those secrets are team-scoped with no user scope, so validateSecretOverrides admits any of them as an operator pin on another webhook config or trigger subscription of the tenant — the "bot posting under a different forge identity" use the…
  • forge: the migration carry re-checks the pins it copies onto a new hook identity (#2644) (d7a20c0)

    whycarryOperatorWebhookSettings copies prev's key and secret pins by value into the row of a NEW hook identity, and the write that lands them is a CAS on the candidate row — never on prev. The same-identity path is covered (its Update is a CAS on prev, which a sweep's clear bumps), and a first migration attempt freezes prev through a CAS too, but a retried migration whose prev is already frozen writes nothing against prev at all: between the read at the top of Provision and the config write — the…
  • lease,forge: a pass verdict stops turning on the order of causes (1acfbab), closes #2825, references #2685 #2916 #2825 #2825 #2825 #2685

    whyA sick-but-alive holder never handed its net over (#2685) because the pass verdict of a sweep that must not abort on its first failure depended on the ORDER its own query listed the items in: lease.TransportFailure read ONE cause, and the two elected forge sweeps collapsed N failures to the first. A refusal (HTTP 422, a permission mismatch) listed before an item whose egress failed (per-node DNS, a 502 from a per-node path) read VerdictRefused — the verdict Observe takes as proof the far side…
  • log: a run's own logger carries its secret matcher, on every surface (2a7ba08), references ADR-149/#2458 #2822

    whyMeasured on acebd5cf1: a run declaring a secret, whose tool prints it and whose workspace snapshot then fails, wrote
  • model: a compaction hook works on its own copy, and its tool inputs cross the boundary (c360870), references #2845

    whyThree holes left by the in-place-rewrite fix, found by the fresh review of the rebased tip:
  • model: a hook's in-place rewrite crosses the boundary, and the screen never writes the live history (b5db4c6), references #2845

    whyTwo hook shapes slipped past the injected-turn wrap's identity rule (the verifier's two lows on the memory-turn tip, left open at its close): a hook that rewrites one of the history's turns IN PLACE — same Content slice, same length, new text — handed the history's own slice back carrying a text it never held, and reached the summariser unscreened; and when a hook returned the input's backing array, the screened copy landed in the caller's live history, a write the caller never asked for.
  • model: iterion's own MCP tools answer to the grant, not mcp: scope (0c9fc9b), references #1801 #1404 #1898

    whyA claw node holding board capabilities died on `model: node "triage" cannot access MCP tool "mcp.iterion_board.add_labels" because server "iterion_board" is not active` as soon as the run inherited ONE ambient MCP server — the target repo's `.mcp.json`, a plugin-contributed server. Iterion's board/runs/watch tools are registered in-process from `capabilities:` and are not catalog entries, so the exact-match loop in checkNodeToolAccess over the node's resolved `mcp:` set could never match them,…
  • model: the memory turn a compaction injects crosses the model boundary (1756b91), references #2845

    whyThe request chokepoint screens every system block, but the workspace-memory installer also re-renders the index and autoload as an injected user turn when compaction fires (memoryPreCompactInjector, via OnBeforeCompact), and no request ever carries that turn: the summariser folds it in first, so a value a memory entry quoted reached the model through the summary's recent-request excerpts, in clear. The turn now crosses at the summariser's door: compactBetweenIterations wraps the hook in…
  • model: the model boundary covers every text a task hands a model (9eaaf6c), references #2845

    whytaskForModel passed the two prompts, the content blocks, the preset focus and the resume answer, while BuildSystemPrompt composed three more texts AFTER the seam: the auto-memory section (the agent's own MEMORY.md, set on the primary build in executor_auto_memory.go and re-rendered for an alternate backend), the calibration fragments, and the skill hints read from the workspace's own .claude/skills/. A value an agent wrote into MEMORY.md therefore reached the next harness in clear. All three…
  • orgsweep: the cred_slots ledger is classified for the org purge (df4a5be), references #2643 #2643

    whycred-key-cap (#2643) added the cred_slots collection; the purge completeness witness, which landed after the tip was cut, refuses an unclassified collection. The ledger is one row per credential fingerprint shared across tenants, so an org purge does not filter it: a deleted org's runs leave its holders as the reconcile pulls a holder whose run document is gone. Repair carried by landing train T50.
  • pools: the guard's reach is production Go, not every Go reader (547d96c), references #2029

    whyThe residual bullet of docs/sovereign-pools.md promised more than the guard delivers: "a guard reports every Go reader of the field". The sweep skips `_test.go`, vendor/ and third_party/ on purpose and says so at the skip, so a fixture reading `.ProvidersServed` lands green under a sentence that claims it cannot. The field's own doc comment was already right where the page was not ("no production code reads it").
  • pools: the providers_served guard asks the type, not the module's text (812da94), references #2029

    whyThe coverage leg of TestProvidersServedHasNoProductionReader counted *ast.Field names across the whole module and only fatalled at zero, so it answered "the guarded field still exists" with "some struct spells that name". Executed in this worktree, with the field RENAMED to ServedProviders and a decoy `poolDTO struct { ProvidersServed []string }` added to non-test Go, the old guard printed `--- PASS`: the field it holds a claim about was gone, the sweep matched nothing, and the leg whose stated…
  • pools: the providers_served guard reports the receiver it matched (4bb9f93), references #2029

    whyTwo claims around that guard reached past what it does.
  • probe: the spec guard reads model ids, bare rows and both count spellings (237a627), references #1110

    whyThe second adversarial round left nine findings on the probe page and its guard; each is fixed at the site the tree shows, none refuted. A third reader then left four low findings on this fix, closed below.
  • proc: the noop driver and codex's SDK spawn hand their child its PWD (8b45d38), references #2915

    whyThe PWD class stopped at the grep's scope. `grep -rn "\.Dir = " --include=*.go pkg/ internal/ cmd/ third_party/` names two more host spawns of an agent CLI that set the working directory alone and inherited the ENGINE's PWD: pkg/sandbox/noop (os/exec on the host, `c.Env = os.Environ()`, no image and no --workdir — the driver a caller pins with FactoryOptions.PreferredDriver) and the codex SDK's two transports (third_party/codex-agent-sdk-go subprocess/cli.go:128, appserver.go:92;…
  • retrypolicy: classify CRED_SLOT_WAIT as transient (#2643) (7dc3abb)

    whyFailureCredSlotWait joined store.ReservedFailureCodes without a row in the automatic-resume table: TestEveryReservedFailureCodeIsClassified was red ("40 rows for 41 declared codes"), and the code fell to DispositionUnknown — AutoResumable false, so the CLI --auto-resume loop would not re-drive a run parked on a key cap, and the queue admission and the runner saw an unclassified code. A slot frees when a holder ends; waiting is the cure, so the row is DispositionTransient.
  • revi: keep GPT convergence board tools reachable with repository MCP (#3018) (80505a1), references #3017

    whyKeep capability-granted board tools reachable when the reviewed repository has ambient MCP servers, and prove the actual tool surfaces of both backends. Record the live Codex fallback recovery and migration requirements.
  • runner: the clone credential honours its secret's sinks (d7271e5), references #2845 #2845

    whyA repo-targeted cloud run clones with the first of forge_token / gitlab_token / github_token its bundle carries a value for, and the runner keeps THAT token in `<clone>/.git/iterion-credentials` — rewritten on every rotation — so the agent's late `git push` re-reads a live one. That store is a file the workspace holds, and no site read the secret's grants before writing it: a `sinks: [prompt, egress]` declaration, whose whole promise is that nothing in the workspace holds the value, got its…
  • runner: the clone credential's grants are judged before the clone (f6c556b), references #2845

    whyThe base tip judged a `sinks:` declaration that withholds `file` right after installGitCredentialStore had written `https://oauth2:<tok>@host` into `<clone>/.git/iterion-credentials`, then took the credential back. A refused run therefore still spent a DNS check, a workspace directory, a forge connection and a clone, and its token sat on disk — in the clone URL git persists in .git/config, then in the store — until the teardown ran. The verdict needs neither: executeRun compiles the workflow…
  • runtime,delegate: pin the fail-closed skill channel, share Task.Hostless, and read ".." escapes exactly (482a8dd), references #1719

    whyReview round 1 follow-ups:
  • runview,server,cloudpublisher: a parked launch answers failed_resumable, not running (#2643) (8a6a762)

    whyA launch parked on the cap returned (0, nil) from SubmitLaunch, and the HTTP handler read the status off the queue position alone: zero meant running, so a CLI, a webhook or the studio was told the run runs while its row said failed_resumable/CRED_SLOT_WAIT and nothing was on the queue.
  • sandbox,envtrust: the path a seam resolved is the path that spawns (#3024) (980b837), references #1643 #2988 #2876

    whyThree PATH-lookup seams asked envtrust.LookPath which binary to run, dropped the answer, and then spawned a bare name, so Go re-resolved the question against the live PATH at exec.Command() time — a different question from the one the probe had just answered, of a PATH that can have moved in between. Nothing held the class closed either: .golangci.yml enables six linters, none of which can forbid a call, and it excludes cmd/iterion-desktop from linting altogether; no test enumerated the sites,…
  • sandbox: the docker exec env travels off the argv (e16d6ae), references #2845 #2845

    whyA per-exec env value rode `docker exec --env K=V`, so every uid on the host read it in the process list for the exec's whole life — the node's, for an agent: the provider credential a sandboxed runner spends and the environment `MaterializeAuthoredShellEnv` hands an authored command both ride that channel. The driver now emits the NAME alone and sets the pair on the returned exec.Cmd, which is where docker reads a name-only flag from; the inherited environment comes first, so the exec's own…
  • server,store: a slot-wait deferral re-tags the retry row cred_slot (#2643) (169b353)

    whydeferCredSlotRetry moved a refused resume through DelayRunRetry, which kept the reason the row was armed with: a run first parked on a usage window (reason=usage_window), resumed by the sweeper and refused on the key cap, stayed tagged usage_window while waiting for a slot. The probe pass itself cannot pull such a row under the default floor — it lists retry_after past now+floor (1 h), and a slot deferral is 2 min out — so the reviewer's pull-forward churn needs an operator floor under the slot…
  • server: a mission waits for a credential slot instead of rejecting (#2643) (26715da)

    whyOf the automatic callers of a resume, the retry sweeper defers a slot refusal, the run watch re-delivers its episode on its own cadence and the workspace hand-off backs off — but an assistant mission made it a final verdict: an activation or an action result refused on the assistant's full key marked the mission capability_blocked for good, and a target resume refused on its key was rejected with the action budget spent. Before the hard cap these resumes never refused.
  • server: a slot wait abandoned past max_wait names the bound and its anchor (#2643) (7b5e643)

    whydeferCredSlotRetry measures max_wait from the park's paid arming (retry_state.scheduled_at), which a usage-window row keeps when its resume finds the key full: max_wait bounds the whole park, the provider wall included, as the circuit deferral does. The abandonment said "the run waited <max_wait> for a credential slot that never freed" — false for such a row, which waited on the wall first. It now names the bound and the instant it is measured from, and the doc comment and byok.md say which…
  • server: every operator surface that resumes a run answers a full key 429 (#2643) (d13e83f)

    whyA resume refused on a key at its concurrency cap got its 429 only through POST /resume. Answering a human gate (POST /answer-human, and an async answer's auto-resume, both through writeSteerError) and delivering a host event (POST /host-event) resume the run too, and fell to 500 — "internal server error" to a client that a retry would serve; the WebSocket answer said "resume_failed".
  • server: opencode's offered effort levels carry the same per-model risk (451eedf), references #2915

    whyThe capability arm withheld `none` because "on 2.x an uncarried variant is a hard refusal, so an offered off-switch would turn a legal workflow into a failed node" — while offering low/medium/high/max, which the same measurement refuses the same way: the names are per-MODEL and iterion passes them through verbatim. A discriminator the measurement does not support is worse than none; an author reading `max` off iterion's own capability response on a model whose options are low/medium/high gets…
  • server: the memory routes take the member rung at their mount (07756fe), references #2401 #2364

    whydocs/cloud-rest-api.md published `member` on all seven /api/memory/* routes while registerMemoryRoutes mounted them with requireAuth alone: any authenticated principal of the team — a viewer, or a rank-0 config_editor (ADR-078) — could PUT, DELETE and import documents in the team's spaces through the public route. requireMemoryWriteAuth returned (r, true) for every non-global visibility, and the handlers called nothing else.
  • store,cloudpublisher: a release names the admission it undoes (#2643) (f40396b)

    whyThe same class as the eviction fixed in bab63e0f2, on the ledger's other write: ReleaseCredSlot pulled every entry of the run. A resume whose publish failed reverts its flip, then undoes its admission — and between the two another resume of the run (the sweeper's, an operator's) can flip the reverted run, re-admit it and publish. The failed resume's undo then pulled the entry the second resume holds: the run executed with no slot while the next launch took it, two runs on a cap of one.
  • store,cloudpublisher: an admission never adopts a resume still deciding its own (#2643) (876f3f1)

    whyA resume claims its run (the flip to queued) before it resolves its credentials and asks the ledger, so for that window the run's document spends the keys its stamp names while its own admission is still to come. The adoption took every such run for a settled spender: two resumes on one capped key overlapping (two replicas' sweepers, two operators) each adopted the other and both were refused on a free slot, then deferred together and liable to collide again until max_wait; a launch adopting an…
  • store,runner,server: a model-idle run keeps its credential slot (#2643) (6d78500)

    whyThe hard cap had a hole on idle→active: a model-idle holder stopped spending (SpendsCredential false on LLMIdleSince), the next refusal's reconcile evicted it and admitted another run, and when the idle run's next model node started the tracker only cleared the marker — nothing re-reserved a slot. With max_concurrent_runs=1: A idle, B admitted, A wakes, two runs spend the key. #661's "idle runs do not count" was the advisory ceiling's exemption; the enforcer cannot keep it.
  • store: a re-admitted holder's entry moves forward, inside the cap (#2643) (bc57d82)

    whyThe Mongo ledger re-admitted a holder — a resume reserving what its run holds — from a plain FindOne, with no write. The reconcile is a read and a write on two collections: a launch on another replica could judge the holder stale from its paused run document, and its $pull, naming the entry it judged ({run_id, at}), then landed after the resume had flipped the run to queued and been "re-admitted". The pull took the entry, the launch pushed its own run, and both ran on a cap of one. The comment…
  • store: an admission adopts the runs that spend a key the ledger never admitted (#2643) (a199bf6)

    whyThe hard cap counted ledger holders only. A run alive when the ledger shipped, or launched while its key had no cap, holds no entry, so setting max_concurrent_runs on a key in use let the admission grant up to max new runs on top of the ones already spending it: a key with three runs alive and a new cap of two admitted two more — a key_overrides pin or a restored key bypasses the advisory chooser, and a single-key provider restores its full key — five runs on a cap of two until the old ones…
  • storekit: the empty pin name is unaddressable, so a legacy empty-named pin takes the rewrite arm (#2644) (0fc2714)

    whyPinNameAddressable("") answered true, but the route guard that refuses an empty name is this tip's: before it, validateSecretOverrides checked the tenant alone, so a stored secret_overrides[""] row is possible. For such a row the Mongo clears took the $unset arm on "secret_overrides." — an empty trailing path component the server refuses — so the sweep errored (the delete answered 500, and so did every retry) instead of clearing. The empty name now reads as unaddressable, like '.' and '$', and…
  • storekit: the two pin stores run one Mongo clear, fenced like their Update (#2644) (d1507e2)

    whyMongoSubscriptionStore and MongoConfigStore each carried a near-verbatim copy of the Mongo clear (the value-guarded $unset with its version bump, the version-CAS rewrite for an unaddressable name, the retry bound), while the memory half was already shared (storekit.DropStringMapValues, PinNameAddressable). The body now lives once, storekit.MongoPinClear; each store hands it its collection, its Get, its version and its two pin fields.
  • store: the filesystem DeleteRun pulls the run from the slot ledger (#2643) (9892532)

    whydocs/byok.md and the CredSlotStore doc promise that a deleted run is pulled from every row at once, but only the Mongo DeleteRun did so: the filesystem twin kept the holder until a refusal's reconcile found the document gone past CredSlotGrace. DeleteRun now drops the run from every in-memory row after the tombstone and the content removal, outside mu (the ledger has its own lock; nothing takes mu then credSlotMu). ReleaseCredSlot and the drop share one pull.
  • studio: row actions name their row; a11y naming scans get ledgers (9d21550), references #2591 #2594 #2591

    why#2591's class is what axe has no rule for: two controls answering to one accessible name ("Delete", "Rotate", "Replay" on every row), and a form primitive whose only "name" is a placeholder, which axe's `label` rule accepts outright. Neither net saw either. This change gives the class one rendered seam, two source scans with ledgers, and fixes the five surfaces the scans found.
  • studio: the forge connection type grafts onto the ForgeConnection schema (0306b1d), references #2760

    whyThe forge connection model moved to pkg/connection as ForgeConnection (#2760), so the regenerated spec names its schema ForgeConnection; the studio's narrowed type grafts onto that name. tsc -b red before on the T48 gate (TS2339 Connection), green after. Repair carried by landing train T48.
  • tool,model: the command narrowing's three fail-closed LOWs, and its live witness (d201316), references #1717 #2905 #2905

    whyFrom the #1717 adversarial convergence round (#2905):
  • trigger: a full-document update keeps the health stamps the marks wrote (#2644) (d758732)

    whyMarkLaunchError and MarkRunOutcome are targeted writes that bump no version — a keepalive firing every tick must not conflict every operator edit — so a PUT whose read predates a fire passes the version CAS and replaced last_error, last_error_at and the last_run_* fields with its stale copy, in both twins. The webhooks twin keeps LastUsedAt across its Update for exactly this reason; the trigger twins kept nothing. The replace was pre-existing, but this tip owns the CAS now.
  • triggers: a subscription's credential pins are a team admin's decision (#2644) (cf9ac34), references #v

    whyThe trigger routes took key_overrides / secret_overrides at the member rung (triggerWriteAuth, mutatingRungAuth), while every other surface reserves the same choice to canManageTeam: a webhook's pins, the team's own keys and secrets. A member could bind any org-scoped secret of the team (a project bot's managed forge token included) to a workflow secret slot of a standing subscription the spine fires as trigger-spine; before this tip no member-reachable surface accepted stored pins (a run…

5.6.0 (2026-11-03) ​

Features ​

  • chart: opt-in namespace compute ceiling (ResourceQuota + LimitRange) (d9bf151)

    whyquota.enabled=true renders a ResourceQuota (requests.cpu/requests.memory/ pods) that rejects sandbox fan-out past the namespace's allocation at admission instead of saturating the cluster's request pool, plus the LimitRange that gives request-less sidecars a default request so quota admission stays open. The runner pool size stays the graceful concurrency lever; the quota is the backstop. CPU stays limit-free (defaultRequest only) — the burstable contract stands. At the ceiling quota admission…

Bug Fixes ​

  • marketplace: the public view of an entry carries no account ids (#3002) (4549270)

    whyThe anonymous catalogue returned entries whole, including the submitter's and reviewer's account ids, the review timestamp and the reject reason. `marketplace.ForViewer` (and `ForViewerAll`) now project the public view on the browse routes — those four fields are cleared unless the viewer is the submitter, a super-admin, or the deployment runs local; the moderation queue keeps the whole entry, which is where the reviewer is needed. The input entry is never mutated. The studio's types say the…
  • orgsweep: the org purge deletes every run with its blobs, and tells the truth (#3004) (6fb1f12)

    whyPurgeOrg deleted the org's Mongo rows and left every run's object-store data behind: with the runs gone, the six key spaces were orphaned and unreachable. It now deletes each run through the RunStore — blobs and collections, under the run's tenant — before any Mongo DeleteMany; a failed run delete withholds the sweep (the org stays pending and any replica retries, idempotently), `ITERION_ORG_PURGE_RUN_BATCH` paces the batch (0 = unbounded, a bad value refuses to start) and defers the rest to…

5.5.1 (2026-11-03) ​

Bug Fixes ​

  • drain captures, preserve compaction summaries and verify Vetty commits (#3006) (7ab0bcf), references #2989 #2916 #2981 #981 #2996 #3011

    whyCentralize the 15 stdout/stderr pipe captures in 11 files. Drain while writers run, restore on panic/Goexit, close both ends, and support repeated intervals. Prove repository sweeps select their execution checkout. Record the pressure/root mutation evidence and keep the flake register within its byte budget.

5.5.0 (2026-11-03) ​

Features ​

  • audit: sign-ins join the audit log (ADR-157) (#2997) (0d0da96)

    whyEvery handler that mints or refuses a session now writes an audit event: `auth.login.succeeded` (org-scoped on the session's active org, a platform row when there is none) and `auth.login.failed` (platform scope, typed reason). The attempted identifier is never stored in clear: `meta.subject_hash` is an HMAC-SHA256 under a key derived from the signer (`iterion/audit/subject/v1`), truncated to 16 hex. The client IP comes from `clientIP` (forwarded header behind a trusted peer), the refresh is…
  • cloud: a converged auto_merge run is landed by the instance and a subbot declares the landing (#2713) (6556933), references #618

    whyThe launch intent (auto_merge) had no instance-side consumer on the cloud plane: the runner banks the run's tree, the doc records the intent, and the merge of the banked branch waited for a human — a campaign lot whose own gate said converged waited 6-9h for that click (#618). And a supervisor bot could not declare the intent on a lot it launches: the subbot node had no landing surface.
  • connection: the connector webhook lane — a Forgejo event believed on the connection's grant, declared by the package (f5b1c48), references #2761

    whyThe last piece of #2761: the inbound half of the connector contract, one vertical proof on Forgejo.
  • server: the cloud authoring surface resolves a declared fragment (4a85fd9), references #65 #1934 #2865 #2827

    whyA consumer bundle that declares `dependencies.fragments` instead of shipping the fragment launches fine since 327c064d6, but that state was unreachable from the studio: the bot-source write gate is an absolute refusal over main.bot, so the `import` the declaration exists for could never land. A probe bundle declaring forge-auth whose workflow USES a node of it answered E046 for the import AND C002 for the node that references what the fragment declares, so tolerating the import alone would…

Bug Fixes ​

  • bots: Vetty decides a lost alignment from the workspace and the PR's commits (#2983) (308d91e), references buildkit-operator#50 bko#50

    whyOn a dependency PR that needs code alignment, Vetty commits the fix and pushes it; that push fires the forge's synchronize event, which relaunches the review on the alignment commit itself. On that pass align re-applies the same edits onto a tree that already carries them (a no-op), commit has nothing to commit, and the shas read an unmoved head — the exact shape of a LOST alignment. commit_check then held the PR red (hold_lost_alignment) and the arming the first pass placed could never fire.…
  • cloudpublisher: every launch-persistence attempt saves the run from its initial state (bd8f665), references #2723

    whyA launch persisted under a scheduler's admission is written inside the Mongo transaction cloudsched.MongoStore.WithSchedule installs, and the driver replays that transaction's callback on a TransientTransactionError. The callback closed over one run value, and mongo.Store.SaveRun advances r.CASVersion in place on a successful write, so the save was a read-modify-write on the caller's own state: the aborted attempt's insert was rolled back but its version advance was not, and the replay offered…
  • connection: the connector lane refuses a dead credential and honors the operator's narrowings (dc715e3), references #2761 #2761

    whyReview round 1 on the #2761 lane, four findings, each pinned by a witness that was watched red under its mutant first.
  • detect,reviewtopology: BinaryFound holds at every step and on every CLI, the facade route's auth is pinned (#1221, review round) (30bd5b3)

    whyThe adversarial review of the route-level publication:
  • dsl: merge_into is refused at compile like merge_strategy (#2713) (b07089b)

    whyRound-4 review, finding 4 (low). validateSubbotLanding checked merge_strategy's enum but not merge_into: a free-form value — a padded branch name, a stray word — compiled, and the child's finalize died at merge time with merge_status=failed, the router burning its bounded retries before the alert. Loud and bounded, but the refuse-where-written treatment merge_strategy gets names it at compile, where the author is still looking at the line.
  • dsl: the C004 remedy names the subbot merge_strategy carrier (#2713) (2c3ac5d)

    whyThe subbot's invalid `merge_strategy:` rides C004 as one of the "refusals that have no code of their own" — but both surfaces that enumerate that family named only the tool-recipe members: the catalogue's fix line (what HintFor serves to `iterion validate`, the studio badge and the MCP result) and the C004 row of docs/references/diagnostics.md. An author refused for `merge_strategy: rebase` who grepped the code landed on a remedy that did not know the refusal existed — the drift…
  • fswatch,runtime,sandbox,server,git: a parse reads the subprocess's stdout, never the stream the environment talks on (1ba3438), references #2781 #2900

    whyThe raw-watcher inventory ran `go list -e -test -export -deps -json ./...` and decoded cmd.CombinedOutput(). Where the module cache is read-only the toolchain says so on stderr, that line lands inside the JSON stream, and the decode stops at "invalid character 'g'" after 14.6 MB: the test rendered its verdict on the machine instead of on this module's Add calls. CI is green because its cache is writable, so the class only ever fires on an operator's tree.
  • model: a wire-reported stream stall carries iterion's own idle verdict (e8dc48d), references #2052

    whyclaw's SSE readers run their own idle watchdog (CLAW_STREAM_IDLE_TIMEOUT, 5 min by default) on the OpenAI chat, foundry and Responses wires, and it fires well before iterion's hot tier (15 min). Driving the chat and Responses wires into a real stall on acebd5cf1 and reading the verdicts: the stall arrives as a plain retryable *APIError — isRetryable true but on the STANDARD budget (3 attempts, not the transient 6), and isDelegateRetryable false, so the delegate loop around the node does not…
  • routing: a finalize that refused to merge is never landed behind its back (#2713) (65c251a)

    whyThe run doc's merge_status=skipped is the worktree finalize's own refusal to merge — a wip-banked HEAD (unreviewed output the run chose not to commit) or no merge target at all (a detached HEAD, whose only "target" would be whatever the repo root has checked out at merge time). Neither router lane read it: EvaluateAutoMerge granted landing on the intent alone and Evaluate granted it on success_when, the only guard downstream excluded merged, and the merge action itself claims from skipped by…
  • routing: the sweep nets read the merge_into opt-out folded (#2713) (9a6d766)

    whyRound-4 review, finding 1 (medium). The mongo sweep net's intent arm matched merge_into exactly ($nin ["none"], case-sensitive, untrimmed) while the ONE canonical reading — routing.EvaluateAutoMerge, now pkg/store.MergeIntoOptOut, the FS twin beside it — folds the field (case-insensitive, trimmed, the way the runtime's finalize reads it). A run doc carrying " None " or " none " was admitted by the mongo arm and declined by the router's pre-claim gate on the same doc: the decline settles no…
  • routing: the sweep nets refuse the unbanked intent shape (#2713) (c1c781a)

    whyRound-3 review, finding 2. The intent arm admitted the one declined shape its own doctrine forgot to mirror: the router's bank gate declines a merge decision on FinalBranch/FinalCommit empty BEFORE any claim (the terminal status lands before the bank push, measured up to 10+ minutes on large repos), yet both nets swept the shape — the FS twin filtered failed/none/skipped/bank-error but not unbanked, the mongo twin likewise, and the conformance suite pinned the unbanked run as swept ("an…
  • runview: the persistence merge target reads the current vocabulary folded (#2713) (bf3eda2)

    whyresolveMergeTargetForPersistence matched "current" with an exact string compare while every other reader of the same field folds it — the compile's validation (ToLower+TrimSpace), store.MergeIntoOptOut (EqualFold), the engine's own finalize (resolveMergeTarget). A merge_into spelled "Current"/"CURRENT" — accepted by the folded vocabulary — reached the instance intent landing (API-launched cloud run; unreachable from DSL subbots, whose finalize folds) as a literal branch name: the repo-targeted…
  • sandbox: the image ENV screen answers the ITERION_ namespace by sub-class, so a planted runner switch is emptied (3c76a84), references #2844 #2859

    whyThe image ENV screen partitions the deny class by what the runtime does when an IMAGE bakes a name in: the credential/routing sub-class is overridden with `--env NAME=`, the rest is reported and keeps the image's value (ARB-121). `ITERION_` was on the reported side WHOLESALE, for a measured reason — iterion's own images bake ITERION_VERSION, ITERION_COMMIT and ITERION_BOTS_PATH — and the namespace is mixed, so the credential/routing members rode with the rest.
  • secrets,sandbox: one endpoint identity for every routing verdict, bigmodel on the egress (3713c54), references #2891

    whyTwo bounded mediums from the kimi52 #2772 round-3 H-10, closed by the round-1 and round-2 reviews together.
  • server: the gate sweep's pass reports the forge its offers reached, not its scan (99b796d), references #2685 #2825

    whyMeasured on acebd5cf1: one pass over a window holding a dead gating run whose every offer failed with a *net.DNSError inside a *url.Error reported `progressed`, and so did a pass over a window where every run owed nothing, and one whose run document could not be read — four windows, one verdict. The sweep recorded `Succeeded()` for its ListNotifiableRuns scan, and lease.Pass.Verdict answers VerdictProgressed on any success, so the offers the pass then made could not change its verdict. A holder…
  • server: the router's attach log names both routing carriers (#2713) (fe4b185)

    whyRound-4 review, finding 3 (low). The startup line still said "policy-carrying terminal runs are decided by their launch-frozen contract" — half of the two carriers the router has served since the intent lane landed. The line is the incident-time grep surface: an operator grepping it for what decides a run would read a router that decides contracts only. It now names both — the launch-frozen contract, or the auto_merge launch intent.
  • subbot: a landing intent a worktree:none child cannot act on is refused at launch (#2713) (0254882)

    whyC329 refuses auto_merge on a subbot node without `isolated:` — the parent's compile closes the dead intent where the node is written. One level down it stayed open: the child .bot's own `worktree: none` removes the finalization the isolated child was granted, and nothing read the child's declaration — the parent's compile never opens the child .bot, the engine honours none by running in place, and the intent sat on the run doc with FinalBranch empty. Nothing signals from there: no local merge…
  • subbot: a landing intent the cloud plane cannot act on is refused at launch (#2713) (e89b6eb)

    whyRound-3 review, finding 1. The C329 remedy — declare `isolated: true` — produced a dead intent on the cloud plane: a run store with no filesystem root runs every `worktree: auto` child IN PLACE (engine_run's decisive arm), so wtCtx stays nil and finalizeOnExit returns at once; a subbot child is never banked either (the bank serves queue-delivered runs only, a child rides its parent's delivery), so FinalBranch stays empty forever and the router's bank gate declines silently on every sweep — no…
  • subbot: every child-launch host arms and refuses the landing intent (#2713) (35d0bf4)

    whyThe launch guard refused a landing intent the child cannot act on at two of the four child-launch sites only: the CLI runner `iterion run` and `iterion resume` share (pkg/cli) and the dispatcher daemon's direct engine path (pkg/dispatcher) wired WithIsolatedChild but neither WithAutoMerge/WithMergeInto/WithMergeStrategy nor the guard, so a compile-clean `auto_merge:` subbot never landed there and no diagnostic said why. Both hosts now call RefuseUnlandableSubbotLanding right after the child…
  • subbot: the launch guard reads the engine's worktree predicate (#2713) (f3aef79)

    whyRound-4 review, finding 2 (low). RefuseUnlandableSubbotLanding refused only `worktree: none` while the engine builds a child worktree for the canonical `auto` and NOTHING else (engine_run's decisive arm): an unset or foreign mode over a rooted store passed the guard yet ran the child in place — the same dead intent. Unreachable from the shipped hosts today (the compile canonicalizes empty to "auto" and C142 refuses the rest), but the arm mirrored the author's vocabulary instead of the engine's…

5.4.1 (2026-11-03) ​

Bug Fixes ​

  • claw: the engine's in-process clients refuse the ambient header block (#2244) (7b4cded), references #1476

    whyThe sandbox crossing refuses an ambient credential header line since T10, but a non-sandboxed claw node resolved its clients in this process, where claw's own identity pass read ANTHROPIC_CUSTOM_HEADERS and CLAW_USER_AGENT from the operator's shell — a credential line in the block would authenticate the node's request in place of the credential the engine resolved, one backend away from the leak #1476 closed for claude_code.
  • claw: the UA chain's CLAW_USER_AGENT leg trims like claw's own read (#2244) (b618a13)

    whyengineClientUserAgent returned CLAW_USER_AGENT verbatim while claw's own read of the same variable (vendored identity.go ResolveIdentity) and the ITERION_LLM_USER_AGENT leg of the same function both trim: a padded value rode engine-resolved wires verbatim, and a whitespace-only one replaced claw's honest default instead of falling through to it.

5.4.0 (2026-11-02) ​

Features ​

  • cloud: surface the copy-paste setup prompt on README, docs home and the landing (#2908) (313992b), references docs/getting-started.md#the-prompt #2005

    whyThe prompt lived two clicks deep (docs/getting-started.md#the-prompt, the README's Getting Started); the product landing and the docs home did not carry it at all. Each surface now leads with it, labelled as the cloud onboarding:

Bug Fixes ​

  • auxllm: the server-side auxiliary LLM surfaces resolve through one credential channel (#2645) (e958555), references #2005

    whyThe merge-conflict resolver, the supervisors and the session board each built their own model.NewRegistry() off the server process env — three sites for one decision: which credential funds run-derived content sent from the server. They now resolve through pkg/backend/auxllm.Registry, with identical behaviour today, so the run's gateway credential (#2005 PR-C') branches one site instead of three.
  • claw: make the executor workDir field atomic against live workspace reads (#2897) (a3ffd4d), references #2889 #2870

    whySetWorkDir/WorkDir traded a plain string while the workspace provider reads the field live on every bash/read/write/glob/grep call - the detector reported Read WorkDir (ReadScope.workspaceLocked) vs Write SetWorkDir. The field becomes an atomic.Pointer[string] and every internal reader goes through WorkDir, so the class closes by construction: any future access to the field is forced through atomic ops.
  • claw: the repoRoot field gets the workDir atomic treatment (#2897) (0b25fd7)

    whyThe round-1 review flagged the neighbor field: repoRoot keeps the same pushed-field/live-reader shape workDir had pre-fix - the engine pushes it at run start (engine_run.go / resume.go, alongside SetWorkDir) while buildTask reads it live on every node (task.RepoRoot) - with no reader per tool call today, so no push/read pair ever overlapped. Refuted as a live race, confirmed as the latent class: the new SetWorkDir doc advertises concurrent-with-Execute safety, and repoRoot is the one neighbor…

5.3.1 (2026-11-02) ​

Bug Fixes ​

  • connection: a read miss lists the tenant's aliases to an untrusted run (6a768d8), references #1802 #2810 #2896

    why#1802 refuses a mutating action before the store is read, so a fork's misspelt alias probes nothing there. A READ action resolves for a fork — the lane's contract — and a misspelt alias on it reached suggestAlias, whose error ends with the aliases the tenant does hold: the inventory a mutation is refused for, handed to the same reader one spelling mistake later, and a probe of which connections exist.

5.3.0 (2026-11-02) ​

Features ​

  • prod-watch: the watch adopts the session's memory read-only after a window (6ecd9ac), references #2835

    whyA session runs the lifecycle on a fork of the watch state under <state_dir>/session/ and never writes the watch's; the watch's first tick past the window replayed it on its own cursors — an incident the session escalated posted again, the window's lines were re-read up to max_window_minutes. The tick now ADOPTS the fork's memory instead: the incidents it alerted (so they are not news), its cursors where they are ahead of the watch's (the Loki frontier, Sentry's since). The fork is read…

Bug Fixes ​

  • ambient: an empty .git directory is not a repository — git's own criteria (50a30df), references #2866

    whyRepoRoot stopped its upward walk at the first .git directory, however empty, while git's own discovery (is_git_directory: a HEAD that validate_headref accepts — a "ref: refs/…" symbolic ref, a hex object id, or a symlink reading refs/…, dangling included — plus objects/ and made RepoRoot answer /tmp where git says "not a git repository". isGitEntry now holds a .git directory to git's criteria and keeps refusing an empty .git file; RepoRoot and IsRepoRoot, the only walks on this predicate, agree…
  • claw: a subagent's inherited ask verdict degrades to a refusal instead of pausing the run (2b5af64), references #2812 #2877

    whySuite de #2812, which withheld the ask TOOLS from a child conversation at filterSubagentTools. The policy channel remained: the child inherits the parent loop's permission policy (ParentPermission, by design), and an `ask` verdict on a tool the child holds raised delegate.ErrAskUser from inside the parent's tool call — the run paused on a question attributed to nobody, and the child's conversation was not persisted for the resume to return to.
  • claw: the bash, write_file and read tools follow the run's workspace, not the registration one (0ee51c9), references #2870 #2889

    whyRegisterClawBuiltinsWithScope captured the scope's workspace string at registration, so on a worktree:auto run bash's shell cwd, write_file's and file_edit's relative-path rebase and the read tools' containment resolved against the construction-time checkout while the prompt path and the command/skill lookups followed the worktree the engine pushes through ClawExecutor.SetWorkDir (#2870).
  • claw: the sandboxed runner installs the default lifecycle hooks too (9d511e0), references #2798 #1424

    whyThe re-measured integrations.claw-lifecycle-hooks row and the superseded docs/e2e_coverage.md note promised the dangerous-command refusal unconditionally, but the in-container __claw-runner built its ClawBackend without WithBackendLifecycleHooks: on a workspace whose settings carry no hooks opts.Hooks stayed nil, and a `rm -rf /` call executed in the container instead of being refused — unlike the in-process path the wiring test drives. The runner now rides the same NewDefaultLifecycleHooks the…
  • connection: a catalog refusal lists the tenant's packages to the tenant's runs only (b284d33), references #1802 #2810

    whyA connector-not-found refusal ended with the ids the operator installed ("it holds: forgejo, probe, …"), and the layered lookup joins every tier's reason into the error a run reads — so on the fork review lane an outside workflow named any id it liked and the posted review carried the list, with the case-fold hint as an oracle for probing the installed ids one at a time. #1802 decided the symmetric case one layer down, for the connection aliases; the package list is the same fact about the same…
  • model: a minted grant referenced in a script: body is refused, never written to its temp file in clear (49b5509), references #2827 #2875

    whyA {{vars.forge_publish_token}} in a script: body rendered the bearer as a language literal INTO the script's temp file — on disk for the node's lifetime, pushed into the workspace by a copy-based sandbox — while the previews (#2827) mask it and docs/secrets.md says a script body resolves nothing. The command: path is safe because its placeholder form is shell and MaterializeAuthoredShellEnv swaps it for a child-env reference; no such swap exists for a script, whose env read is each…
  • prod-watch: the sentry-cursor adoption never writes an at the plan refuses (9dda05e), references #2835

    whyA fork whose Sentry cursor lost the offset on `at` — hand-edited, or written by an older bot — passed the adoption guard (it types `since` and the identity only) and landed `at: ''` in the watch state. The plan judges every cursor whole (armed_at, since, at each offset-ISO), so the next tick refused the state by name and the watchdog stayed down until an operator repaired state.json — exactly what the lenient reader promises never to do. The stamp falls back to `since`, the value the guard…
  • prod-watch: the state commit judges paths in git's own frame (4e65598), references #2838

    whyWith state_commit=true a state dir spelled `./x`, holding a non-ASCII byte, a `"` or a `\`, or given absolute never committed: the preflight compared git's quoted, repo-normalized paths with the dir as typed, so every tick died refusing path(s) outside the state dir and the state never advanced on an ephemeral runner. The dir is now framed the way git frames paths — relpath against rev-parse --show-toplevel — and every reader of git's output takes -z (NUL-separated, unquoted): the staged diff,…
  • repomap: the generated-artifact walks skip the operator-local .local tree (b1c5403), references #2898

    why.local/ is gitignored scratch, but treeskip did not name it, so a host's junk with Go files (.local/scratch/codex-sdks on the operator's host) landed phantom rows in docs/references/map-packages.md and turned the session's task check red on a clean main. The agreement guard plants a .local canary in its floor, so the maps and the graph both prove they skip it.
  • runner: the fetch loop recovers from a NATS leadership change in seconds (#2890) (10cabf3), references #2904

    whyThe Oct 30 wedge opened with `nats: leadership change (backing off)` and the fetch loop never recovered: the election can freeze the durable consumer's raft state, after which every fetch long-polls an empty answer forever — silent, since ErrNoMessage logs nothing. The stall detector landed this morning (#2904, 7581a994b) is the posture net, but it only trips after its window (default 10m) and only while messages keep arriving. The ticket asks for seconds, not the consumer's life.
  • sandbox: a phase cut reports the wedged in-pod tar, whichever leg died first (6c27efa), references #2854

    whyThe copy pipe's two legs die at the phase deadline together, and which death the error named was the scheduler's order: a loaded runner let the host tar still be starting at the cut, the host leg led, and the typed error lost the in-pod cause ("in-pod tar extract") the test reads (FAIL 0.53s, run 40919550421). When the phase's context is already done, the far side's death now leads — the local tar died of the same cut through the pipe. The test's payload outgrows the pipe buffer, so the host…
  • sandbox: the kubectl lookup witness stops reading the pod it runs on (6cedc55), references #2876

    whyTestDetectResolvesNoKubectlUnderAPlantedPATH is red 2/2 on the ARC CI runners (41418469570 and its rerun): those hosts ARE kubernetes pods, the service-account mount exists, and Detect legitimately succeeds — the first assertion wanted the 'not running in a kubernetes pod' error. The namespace seam becomes a var and the witness points it at an absent scratch path, so it judges the lookup order on every host shape. The CI-log red is the mutation witness this fix answers.
  • sandbox: the phase cut carries the host tar's own cause too (cc92769), references #2854

    whyThe cut branch of the copy pipe returned the wedged in-pod tar's cause alone: a host tar that named an independent diagnostic before the cut reached it ("file changed as we read it") lost that tail — the same loss-class the in-pod side was closed against. Both tails now travel in the cut branch's error, the wedged far side still leading; an empty host tail renders exactly as before. Witnessed by mutation: the reverted branch reds the new test on "the cut branch dropped the host tar's own…
  • security: the LookPath class closes at its last sites, and the seam's justification covers both planting lanes (9a73dc6), references #2876 #2876

    whyReview round 2 on the #2876 fix: the class is the rule everywhere, and four exec.LookPath sites still answered the authority question against the live PATH:
  • security: the remaining LookPath sites resolve under the operator's PATH, never a planted one (a2d37aa), references #1945 #2809 #2876

    whyWhich executable a launcher-started process runs is an authority question (#1945 at the config, #2809 at the envtrust.LookPath seam), and these sites still answered it against the LIVE PATH — which a project .env answers when it planted one (applyDotEnv fills only unset names, so a planted PATH means the operator's process had none). Each now goes through envtrust.LookPath, whose refusal is loud (exec.ErrNotFound) and reaches the caller exactly as a miss always did — no signature changes:
  • studio: the clamp announces its text once and leaves the control's tab stop alone (f73b36a), references #2790 #2803 #2790 #2803

    whyClipping is visual: the whole string stays in the accessibility tree as the block's own text, so the sr-only copy and the aria-describedby pointed at it announced a clamped block two and three times. Both channels go, together — a describedby whose target is gone is an axe aria-valid-attr-value violation of its own — and the standalone block keeps its focus stop and data-clamp-text. (#2790)
  • train: T40 coverage-chain landing repairs — stabilisation table re-derived, shield git calls guard auto-maintenance (c4fb9de), references #2795

    whyThe bots-coverage split of the uncovered-catalog aggregate into six honest per-bot rows re-derived the matrix (409 rows) without carrying the plan's per-surface summary (still 404): the table now matches the committed matrix, as the committed-derivation guard demands. The one-tool-run shield harness predates the git auto-maintenance guard: its two direct exec.Command("git") callers now pass git.NoAutoMaintenance like every other test git caller.
  • treeskip: skip .gomodcache too — the other gitignored tree whose expected content is Go (331918a), references #2898 #2898

    whyThe review of the .local fix named it: a sandboxed go command dropping its module cache in .gomodcache/ would flood the generated maps with phantom rows, the same class as #2898 at a larger scale. The agreement guard's floor pins both.

5.2.0 (2026-11-02) ​

Features ​

  • claw: honour a workspace command's allowed-tools frontmatter (#1717) (685b427)

    whyA command that narrows itself with allowed-tools: was bounded on claude_code and ran with the node's full tool set on claw, on both surfaces of the capability. It now narrows on both:
  • runner: serve the sandboxed board/runs MCP transport on the cloud runner pod (#2632) (1c1c94d)

    whyA sandboxed node with board.* capabilities on iterion.cloud could not write: the runner pod built its engine without runtime.WithBoardMCP and its executor without BoardRegister/BoardRevoke, so the delegate disabled the board MCP and Acci/rgaa-audit ran with post_to_board=false.
  • runner: the stall onset self-heals the runs consumer (#2904) (7581a99), references #2890

    whyThe Oct 30 wedge froze the durable consumer's raft state - deliveries stopped while pull requests kept waiting - and never self-resolved: only deleting the consumer cleared it, by hand, 15 hours later (issue #2890). The stall detector's onset now carries the heal: Recreate deletes and re-creates the durable consumer, guarded by DisableStallSelfHeal (ITERION_RUNNER_DISABLE_STALL_SELFHEAL, wired through pkg/config and cmd), a pure three-arm decision function (off switch / unreadable posture /…

5.1.0 (2026-11-02) ​

Features ​

  • dsl: C328 — naming a host tool without its capability is told at compile time (#1801) (c51f98b), references #2857

    whyThe capability gate's verdict was only learnable at run time: the grant filters registration, so an agent naming create_issue without board.create loses the tool to a process-log line and a tool node whose bare command resolves to a host tool fails dispatch on "unknown tool" — a call that still reaches the board layer is refused on boardops.ErrCapabilityDenied. The compiler now says it, and names the capability to declare:

5.0.2 (2026-11-01) ​

Bug Fixes ​

  • runner: the pending sampler detects a wedged runs consumer (#2895) (d643652), references #2890

    whyA stall - work held by the consumer while deliveries stop - used to be silent: the Oct 30 incident held five messages in the ack-pending set for 15 hours (NumPending reading zero: they were delivered, the redelivery machinery was frozen) while messages kept arriving and every pod polled an empty fetch. No log line, metric or alarm named it (issue #2890).

5.0.1 (2026-11-01) ​

Bug Fixes ​

  • claw: the gateway table memo keeps provenance, and a planted gateway key crosses empty (1a4aae4), references #2813 #2813

    whyTwo blind spots of the #2813 gateway fix, found on re-review:

5.0.0 (2026-11-01) ​

⚠ BREAKING CHANGES ​

  • rewrite: skip the rewriter chain under an active sandbox by default

Features ​

  • dsl: a node's capabilities: [] is a declared empty grant, not an inheritance (cc4cbeb), references #1801

    whyThe empty-list trap of #1801: an agent/judge writing `capabilities: []` compiled to nil through parseBracketList, and a nil capability list is the inherit marker — so the author who emptied the list silently held every capability the workflow grants instead of none.
  • model: resolve named subagents from <workspace>/.claude/agents/ on claw (7dd3504), references #1717 #1714

    whyThe agent tool's subagent_type now names a file-backed agent of the node's workspace — the file a plugin's contributes: agents mirrors into .claude/agents/<name>.md, which claude_code discovers natively and claw left inert. The resolver lives iterion-side (ARB-83 option A): model.NewSubagentRunner executes every spawn a workflow drives, for both backends, while claw's own loop is not the one iterion pilots.
  • rewrite: make the rewriter chain's sandbox placement consultable behind ITERION_REWRITE_SANDBOX (56846d9), references #2007 #2007

    whyThe rewrite chain (rtk and plugin rewriters) rewrites a node's Bash commands host-side, in the launcher/engine process, and the sandboxed CLI or tool node runs the rewritten command in-container. For a run the operator asked to be isolated, every Bash command therefore passes through a host-side subprocess the sandbox boundary otherwise excludes (#2007).
  • rewrite: skip the rewriter chain under an active sandbox by default (989b501), references #2007 #2007

    whyOperator decision on #2007 (Q44=A): the default flips. With ITERION_REWRITE_SANDBOX unset, a task whose commands execute inside a sandbox no longer passes them through the host-side rewriter chain — the claude_code PreToolUse hook is not installed and the tool-node recipe leaves the command verbatim, so every Bash command of a sandbox-requested run stays inside the sandbox boundary. rewrite.SkipsSandbox now returns true for a sandboxed command unless the knob explicitly pins "host":…
  • runtime: a loop declares its work signal, and the budget guard prorates the crossing (d65f0a8), references #2677

    whyThe back-edge affordability guard prices the next iteration at what the previous one consumed — so a loop whose iterations shrink (a re-ask of the still-pending subset, a retry of the failed batch) is priced at its first, largest pass and declined on a stale measurement: the ticket's run declined a 43-item re-ask priced as the 104-item round, on duration, and shipped 43 criteria unanswered. The `guard: off` half landed already; this is the proration half, proposition 1, now that #1690's bound…
  • sandbox: sandbox.image names a stock class, and the rgaa variant carries ultra11y + Chromium (efdd5e8), references #2632

    whyA workflow's sandbox block declares a capability CLASS by bare name (`image: rgaa`): pkg/sandbox/imageclass holds the closed, operator-owned vocabulary (slim, full, sec, browser, rgaa), and the runtime resolves a known class to the published variant image — ghcr.io/socialgouv/ iterion-sandbox-<class>, pinned to the running binary with the same :latest registry fallback the built-in default carries. Everything else keeps its reading: a full reference is the operator's own image (the escape…
  • studio: open every .bot of a bundle in the canvas, not only main.bot (e72ed1e), references #1650 #1659 #1650

    whyThe bundle files drawer routed main.bot to the canvas and every other file — .bot fragments and companion workflows included — to the inline text editor. The routing special case dies (#1650, Q42=A): one canvas route per workflow file, botSourceEditorPath(team, slug, rel), which EditorTabHost's openFile already loads as its own unit — a companion workflow's imports parse as its unit, a bare fragment as its own document, parse diagnostics included.
  • validate: the claude_code capability drift is disclosed where an author reads — validate --explain and the launch picker (32186ac), references #1651 #1579 #1651 #2815

    whyOn claude_code a tools: list becomes --disallowedTools over a roster iterion does not own, and what the list actually bounds was written down nowhere an author reads: five roster natives (AskUserQuestion, SlashCommand, BashOutput, KillShell, ScheduleWakeup) leave EVERY node whatever its list says — a headless session has nobody to ask, no background shell and no later turn — and Agent/Task leave any list that does not declare agent. ARB-50 withdrew the per-node compile warning #1651 had planned…

Bug Fixes ​

  • backends: a claw subagent holds no ask channel (07d82f9), references #2812 #2812 #1404 #1940

    whyA claw child draws its tools from the parent node's effective set, which interaction: stocks with the ask family — so a child could call ask_user from inside the parent's tool call, raising ErrAskUser through the wrap to the parent's own askUserResult path: the run paused on a question the child asked, attributed to nobody, and with the child's conversation unpersisted the resume replayed the parent's pending tool_use and paid the child again (#2812).
  • backends: carry the gateway catalog provenance on the Result so it reaches the persisted node output (b005487), references #2786

    whyThe promise (ADR-122 §resolution, claw_backend.go's own comment) is that the gateway provenance rides the output as _gateway_spec. It did — until the next hop: stampDelegateOutputMeta owns the reserved _ namespace and wipes every _-prefixed key it does not project from delegate.Result (trusted metering: a model-written key of the same name must not survive), and _gateway_spec rode the map alone. The backend's return value carried it, the node_finished event an operator reads did not — the live…
  • backends: gateway auto-selection reads the whole chain, warns once (7d358bc), references #2785 #2812

    whyAdversarial review round on 657f148f7.
  • backends: gateway route auto-selection consults the parity predicate (f55ee42), references #2785 #2785 #1423

    whyA node naming an openai_compatible/* route and no backend was handed to whatever host auto-detection found first — claude_code on any machine with the claude CLI — and the run aborted at task build, 80 ms in, on the refusal the gateway parity rule already types (#2785).
  • bundlelint,toolcatalog: the durable-write question reads readonly: and asks whether the tools: list reaches the backend at all (be7bdff), references #2882

    whyNo arm of the durable-memory capability reading (C274) read `readonly:`: on claude_code the flag withholds the file writers while keeping the shell, on codex it is a read-only OS sandbox, on pi a readers-only tool list, and claw/kimi/grok/opencode ignore it — so `readonly: true` beside `tools: [write_file]` on claude_code was credited as the write path the spawn withholds. Same class: pi, kimi, grok and opencode never receive the `tools:` list (toolcatalog.ReceivesToolList), so a declared…
  • bundlelint: ask the durable-memory gate question per route, and hand the corpus twin the same predicate (44c8fb9), references #2881

    whyC274's gate question was asked over the UNION of every route's spellings: a writer held on ONE route and refused there by the permission gate was credited through another route's surviving spelling — `deny` + `allow: ["Write*"]` leaves `Write` (the claude_code route) while removing `write_file` (the claw fallback where the writer actually sits), and the gate-refused-writer bundle went from warned to silent. A call is made on the route the run took, under that route's spelling, so the question…
  • claude_code: the structured-output spawn carries the secret hooks (c14d748), references #1672 #2782

    whySince #1672.2 the formatting pass is bounded by the task's policy rather than by withholding the roster, so a tool the policy allows can run on that spawn — but Execute wired installMaterializeSecretsHook (PreToolUse: swap __ITERION_SECRET_<name>__ for the value immediately before the tool runs; PostToolUse: turn known values a tool's output quotes back into placeholders) and formatOutput wired neither. A gated node is exactly the one with a declared secret boundary, and a call carrying a…
  • claw: a planted gateway route never crosses the sandbox either (17a9a0d), references #2844 #2813

    whyReview of the #2813 fix found the sharp case standing one branch over: the gateway block of forwardableProviderEnv read compatgw.FromEnv (os.Getenv) — the LIVE environment — and OPENAI_COMPATIBLE_BASE_URL / OPENAI_COMPATIBLE_API_KEY are not in providerCredentialEnvVars, so the forwarding loop's planted refusal never saw them. Worse than the loop: the branch writes what crosses EXPLICITLY, outranking even a container-level fill, so a route the reviewed repository's .env planted on the host…
  • claw: resolve command/skill workspaces through a provider, not a captured string (0be91fb), references #2870

    whyRegisterClawCommand and RegisterClawSkill captured the workspace string at registration time, while the prompt path (model.expandWorkspaceSlashCommand) reads the executor's workDir LIVE — the field the engine replaces at run start via ClawExecutor.SetWorkDir when worktree:auto produces a per-run worktree after the registry exists. On such a run the /name prompt expansion and the command tool resolved .claude/commands/ in two different checkouts, possibly at two different commits — exactly the…
  • claw: the gateway table memo keeps provenance, and a planted gateway key crosses empty (77f0bac), references #2813 #2813

    whyTwo blind spots of the #2813 gateway fix, found on re-review:
  • claw: the sandbox env crossing never copies a value a workspace .env planted (2fb2c26), references #2813

    whyforwardableProviderEnv read os.Getenv(name) for every forwarded name and copied it as the launcher's value. On the host, cmd/iterion's applyDotEnv plants the reviewed repository's .env into the process environment (it fills only names that are unset) and internal/envtrust records which — the crossing never consulted it. Wherever the operator set nothing, the repository under review decided what a sandboxed node's requests carry: a client identity, a routing name, the key the node's traffic is…
  • cli: thread the command's context through fmt, fix, dsl migrate and import (a3c7634), references #2689 #2839

    whycmd/iterion/main.go builds a signal.NotifyContext and calls ExecuteContext, so cmd.Context() IS cancelled on SIGINT/SIGTERM — but RunFmt, RunFix, MigrateDSL and RunImport took no context, so nothing they did was cancellable. It bit at the chokepoint #2689 made: every writer of a bot's file takes the authoring lock through replaceBotFile, and with no context at the call sites the package had to answer the caller's question itself — botFileLockWait, a 15s bound an operator's Ctrl-C did not…
  • dsl/expr: the evaluator's work is bounded per node evaluation, charged as it builds, interruptible, and quoted by excerpt (cf44b06), references #2775 #2775

    whyThe element-visit budget (map/filter/reduce) counted visits, not what a visit costs: 100 000 visits each evaluating a body of thousands of nodes is hours of CPU at the read budget's edge, and a left-deep string `+` chain re-copied its prefix at every step while spending no visit at all (2 255 MiB allocated in 1.7 s for 336 KB of source, an OOM the evaluator's recover cannot catch) (#2775).
  • dsl/ir: group expansion spends one budget the bot's uses share, before it writes (b457173), references #2774 #2774

    whyThe lexer counts a `{{params.x}}` marker and its bound value as one record each, so group substitution multiplied one string record into unbounded compiler work BEFORE any budget saw it: K markers × an S-byte value became a K·S-byte string at compile time — a 230 KB bot held C317 off while building 1 GB (2.0 GB maxRSS), an 850 KB one died out of memory inside substParams, a 220 KB one was ACCEPTED with a 1 GB command: string in the IR, and even without parameters N `use` lines cloned every…
  • dsl/ir: predecessor sets are computed on demand, never materialised for every node (62979e5), references #2776 #2776

    whyvalidateLoopCapExpressions called buildPredecessors unconditionally, and buildPredecessors materialised the transitive predecessor set of EVERY node — quadratic in the node count: 2.0 GB at 8 000 nodes of a plain chain, OOM at 16 000 (590 KB of source), far under the read budget and the size cap (#2776).
  • dsl: a shared prompt's references draw O(R) findings, and identifiers are capped at the lexer (#2779) (c4d83b7), references #2627

    whyTwo amplifications of the class #2627 bounded in text but not in count:
  • dsl: every diagnostic constructor passes the recording door's bound (#2801) (cca776b)

    whyThree constructors reached a user without passing the doors that hold a message and a hint to expr.MaxDiagnosticText: the YAML twin's docDiag (a 2 MB anchor name rode an E050 whole), the unit loader's first refusal (a 2 MB import path rode an E046 three times), and the server's rewrite of a recorded E010 (two workflow names whole).
  • dsl: the canary walks the tree it means to, and the alias probe acquits a plain import (67ad771), references #2801

    whyRound-3 review of ea1ebcd90 (F4 blocking, F5):
  • dsl: the diagnostic-door canary reads collection elements and import aliases, and the merged readers' bare form is bare (ad674f7), references #2801 #2779

    whyReview of 4721361be/cf4bee235 (F1 medium, F2/F3 low):
  • dsl: the sized_by diagnostic moves to C326 — C325 collides with an in-flight branch (4a57d6d), references #2677

    whyDiagInvalidLoopSizedBy claimed C325, which kimi29's in-flight DiagGroupExpansionPastBudget already carries; the one-row-per-code guard (TestDiagRowsAreDefinedOnce) would refuse the merge. C326 is the next free code in the tree. Code, catalog, tests, grammar, dsl.md and the diagnostics reference move together; the clause's behavior is unchanged.
  • dsl: warn (C179) when a Verified Action's recovery has no reachable rung (d8db28a), references #2783 #2871

    whySince #2783 the self-repair rung (rung 3) is gated on the recipe's genre (ir.ToolRecoverySelfRepairRung reads ir.ToolRecipeSelfRepairable): a registry recipe — a bare registered tool name like command: "noop_tool" — has no command to correct, so dispatch skips the rung. A tool node under policy: recover whose recovery budgets only max_repair_attempts therefore reaches no rung at all (rung 4 is off without max_agent_attempts), and the author heard nothing: C106 covers only the other inertness…
  • e2e-coverage: honour the GFM pipe escape and name an over-wide matrix row (e8b6f7a), references #2800

    whyThe matrix contract parser in verify_run split every row on each raw pipe: a cell carrying the GFM escape \| shifted every cell right of it, and a row with a raw extra pipe (more cells than the contract's six) was read silently with the Status column landing on whatever text followed the pipe — two committed rows were in exactly that state and red only as "invalid status", which names the symptom, not the cause.
  • mcp: the host Chromium runner and the PATH lookups keep nothing a .env planted (1006caa), references #2018 #2809

    whyTwo ambient-inheritance sites #2018 left, closed:
  • model: frontmatter parses past a BOM and CRLF — a model: key is never silently ignored (32bdb20), references #1714

    whysplitAgentFrontmatter demanded the exact '---\n' bytes: a CRLF-ended or BOM-headed agent file — which claude_code parses natively — read as frontmatterless, so its model: was IGNORED (not refused, breaking the invariant ErrWorkspaceAgentModel stands on) and the raw header served as the child's system prompt. The split now folds a UTF-8 BOM and CRLF endings before matching, as claude_code's own parse does.
  • model: the workspace-agent ceiling obeys no planted override and never crosses the sandbox absent (9172da3), references #1714

    whyITERION_CLAW_WORKSPACE_AGENT_MAX_BYTES is a security switch: a repository under review planting it through the auto-loaded .env could remove the per-file ceiling (=0, an unbounded read of what becomes a billed system prompt) or drop it to nothing (=1, every agent file refused TooLarge).
  • parser: the broken-list resync's stack skips the remainders the loops never saw (ac3abbd), references #2768 #2626 #2768 #2771

    whyThe broken-list resync's open-block lookup replays the CONSUMED token stream, and an earlier resync's consumed remainder held INDENT/DEDENT tokens the parser's block loops never saw (plus the DEDENT that resync spliced). The replay popped levels the loops still held open, so a later resync in the same block read a shallower stack: on the ticket's shape (a second `rules: [b,` broken under a second `network:` after a first rescue), the second openBlocks answered [1 3 7] for the loops' [1 3 5 7],…
  • rgaa-audit: a rewind before fold_final keeps the fold's ledger copy (d60e3d4), references #2438 #2438

    whyTwo operator-rewind paths corrupted the fold's ledger copy (RUN/ledger/<std>.json):
  • rgaa-audit: the adjudicate/refute prompts spell their paths and tool set (5cad0b7), references #2675 #2675

    whyOn a small model the adjudicate branch burned 159 tool errors on 1445 calls: paths typed from memory, skill NAMES passed to read_file, write/todo_write attempted on a read-only node, a forbidden web tool reached through an MCP server the sandbox never started, and partial batches read as lost.
  • rgaa-audit: the audit never ingests a previous run's page captures (b469638), references #2348 #2623 #2623

    whyThe engine's audit step ingests whatever sits under .ultra11y/pages/<id>/ of the audited workspace — including a previous run's captures, whose stale probes.json anchored findings the fresh scan no longer produced (measured on #2622: 10.7 → 2 occurrences with stale captures, 0 after rm -rf). The bot orders static_audit before render, so a second run on the same machine (the demo writes captures under the audited checkout) inherited the first run's captures.
  • rgaa-audit: the embedded skills name the same path discipline as the prompts (8c34099), references #2675 #2675

    whyThe adjudication-protocol, refutation-protocol and ultra11y-engine skills still ordered the model to open <briefs_dir>/<id>.md and <items_dir>/<id>.json — templates built from directory variables the adjudicate and refute agents no longer receive since #2675, while the prompts declare the skills authoritative. The three skills now name the same discipline as the prompts: open only the absolute paths the batch input lists (item_paths / brief_paths, and the draft report and technical annex for…
  • runtime: a resumed non-worktree subdir run keeps its launch repo root (e266896), references #2867

    whyA run that is not worktree-managed (worktree: none, or a degraded auto) and whose workDir is a subdirectory of the repository resolved its project-rooted dirs on two different keys. Measured with repo <tmp>/002 and workDir <tmp>/002/services/api:
  • runtime: one decision for a setup failure's record and its returned error (92ee379), references #2086 #2862

    whyA setup failure's outcome was decided by several separate reads of the run context — fetchFailure (attachments), setupFailureStatus via markFailedBestEffort (the recorded status), and Engine.setupErr (the error returned to the runner) — so a cancel, a drain or the run's deadline landing between two reads made the record and the return disagree (measured in the #2086 review loop):
  • runtime: resolve {{attachments.*}} through resolveRef on the mapping surfaces (e50e483), references #2864

    whyresolveRef had no RefAttachments arm, so an edge with:, an emit payload, a fail message: and a human node's instructions (and the review companion's system prompt, rendered through the same function) rendered every {{attachments.*}} reference empty while prompts and tool bodies resolved fine through the model template resolver. The arm reads rs.attachments with the model resolver's sub-field rule (path by default, then url, mime, size, sha256; an unsigned url resolves to nil rather than…
  • runtime: the drain bound's cut is also the teardown of the group the command led (04a2234), references #2883

    whyOnce a tool node's shell has been reaped, exec's context watcher has already handed its verdict to Wait, so the group kill proc.TerminateGroupOnCancel arms is never dispatched — whatever the context does afterwards (measured by fix-2855's reviewer: a node under a 3 s deadline whose command left `sleep 30 &` came back at 30 s, the group kill never sent). The drain bound's cut is the last moment the recorded group id is known to be ours — it is the child's own pid, recorded at spawn — and a…
  • runtime: the workspace-safety arm's agent reading is decided, explained and pinned (836f273), references #1940 #2811 #1404 #1940

    whySince #1940 the shipped subagent runner (model.NewSubagentRunner) draws a claw child from the parent NODE's effective tools, so on the shipped hosts the ultracode-granted agent adds no reach the arm does not already read. The ticket asked whether agent can therefore leave the arm's unsafe reading. The decision is no, recorded at the arm (pkg/runtime/workspace_safety.go): whether the runner bounds the child to the node's list is a property of the HOST's wiring — ClawDefaults.Subagent is a host's…
  • security: a preview renders a server-minted secret var as the placeholder, never the value (27486c1), references #2033 #2827

    whyResidual 3 of ARB-97 (follow-up of #2033): after the env carriage of the forge publish grant, the indirection that turns {{vars.forge_publish_token}} into the guard's placeholder existed only where the materialiser that resolves it exists — a tool body under the run's guard. A preview renders without one (RenderCommand/RenderScript pass a nil guard; a prompt's TemplateResolver has no materialiser behind it either), so a template naming the publish token rendered the bearer in clear.
  • server: a refused declared-fragment miss names the dependency, not the import (659e368), references #2865 #2853

    whyA bundle that declares dependencies.fragments and carries the import line the declaration exists for holds an E046 (no such fragment) as long as the dependency is not materialized — the cloud editor's whole authoring path then refuses it as "the unit does not load", and every refusal pointed at the wrong actor (ADR-145 clause 3): "fix the import" sent the author to repair the one line that is right.
  • server: a rung hidden behind a closure refuses the annex generation, and the rung witness learns the plugin-write rule (ea4bbaa), references #2821

    whyRe-review of 893b9d8cc found the surviving variant: a wrapper hidden behind a function VALUE — `hiddenAdmin := func(next http.Handler) http.Handler { return s.requireSuperAdmin(next) }` — resolved to "func", was read as an inline literal, and the closure's body (which no source scan owes it to read) carried the rung invisibly. Proven end to end on POST /api/v1/plugins/kimi38-probe: the annex printed "session" with every gate green.
  • server: the cost preview reads the launch's verdict, not "is there a workflow" (7739bac), references #2814

    whyPOST /api/runs/preview-cost judged a candidate on one bit — cr.Workflow != nil — so a program the launch refuses was priced as if it ran: a node naming a prompts/*.md the bundle ships compiled, without the merge, to a C003 error with a non-nil workflow, and the chip priced it.
  • server: the raw-mount guard walks every registrar, and the head token reads digits (e77907d), references #2821

    whyRe-review of 8d7e980d6 (LOW+): TestNoAdminFamilyMountsOutsideTheScan read only server_routes.go, and its premise ("the raw mounts all live in server_routes.go") was already false — board_cloud_routes.go mounts /api/v1/native today. A raw sub-tree mount under /api/admin in any other registrar left the guard green. The guard now walks every non-test source of the package (the same walk platformAdminRegistrations makes), and its blindness floor moves to the three mounts of today.
  • server: the REST annex's auth rung gets an independent witness, and a hidden rung refuses the generation (c87379e), references #2821

    whyReview of fed3f21e6 (1 HIGH, 3 LOW).
  • store: a failed attachment record leaves no bytes, and a stranded .iterion file reads as not-found (1a2cf11), references #2863

    whyWriteAttachment renamed the bytes into place BEFORE writeRecord, so a record failure — an original filename exactly `.iterion`, which blocks the record's directory with a plain file — stranded the bytes on disk, and loadAttachmentMeta then surfaced the raw ENOTDIR, aborting the run's whole ListAttachments. Reachable via a bundle attachment default named `.iterion`.
  • store: a pre-move .iterion bytes file falls back to its legacy record (977e189), references #2863 #2863

    whyBefore #2863, `.iterion` was a legal filename — only meta.json was reserved — so an old store can hold bytes at dir/.iterion with their record at the legacy dir/meta.json. The new-path read then fails with ENOTDIR, which is not fs.ErrNotExist, so readRecord never fell back: attachments read as not-found (silently dropped from ListAttachments) while record and bytes sat intact, and staged uploads under that name surfaced a raw untyped error, unpromotable.
  • store: a retried attempt keeps its own turn checkpoint, the previous one superseded (26cb39e), references #1481 #2806

    whyThe turn checkpoint's coordinate derives from the LLM step, not the node's execution attempt (model/hooks.go, and executor.go passes Step: 1 for every delegate call), so a retried or resumed attempt of the same node wrote at the same (LoopIter, TurnIndex) as the attempt before it and overwrote it — the earlier attempt's usage disappeared from the baseline the bench reconciliation trusts, and a fork anchored on the turn replayed the last attempt's conversation with no trace that an earlier one…
  • store: move the record sidecar out of the bytes' namespace (39d3a4f), references #2086 #2863

    whySince #2086 both run-store twins refused an attachment or a staged upload whose filename is meta.json: the filesystem layout kept each record as meta.json beside the bytes, so a file under that name was read back as its own record, and the Mongo twin refused only to keep one contract. meta.json is a common filename (a bundle default, a tool's deliverable, an operator's upload).
  • store: the mongo turn twin bounds its superseded list, and a bare redelivery keeps the blob (0fde748), references #2806 #2806

    whyRevi on the #2806 supersession twin, one moyen and two mineurs:
  • studio: keep the sidebar readable at 200% text size (RGAA 10.4.1) (9106e72), references #2522

    whyThe primary nav labels were truncated ("What's Next", "Dispatcher", "Marketplace" lost text at 200%, available only in a title attribute) and the rail was a fixed w-[220px] with overflow-hidden, so it never grew with the text.
  • studio: raise the fragment-parse warning as an editor banner, not a toast (1aece3c), references #1650 #1650

    whyQ42=A: the apply-time warning of #1650 item 3 — a buffer that parses 200 with diagnostics null/empty while declaring no workflow — moves from the interim persistent toast to the DSL editor's own banner surface (the InlineBanner the 409 CAS conflict already uses). A toast reads as the answer to whatever the author just did anywhere in the app; the banner is the document's own verdict, where the apply happened.
  • studio: the assistant-reference drag grips are attach buttons (818c2a3), references #2770 #2770

    whyDragging a reference onto the assistant was pointer-only (#2770): the grips spread referenceDragProps on a non-focusable span, and no keyboard or single-pointer path performed the attach (SC 2.1.1 / 2.5.7). The placement decision landed on option A: the grip BECOMES the control.
  • studio: the bundle drawer's file rows offer the same attach grip (d0a8c70), references #2770 #2770

    whyBundleFilesDrawer spread referenceDragProps on the whole file row — the fourth referenceDragProps site, with the same pointer-only defect as the three #2770 named. The row holds the edit/delete buttons, so it cannot become a button itself; it keeps its whole-row drag, and each row gains the same grip the other surfaces have: a real button carrying the drag props and attaching the bot-file reference through the shared action on click/Enter/Space. One more tabbable per file row, and the grip…
  • studio: warn when an applied buffer declares no workflow (2de2447), references #1650 #1650

    why/api/parse answers an empty buffer or a .bot fragment (declarations, no workflow) with a 200 and diagnostics null/empty — the parser has nothing to complain about, and the document it carries then fails /api/validate (C006, or the C001/C008 of declarations whose referents live in the fragment's sibling files) with no prior warning (#1650 item 3).
  • train: T40 gate fixes — derived coverage table, the agent-tool sentence, two env names classified (62c5f15), references #2775 #1714

    whyAdversarial-Rounds: 1 (integrated-diff slice: the T40 repair commit) Adversarial-Model: kimi-code/k3 (subagent, adversarial posture) Landing-Train: T40
  • train: T40 integration repairs — C327 renumbering, claw crossing restored with the planted/crossingRefusals design, predecessorIndex call sites (8a5852c), references #2632 #2786 #1714 #2813 #2776 #2871 2881/#2882 #2861

  • train: T40 round-2 integration — kimi31 (capabilities: []) and kimi40 (parser resync) join the train (7b273e3), closes #1801 2768/#2771

    whyThe second adversarial round caught both branches missing from the pick list: kimi31 closes the #1801 trap (a node emptying capabilities: inherited the whole grant), kimi40 carries the broken-list resync's correctness fix beside its perf half (#2768/#2771). The perf commit's splice rides main's Lexer.splice queue mechanism, the closePoint memo maintenance kept. kimi53's C325 rationale comment restored after the collision repair.

Performance Improvements ​

  • parser: the broken-list resync's close-point scan reads its region once (c2c34a8), references #2771 #2626 #2768

    whyThe recovery of an inline list left open at the end of its line scanned from each rescued line to the end of the enclosing block (listBlockClosePoint) on EVERY broken list landing on a sibling inside a block two or more levels deep: N sibling broken lists in one nested block cost N scans of an O(N) region, quadratic CPU on every route that parses an uploaded source. On the ticket's shape (one nested block of N `rules: [a,` lines, then `image:` under the enclosing block):

4.10.0 (2026-10-31) ​

Features ​

  • model: the serving route's provider rides the run record beside its model (15a049d), references #2772 #2772

    whyThe selected route is provider AND model bound together (#2772, the Q49=A arbitration): published as a fact of the run — never a provider-only var — at the run level of the routing ladder, by the carriers the level system already has.

Bug Fixes ​

  • blob: the S3 client bounds dial and response-header phases (#2892) (c90ab1b), references #2890

    whynet/http ships with no default timeouts, so an S3 endpoint that accepts the TCP connection and never answers hung every caller forever - the Oct 30 storage-slot exhaustion wedged each cloud run for hours inside PutAttachment's roundTrip before its first node executed (issue #2890). The new Config.DialTimeout (10s default) and Config.ResponseHeaderTimeout (60s default) fail a degraded endpoint loudly while body streaming stays unbounded: GetAttachment returns a reader the caller drains at its…
  • delegate: a facade route that cannot serve the requested model is refused, not substituted (49c92e6), references #2772 #2772

    whyA vendor facade on the anthropic wire (z.ai, Moonshot) answers ANY model id with the one model its account aliases it to: the call succeeds and the run's record names a model that never saw the prompt — review-pr's claude primary reviewed by GLM under the claude label, the declared glm rescue never firing because the primary "succeeded" (#2772).
  • model: the facade verdict judges the endpoint the delegate judges (52afe55), references #2772

    whyThe claw registry's claim predicate was the NON-EMPTINESS of a base URL — the delegate's is equality with the vendor endpoint — so on the exact wiring z.ai documents (ZAI_API_KEY plus ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic) the delegate refused a claude id and the registry built the client: GLM served under the claude label, the run record blessing the mislabel. Moonshot's twin refused only an unset MOONSHOT_BASE_URL.
  • secrets: the facade claim judges the vendor's whole published ensemble (b7e2e0c), references #2772

    whyThe claim predicate folded the base URL against ONE vendor constant, but each facade publishes the same Anthropic wire on TWO hosts — and the tree itself documents the seconds: Moonshot's api.moonshot.cn gateway (byok.go's own comment, the netproxy facade allowlist, the claw env-carrier note) and Zhipu's open.bigmodel.cn for the z.ai family (detect, executor funding and the claw billing split all read "bigmodel" as z.ai). On either host the refusal #2772 closed on api.z.ai and api.moonshot.ai…

4.9.0 (2026-10-31) ​

Features ​

  • model: DISPLAY/WAYLAND_DISPLAY decided launcher-side for ever, the pin becomes the verdict (f2ce0d3), references #2802

    whyQuestion 50 landed (verdict A): a sandboxed node has no display and computer_use — the tool that reads the pair — drives the launcher host's, so the names are launcher-side for ever and cross nowhere. The fail-closed undecided class flips to the decided launcher-side class: the classification test's launcher case now accepts the PlacementRefused shape, where the refusal IS the launcher-side verdict (a move INTO the container reopens the classification, as before), and the crossing witness keeps…

Bug Fixes ​

  • chart: a default controllerNamespaces scopes the server port, an emptied config warns loudly instead of refusing (1d32f47), references #2804

    whyQuestion 51 landed (verdict A): the empty-config behaviour is decided. The chart now SHIPS a default networkPolicy.ingress.controllerNamespaces — ingress-nginx, the common case the prod values and the docs already name — so a networkPolicy.enabled=true with nothing said renders the server port's rule scoped, not admit-all. An operator who empties BOTH controllerNamespaces and ingress.extraAllow keeps the historical from:-less admit-all render — the render is never refused, so an upgrade never…
  • claw: route read_image's path input through the read scope (28e0168), references #2650 #2885

    whyread_image took a model-supplied path bounded by neither the ReadScope nor the credential exclusion list: the kimi17 class inventory was keyed on `scope *ReadScope`, and read_image's registration took no scope, so it escaped — read_file/glob/grep refused ~/.ssh/id_rsa or a workspace .env while read_image opened any path the loader could stat.

Performance Improvements ​

  • parser: the broken-list rescue's splice joins the token queue instead of recopying the stream (a5f66a6), references #2880 #2768 #2771

    whyspliceDedents inserted a rescue's closing DEDENTs into the flat token slice, recopying the whole tail of the stream at every rescue — the second quadratic of #2768's comment, and the one that stayed after the close-point scan's: on the splicing shape of repeated broken lists, N=2000 -> 0.33s, 4000 -> 1.33s, 8000 -> 5.30s (x4 per doubling, the tail's memmove), extrapolating to ~29s at 16000 rescues.

4.8.0 (2026-10-31) ​

Features ​

  • bundlelint,bots: root the session-continuity family on the member that can serve it, and name a skill promising a durable write its bundle cannot (7f400ef), references iterion#2773 #1 iterion#2773 iterion#2861 #2832

    whyA shared skill taught claw's `memory_write` contract from a bundle whose own node cannot reach a memory tree: `internal/skillshare` rooted the session-continuity family on `whats-next`, which runs its turn on claude_code with no `memory:` block, so every reader of that page read a write contract the bot carrying it could not honour (iterion#2773). The corpus guards had closed the class for the shipped catalogue by PARDONING that page — two exemption tables, one row each. A pardon is not…
  • claw: the command tool — model-invoked workspace slash commands (d4fdeae), references #1716 #1716 #1418 #1717

    whyclaude_code lets the MODEL invoke a workspace command mid-conversation (its SlashCommand tool); on claw a command was reachable only at the head of the operator's prompt. The new command builtin closes that half of the capability, gated by the node's tools: like every other builtin and resolving through the same contained pkg/api/commands lookup as the prompt path (clawcmds.LookupWorkspace + Expand), under the same byte ceiling and the same kill switch: ITERION_CLAW_SLASH_COMMANDS=off now takes…
  • claw: the command tool reports what it discarded, and the operator's ceiling reaches the container (0a7ffe5), references #1717 #1716

    whyTwo halves of #1716's model-invoked surface that the prompt path has and the tool did not.
  • cli,studio: the one-command remote teams switch is back, minted by the browser approval's live session (9af6629), closes #2674, references #1668 #2674

    whyA personal access token mints nothing since #1668, which left `iterion remote teams switch` a refusal naming a fresh login. The one-command switch is back: the CLI re-opens the studio's /cli-auth approval with ?team=<id> (the argument resolved to the team's UUID first, a slug included), the page mints the team-pinned token under the APPROVING live session — never the stored PAT, which the flow presents to nothing but the read-only resolution and the revocation — and echoes the server-stated org…
  • connection,server,backend: the MCP facade — the agent capability stops being inert (af8917d), references #2758

    whyAn agent node declaring capabilities: [connectors] is registered with an iterion-owned MCP server (iterion_connectors) served over HTTP with a per-run X-Iterion-Run token. The facade (pkg/connector/facade) is generated from the SAME package the deterministic node reads: the overlay's curated mcp:true operations as named tools, the long tail through search_operations / call_operation. The server resolves the connection through Resolver.ResolveAgent — the one chokepoint, asking for CapAgent — and…
  • connection: the Mongo twin of the connection store (95a0ac9), references #2759

    whyCloud-native rule 3: the tenant-scoped Store seam now ships its cloud twin in the same change as the route that reads it, not as a later hole. The unique (tenant_id, connector, alias_norm) index is what keeps the alias rule true between replicas, where neither twin's lock reaches.
  • modelspecs: snapshot schema 2 carries the capability flags models.dev publishes (b722820), references iterion#2103 #2103 #2842

    whyThe embedded models.dev snapshot stored only the numbers, so the catalog-snapshot tier answered every capability flag as unknown: an id models.dev publishes as `tool_call:false` — 1053 of its 8918 entries, every embedding and transcription model among them — earned the studio's WARNING offline and on a cold start, where the live table gives a BLOCK. The verdict depended on whether the live table happened to be loaded; the pin left behind by iterion#2103 recorded that, and this is its fix.
  • model: the env claw's tool packages read in the sandbox is derived and classified, the two forwarded dials cross (1b731f7), references #1804 #2802

    whyThe provider half of the vendored-claw env walk (#1804) had no twin for the tool packages: twelve names read in claw-code-go/pkg/api/tools and internal/tools, five of them already decided with web_search's launcher placement. This change extends the same derivation to the two tool packages (same loud failure when a prune takes one), classifies every name with its reason, and crosses the two sandbox-placed dials — CLAW_BASH_TIMEOUT and CLAW_CONFIG_HOME — through forwardableToolEnv, merged into…
  • sandbox: the image ENV screen carries its whole class as a run event, and a container arm proves the override (0565864), references #2317 #2317

    whyUnit 2317.2 of #2317, the visible half ARB-113 = A cut: the screen that neutralises what a repository-authored image bakes into its own `ENV` now reaches the operator through the run's event stream, and an end-to-end container arm settles that the override actually lands.
  • server: a declared shared fragment resolves on the launch plane too, before the parse blames a file (327c064), references iterion#2865 #2853 #2865

    whyA bundle that declares `dependencies.fragments` (ADR-145) was launchable git-native and refused in cloud, by this sentence — measured, not supposed:
  • server: the tenant connections CRUD — /api/teams/{id}/connections (09acb08), references #2759

    whyA tenant manages its connector connections from the cloud: create, list, read, patch (rename, capability change, revoke, token rotation) and delete, every read tenant-scoped in the store's signature so a cross-tenant id answers 404, and no response carrying a credential — the DTO is built field by field and the token is sealed before the store sees it.
  • studio: the tenant's connector connections, managed from Integrations (4abbad8), references #2759

    whyA new Connectors tab on /integrations lists a team's connections with their capabilities, status and the server's own warnings, and — for a team admin — adds one (connector, alias, instance URL, scheme, capabilities, write-only credential), revokes one in place, or deletes it behind a confirm. The API module is typed against the regenerated spec (connectionDTO/connectionCreateReq/connectionPatchReq); the create request's optional fields are omitempty so the spec stops claiming them required.

Bug Fixes ​

  • auth,server,cli,desktop: the team-token mint class is closed at the verb, and an approved token is checked before it is stored (472cc0b), references #2687 #2674 #2687 #2674

    whyAn independent review of kimi12 found the chokepoint of #2687 gated by a LIST of route names rather than by the verb, and the switch of #2674 storing whatever the browser handed it.
  • auth,server,desktop: the team-token mints are gated, a socket dies with its credential's row, and a refused desktop SSO reaches the loopback (2d8cb51), closes #2687, references #2687 #1668 #2687

    whyThree residues of #1668's chokepoint, one lot (#2687):
  • board: the tool door drops the reserved launch context like every client write (9b23a4d), references #2371

    whyFinding (HIGH, round-5 review of 24b0e1c14): boardops.doCreate passed bot_args verbatim to store.Create, so the sixth board write door — the TOOLS path — persisted the whole reserved launch class (__iterion_webhook_id, __iterion_project_path, the BYOK overrides, __iterion_repo_url). The reviewer's probe replayed it end to end: a create_issue carrying a forged __iterion_project_path stored it, and processBoardCard both metered the run under victim/other and refused it at THAT repository's quota…
  • bundlelint,cli,skilllib: read what the runtime installs, not the presence of a block, a list entry or a page's shape (4958e72), references iterion#2832 #2832

    whyC274 landed with four silences, each one a capability credited by PRESENCE rather than by what a run actually gets (iterion#2832, one independent review round, every finding reproduced before it was fixed):
  • bundlelint,permission: ask the engine what a node holds, and the gate one rule row at a time (8c023eb), references #2832

    whyThe durable-memory check re-derived three of the engine's own decisions, and each re-derivation was wrong in a direction the engine's seam answers for free:
  • bundlelint,permission: ask the gate instead of re-reading its grammar, and credit a durable write path only where a call of it runs (62c35f7), references #2832 #2832

    whyC274's gate question had its own reading of the permission grammar, and it diverged from the gate at every form it had not thought of. A tool GLOB was the one that bit both ways on the same commit: `allow: ["Write*"]` under `permission: deny` fired the warning on a bundle whose writer the gate ALLOWS (`Evaluate("Write", …) -> allow (rule "Write*")`, measured), while `deny: ["Write*"]` under `ask` credited a writer the gate denies on every call. The cause is one spelling: the lint asked about…
  • bundlelint: carry a when:-gated fallback route ungated in the route reading (c717bc5), references #2832

    whyThe chain nodeRoutes reads came from the program-only executor, which holds no vars, so dispatch's posture (fallbackWhenActive) had dropped every `when:` route whose gate it cannot answer — while dispatch on a run that carries the vars walks the same route. C274 fired on bundles whose only write path sat behind such a gate (H-10 round r3b finding 1): a valid claude_code bundle with a writer-free gate and a gated pi fallback went from quiet to flagged, the base quiet on the identical file.
  • chart: the server port's NetworkPolicy ingress admits the controller's namespaces and the release's pods, not the whole cluster (e7c3aaa), references #2804

    whyThe rule carried no from: selector, so any pod of any namespace could dial the server port directly and set its own X-Forwarded-For — forging the audit IP and rotating the rate-limit key, the in-cluster half of the evasion #2673 closes for external clients. The rule now scopes to networkPolicy.ingress.controllerNamespaces (matched on the kubernetes.io/metadata.name label the kube-apiserver stamps on every namespace since 1.22) plus the release's own pods, with networkPolicy.ingress.extraAllow…
  • claw: tool_search answers for the node that asked, not the whole registry (2bf7cce), references #2717 #1653 #2717 #1653

    whyA declared tool_search searched the registry's whole catalogue and answered names and search metadata only: a reviewer that ran select:bash found bash, absent from its own tools: list — and the call then met unknown tool. The tool made the reviewer lie (#2717), on the result side this time (#1653 was the contract side). The G37 decree stands: reviewers keep ToolSearch; it is the answer that becomes honest.
  • cli: an engine-spawned iterion does not auto-load its workspace .env (c8b5375), references #2844

    whycmd/iterion loads the nearest `.env` walking up from cwd for almost every invocation, filling in each name the environment left unset. In production that cwd is the repository a run cloned: a tool node's script re-enters the CLI through ITERION_ENGINE_BIN with cwd = the tree under review, and the in-container `__claw-runner` starts there too. The repository therefore got to name the provider endpoints (ANTHROPIC_BASE_URL, OPENAI_BASE_URL, the ones providerRoutingEnvVars does not know) that the…
  • connection,orgsweep: the connections collection is declared to the guards that hold the tree (399a26c)

    whyThe Mongo twin of the connection store opened its collection through a parameter, so no .Collection( site named it: the diagram guard read an unnamed dynamic site, and the purge completeness scan could not resolve the name. NewMongoStore now names ConnectionsCollectionName at its own .Collection site — the form every store under pkg/ is held to — while NewMongoStoreIn keeps the conformance suite's fresh-per-build seam, held by a row of the dynamic-site table. The purge classifies connections as…
  • connector,connection: the curated set bounds call_operation, and maturity holds at the chokepoint (d211bb1), references #2758 #2758 #2759

    whyTwo doors answered differently about what a model may reach. The deterministic node path refused an operation the package does not qualify (checkOperationUsable: maturity, determinism) while the facade's discovery pair served every operation in the catalog; and call_operation reached past the curated set — 85 deletes and 58 updates on the shipped forgejo package, against a curated list an operator reads as EMPTY.
  • connector: a vendor's 2xx body is redacted like its error, so a query-placed credential never reaches the caller (d53d5cf), references #2758 #2758 #2759

    why`exec.Call` scrubbed the credential out of the typed error and handed back `Result.Data` untouched. That field is the vendor's decoded body: it is what a `tool … action:` node writes to its output and what the MCP facade (#2758) serialises into an agent's tool answer. A vendor that echoes the request URI — a `self`/`next` pagination link, a debug envelope, an audit field — hands an `in: query` credential straight back, and the probe that found it read the token verbatim out of an…
  • delegate,cli: the facade serves each run its own catalog, from its own workspace and trust (e9348f2), references #2789 #2758 #2759

    whyThe local studio built the facade's connector catalog ONCE, at startup, with `TrustOfRun(RunTrustDefault)` in hard code and rooted at the studio's own cwd. Both halves were the defect:
  • delegate,connector,server: the pi nodes' MCP token leaves the env, and the walk reads its keys redacted (ad7bc64), references #2758 #2759

    whyTour 2 on leap/kimi16-fix: the HIGH the H-10 review left open on the pi half of the host-MCP transport, plus the three lows the same review rendered.
  • delegate,model: the per-node host-MCP token leaves argv and dies with its node (b7fa9b5), references #2758 #2758 #2759

    whySince #2758 the `X-Iterion-Run` token spends the tenant's connector credentials through the facade, so it is a credential in its own right. It was treated as neither.
  • delegate: the connector facade's tools are iterion's own, so a fan-out that holds them keeps its verdict and its cause (cc4129d), references #2758 #2759

    why`IsIterionMCPTool` knew `iterion_board` and `iterion_runs` and not `iterion_connectors`, so the pair a `connectors:` node holds (`search_operations` / `call_operation`) entered the workspace-safety classifier as an unvouched tool. Two damages, both measured on the committed admission table's own shape:
  • desktop: drop the io import the mint-chain refactor left unused (94b1cd8), references #2687

    whyThe team-token mint rework (9af66298d) removed cloud.go's last io consumer; the desktop-tagged vet has failed on all three platforms since (vet: cmd/iterion-desktop/cloud.go:8:2).
  • envtrust: the rebased dotenv doctrine answers the tree's own guards (3bcc966), references #2844

    whyThe stack replays onto a main that landed #2844 and the env classification while the branch evolved the same names; the unions left six completeness guards red, and each fix keeps both intents:
  • layerguard: ask the go tool which files a build compiles, and judge every importer (3c5b7e8), references #2829

    whyTwo rounds of this fix re-implemented, inside the guard, a rule the go tool owns: first the `_x.go` / `_x` name convention, then "the constraint names `ignore` somewhere". The second one threw away `//go:build ignore || mytag` — a file `go build -tags mytag` really compiles — and with it the crossing such a file holds: reproduced on a scratch module, `go list -tags mytag` holds `pkg/a -> internal/secret`, the base named it and the tip said nothing.
  • layerguard: exempt only what no build compiles, and demand tags off the whole-module listing (c801853), references #2829

    whyThe exemption was go/build's MatchFile over the guard's five contexts — five POINTS, four platforms carrying no tag and one tag set carrying no platform — so `//go:build ignore || (windows && mytag)`, satisfiable only at their product, was called exempt and the crossing it held shipped in silence. It is now the constraint go/build honours with `ignore` pinned false: the names left are free variables, enumerated exactly (2^n, n bounded by maxNamesInAConstraint), and a file some build compiles is…
  • layerguard: name the leaf's accepted dispatcher import, and skip what the toolchain compiles nowhere (7216565), references #2857 #2829 #2829

    whyRound 2 of #2829's review, four findings, each closed at its class:
  • layerguard: refuse the import the rules cannot judge, and drop a file by the whole convention (95487e5), references #2829

    whyRound 3 of #2829's review: two highs, one medium, three lows, each replayed from the reviewer's probe and seen red before being touched.
  • runner,runview: a rotated clone token is registered on the live guard (2f7f884), references #2756

    whyThe runner's rotation loops arm BEFORE runview.BuildExecutor builds the run's secret guard, so the handle between them is late-bound: the runner creates a per-run atomic holder, BuildExecutor delivers the guard through the new ExecutorSpec.SecretGuardSink the moment it is built, and every rotation tick reads the holder. refreshGitCredentialsOnce, refreshFileSecretsOnce and refreshSandboxFileSecretsOnce register each fresh value (RedactOnly — its placeholder resolves nowhere) BEFORE the write…
  • runtime: a bind source the plan verified is re-asked at the driver border, and the ephemeral tmpfs hands its owner over (4b79d41), references #2069 #1959

    whyThe bind-source check ran when the mount plan was built; the daemon resolves the source at container creation — minutes later, across the image pull. A sandboxed run alive through that window swapped the verified scratch leaf for a symlink and wrote the host path it chose through the emitted mount string (replayed on docker 29.9.2: uid 1000 landed on the attacker's directory). The plan now names every source it verified — the project scratch and the five caches, in Spec.BindSourceChecks — and…
  • runtime: a fork run whose sandboxed node gets no credential is refused at launch (10dc8b2), references #1768

    whyAn untrusted (fork-lane) run executes only inside a per-run sandbox — the PR2b admission floor. But a sandbox alone does not feed the agent: an operator authenticated only through a file-based subscription login (claude login, a ChatGPT login) has it reach a local docker sandbox through the host_state mount, and a host_state: none run withdraws exactly that channel, leaving the node to die mid-run on the backend's opaque "not logged in". The spec level cannot see the exec-time channel — the key…
  • runtime: the border answers a bind source by its shape — a file by identity alone, a gone source by refusal (4dda5c9), references #2069 #1959

    whyThe border's create-on-missing ask ran on every recorded check: a file the plan verified — the plugins.yaml copy seed, a worktree's .git/config — met a MkdirAll there, so every honest container was refused with ENOTDIR, and a file gone missing since the plan would have been stood back up as a DIRECTORY on the operator's path. The review's honest worktree and checkout probes were replayed red at this tip before the fix (both workspace shapes refused) and green after (the container is created).
  • runtime: the drain bound's page is scoped to the host and says what it kills, and a cut that could be a prefix is published as one (22b4049), references #2855 #2855

    whyRound 2 of #2855 on `leap/v133-fix`, from the bounded review of 0295484ba (wf_ebb96b4b-5e9) under ARB-118 — the tool node stays in the exit-status family, keeps its success and its answer, and says the drain was abandoned.
  • runtime: the four readers of a tool node's run judge it by its exit status, so a drain the bound abandoned is reported and never charged to the node (7d54475), references #2855

    whyb0d697664 bounded the drain of a tool node's output and read the cut as the OUTPUT family's failure — inside `runWithSeparateStreams`, the runner FOUR readers share. Three of them judge a run by its exit status and the fourth is ADR-044's own oracle, so the reading reached verdicts it was never meant to: a postcondition that exited 0, printed its whole state and left `sleep 6 &` holding its output was read UNMET, the idempotent-skip rung did not skip, the recipe ran a SECOND side effect, the…
  • runtime: the host-state guard covers what the host EXECUTES from a mounted home, and the caches it would compile (dfbfbe6), references #2069 #1425 #2069 #1959

    whyThe #2069 guard shadowed `~/.claude/settings.json` — a POINTER. A hook is a `command:` naming a SCRIPT, and the script stayed writable at its own host path through the read-write `~/.claude` bind, as did `local/` (the CLI install iterion's own probe resolves and runs), `skills/`, `commands/`, `agents/`, `output-styles/`, `rules/`, `workflows/`, `routines/` and `~/.claude/CLAUDE.md`. Membership is now decided by the verb that defines the class — what the host EXECUTES, or LOADS AS INSTRUCTIONS,…
  • runtime: the mounted CLI homes are per-run tmpfs, and a bind source is resolved before it is bound (bd90567), references #1959 #2069 #1959

    whyRound 2 over kimi14. The first round shadowed what the host executes out of `~/.claude` and `~/.codex` with read-only overlays and gave the sandbox its own warm caches. Two defects survived that shape, and both are closed here by inverting the posture rather than by lengthening a list.
  • runtime: the sandbox page says what it measured, and the drain-cut mark follows the answer that survived the merge (f2ada14), references #2855 #2855

    whyRound 3 of #2855 on `leap/v133-fix`, from the bounded review of cfb729795 (wf_d59a587c-60b) under ARB-118 — the tool node and its postcondition stay in the exit-status family, and the drain the bound abandoned is said on the event and in the log.
  • runtime: the three execs that read a child's output join the drain-bound class, and the bound says where it cannot reach (feeb2a5), references #2469 #2855 #2855

    why`exec.Cmd.Wait` ends only when EVERY holder of a child's output pipe has closed it, and a context deadline kills the direct child, never what it forked. #2469 gave that class one owner (`proc.SetWaitDelay`), one override (`ITERION_PROC_WAIT_DELAY`) and one reading per site. Three execs that read a child's output still carried `WaitDelay` zero, so `awaitGoroutines` waited for the copier with no bound at all:
  • runtime: the workspace joins the bind-source border question, every verified source is named for it, and the sweeps delete only under the operator's home (b4360dd), references #2069 #1959

    whyThe workspace bind was the one mount the driver derives from run metadata outside the double question: read-write, source under the iterion home a living run holds read-write, asked at neither moment. The guard now asks the plan-time half — the resolved source is still the tree the engine laid under the store; a swap refuses the RUN, since a workspace has no cold skip to fall back on — and names it MustExist for the border, whose re-ask refuses a swapped source and refuses, instead of…
  • runview: the fork's stranding propagation stays scoped to the pause's own anchor (03ae3ee), references #2461 #2461

    whyThe fork copied parent.Checkpoint.BackendSessionStranded onto any claw anchor with a pinned conversation. The pause's stranded conversation is the pause node's LAST turns: an anchor elsewhere — another node, or an earlier turn of the pause node — pins a conversation that joined the session normally, and the stamped child's OnSessionCapture silently refused its own legitimate captures, so the child's session slot never accumulated (the exact live-discard class #2461 closes).
  • runview: the in-process lane stamps the resolved bot-source tier on the run document (a6e878f), references #1959 #1959

    whyThe studio's in-process launch passed the resolved tier to the EXECUTOR (ExecutorSpec.BotSourceTier, so the launch's own predictions judge the floored workflow) and stopped there: the run DOCUMENT never recorded it. A resume rebuilds the engine from that document, reads an empty tier as the operator's own launch, and hands a team-authored `sandbox: none` back — the #1959 floor undone on the second attempt, with nothing in the run saying so. Unreachable today (a team botsource row comes with a…
  • sandbox: a relayed mirror gap strands the pause it produces, not only the crossing (c4e4f8d), references #2461 #1547

    whyA crossing stranded by a RELAYED mirror gap, once resumed from the pause it produced, mirrored its own blind turns into the host's session — the stranding verdict lived in a closure variable of the crossing's dispatch and died with it, so the resume's recomputed seed found a deliverable host copy and nothing on the pause said the crossing never saw it (the residual replaced by one crossing's 2).
  • sandbox: network rules expand env, and the session board pins on a gateway-served run (0c8a220), references #2099

    whyTwo of #2099's three env-funded boundaries. (2) sandbox network.rules were static globs a catalog bot could not point at the deployment's gateway host: ResolveNetworkPolicy now runs every assembled rule through ir.ExpandEnvWithDefault at proxy start, runner-side where the gateway's name lives, and a reference that resolves to nothing refuses the sandbox start rather than silently dropping the rule (a dropped denylist rule would fail open). (Sessionboard sibling of the supervisors' gate): the…
  • sandbox: the channels that re-plant what the image screen empties, and the refusal its call site owed (57f3984), references #59 #2317

    whyARB-119 on unit 2317.1, after an independent review of the vague-130 tip. The mechanism holds — `--env NAME=` beats a baked `ENV`, and every consumer of the sub-class reads a value, so an empty one is inert — but three rows of the KEPT half are themselves channels into the same environment, and the refusal the screen's own doc promised did not exist at its one call site.
  • sandbox: the env a repo-authored IMAGE bakes in is screened, and its credential half neutralised (655caac), references #2317

    whyThe deny class screens `containerEnv`/`remoteEnv`. It never saw the other channel into the same environment: a devcontainer.json ALWAYS names an image or a build, and that image's `ENV` applies to every process iterion starts in the container, because the drivers emit `--env` for spec.Env alone. A repository committing `{"image":"ghcr.io/planter/dev:1"}` whose Dockerfile carries `ENV CLAUDE_CODE_USE_BEDROCK=1` plus the planter's AWS keys redirects every prompt of the run with ZERO containerEnv…
  • sandbox: the image ENV record is DEDUCED from the container that started, and the page states the residue it cannot close (dde8cfe), references #2317

    whyTwo rounds of review contradicted one disposition each of the `sandbox_image_env_planted` event: `HTTP_PROXY` published as "set and EMPTY", then `NODE_EXTRA_CA_CERTS` as "let through untouched by the host knob", of containers `docker inspect` showed carrying iterion's own value. Both times the rules were right and the ORDER the daemon applies them in was not — a defect no care in a predicting caller removes. So the approach changes: the record is a MEASUREMENT.
  • sandbox: the image ENV screen promises the DECLARATIVE redirection alone, and deduces a doubled name order-free (04e02f4), references #2317 #63 #2317

    whyDoctrine A on #2317 (ARB-121, the ruling on question #63, amending ARB-119): the screen's one promise is the DECLARATIVE redirection of credentials and model routes. The neutralised sub-class is the credential/routing one and nothing else — every code channel of the image (BASH_ENV, NODE_OPTIONS, NODE_PATH, LD_PRELOAD, LD_AUDIT, SHELLOPTS/PS4, BASH_FUNC_*, NPM_CONFIG_NODE_OPTIONS, GIT_CONFIG_*, OPENSSL_CONF, BUN_OPTIONS, TAR_OPTIONS, ...) is REPORTED, not emptied: an image a repository chooses…
  • sandbox: the image ENV screen takes the whole startup-code class, and every disposition describes the container (582c8e5), references #2317 #2317

    whyRound 3 on #2317, after two independent reviews of the whole fix. The mechanism holds; what did not was the REACH of the class and the TRUTH of three of the five dispositions.
  • sandbox: the interpreter startup knobs answer at the plant gate alone, one row both gates read (17688e5), references #2844

    whyRound 6 put the nine interpreter knobs (PYTHONPATH, PYTHONHOME, PYTHONSTARTUP, RUBYOPT, PERL5OPT, PERL5LIB, JAVA_TOOL_OPTIONS, JDK_JAVA_OPTIONS, CLASSPATH) into deniedEnvNames as reportedOnly, and the image screen read them: ScreenImageEnvName answered denied=true, and pkg/runtime's measured rows went red — the screen's membership test is what ITERION itself starts, and there the knobs read nothing (the python node runs `python3 -I`, isolated mode ignores the whole PYTHON* family; no site of…
  • sandbox: the witness of the DEPARTURE, and the audit records that did not describe the container (777c0c6), references #2317 #2317 #59 #2317

    whyRound 2 on #2317, after an independent review of 2317.2. The mechanism and the refusal hold; what did not was the layer that CERTIFIES them.
  • secretguard,runtime: a rotated credential carries the scope of the value it replaces (ff21993), references #2756

    whyThe mid-run rotation seam registered the fresh value bare: a `hosts:`- scoped credential came back allow-any at the Layer-2 egress gate while the launch-time value of the same name stayed blocked, and a rotation of a DECLARED secret read back as a _redacted__ placeholder that resolves nowhere while the declared placeholder kept spending the dead launch value. Register now carries the replaced same-name registration's attributes over (Hosts, Sinks, env-only and ambient classes, placeholder, file…
  • secretguard: the guard's matcher state is copy-on-write, with a Register seam for mid-run values (5c84d05), references #2756

    whyThe guard was documented "immutable, built once per run" — so a credential that rotates mid-run (the clone's forge token, an as: file secret the runner's refresh loops rewrite) never reached the known-literal layer, and a bot printing the credential file after a rotation landed the fresh token verbatim in the run's log and events.
  • secretguard: the rotation chain carries a name's scope through every generation (933f57f), references #2756

    whyA second rotation of the same name matched the redaction-only remnant the first takeover left — not the live generation: the fresh value came back allow-any at the egress gate and the declared placeholder spent the dead intermediate token. carryReplaced now scans the name's materialisable generation first and falls back to the remnant for attributes alone, the remnant itself carries the name's attributes, and a sub-MinLen fresh value takes no slot over. The resolvers answer from one state…
  • server,auth,forge,webhooks,configshare,docs: the mint primitives demand the derivation verdict in their context (e94d5b9), references #2687 #2687

    whyRound 6 of #2687, structural switch arbitrated after five rounds of shape- chasing: the class guard that walked callers' shapes was hunted past once per form (name, value alias, branch arm, local binding, flow), and the real create-webhook route could run without its gate under green guards. The bypass is now impossible by construction:
  • server,auth: the mint verdict's inventory holds its whole currency by symbol, not by spelling (ef88404), references #2687

    whyThe stamper inventory (TestEveryTeamTokenMintVerdictStamperIsTheGateOrATest) matched a stamping as `alias.NewMintVerdict` — or as a bare Ident only inside pkg/auth — so a dot-import of pkg/auth spelled both constructors bare outside the package and the inventory saw nothing; and inside pkg/auth a MintVerdict{…} literal plus a bare context.WithValue(mintVerdictKey{}, …) minted the currency through neither stamper spelling. Both H-10 probes minted end to end with every guard green.
  • server,connection: a PATCH carries the version it read, and an empty capability list is refused (15bac9d), references #2758 #2759

    whyPATCH /api/teams/{id}/connections/{conn_id} read the whole record (credential included), applied its fields and wrote the whole thing back, with the only lock held inside Store.Update — so the window belonged to the HANDLER and none of the locking covered it. A concurrent cosmetic PATCH overwrote a revocation, a rotation and a capability narrowing in silence, both requests answering 200: the operator revoked a leaked connection, was told it worked, and `connections list` still read `active`…
  • server,forge,cli,docs: the team-token mint is gated where it is decided, and the mint class is closed with no name filter (3f29709), references #2687 #2674

    whyTwo independent reviews of the previous round found its class guard failing OPEN on a naming convention, and its gate posted at the route's door rather than at the mint — refusing eight runbooks that fabricate nothing.
  • server: a card's repo identity is the server's derivation, not a bot-arg (094644c), references #2371 #2099

    whyFinding 1 (HIGH, review of kimi18@30cb77758): the repository identity that bounds (launchSubject.Repo) and meters (spec.ProjectPath, the spend meter's key) a board run — and the repo audience a BYOK key resolves for — was __iterion_project_path, a bot-arg the member-rung board write routes persisted verbatim. A member could launch a run past its repository's ceiling and re-key an audience with one field (the reviewer's probe, replayed: the honest card refused, the forged one launched).
  • server: a pipeline board upsert re-stamps the stored launch context (c7dc7cf), references #2371

    whyThe create route's upsert branch matched a webhook-carried card via FindByBotInputPath and then swapped its bot_args wholesale (upsertPipelineTask): one POST {upsert:true} erased the reserved launch context the server had stamped - __iterion_project_path among the six - so the card relaunches with no repo subject to meter or bound, and silently loses its BYOK overrides. The same seam the PATCH door applies (KeepStoredLaunchContext off the stored card) now runs inside upsertPipelineTask, the…
  • server: a repo's quota identity survives every URL spelling, not one trim (f7f4e46), references #2371

    whyThe r2 review replayed the neighbours of the closed ".git/" form and all four launched in 202 against an exhausted quota: "o/hungry.GIT", "O/Hungry", "o/hungry.git//", "o/hungry.git.git". Two independent folds were missing, each measured to its own seam:
  • server: the board dispatcher derives, pays and is bounded by the card's repo quota (643308d), references #2371

    whyA board-dispatched run carried no repository identity: the launch gate was called with an empty subject and the run document no ProjectPath, so a whole launch surface escaped the per-repo ceiling an operator set and its spend was charged to nobody (D33). The card now carries the forge slug beside its clone URL — the webhook payload's own project path, stamped at carding, byte-for-byte the identity the meter keys on — and processBoardCard stamps it on the run and passes it to the gate, with…
  • server: the board's launch-context filter covers the whole mounting, and the slug derivation one trim (70ef72f), references #2371 #2099

    whyFinding 1 (HIGH, round-2 review of 029750b1f): the identity filter the first round put on four write doors missed the fifth on the same REST mount — POST /issues/{id}/comments stamped commentReq.BotArgs verbatim, so the forged __iterion_webhook_id + __iterion_project_path pair (ids readable on any webhook-created card of the team) stayed launchable one gate away, BYOK overrides riding the same door (the reviewer's probe replayed as a guard: the forged pair persisted, then the over-quota…
  • server: the connections CRUD registers where the spec can inventory it (ca8f6b6), references #2759

    whyThe route family's gate reads the auth stack (never wired in local mode, where there is no team for the path's {id} to name) rather than the mode string — so BuildOpenAPISpec's in-memory wiring registers it and the committed openapi.json carries the five routes. The spec builder stubs Connections with the memory store and the baked catalog.

4.7.1 (2026-10-30) ​

Bug Fixes ​

  • bots: the SSRF guard validates the target host even when it is the proxy's (#2848) (11561cd), closes #2847

    whyvuln-watch's four validators and feed-watch's one exempted from validation any URL whose host equals the egress proxy's host (a loop over the proxy variables, or `host in _proxy_hosts`), while prod-watch's six already carried the opposite doctrine. Wherever the proxy sits on a loopback or private address, every target on that address bypassed the check: with the sandbox proxy at 127.0.0.1 the loopback feeds were fetched, not refused, and TestVulnWatch_FetchRejectsSSRF /…

4.7.0 (2026-10-30) ​

Features ​

  • backend: the capability flags gain an unknown state and a provenance, read from the gateway catalog (feae61a), references #2103

    whyModelCapabilities carried Reasoning, ToolCall and Temperature as plain bools, so a gateway id (openai_compatible/<id>) — which resolved to nothing — read as KNOWN-false everywhere: the studio picker announced "no tools", modelCapabilityWarning blocked the launch, and `iterion models` printed no.
  • botinstall: a declared shared skill resolves at install, and a miss is named (b6fe9b5), references #2763 #2763

    whyUnit 2763.3 of #2763 — ADR-144 clauses 2, 4 and 5, on the shape ARB-70 = C decided. `exports.skills` / `dependencies.skills` already decode with their release floor (unit 2763.2); what was missing is the resolution, so a consumer can stop carrying a copy of a playbook another bundle publishes.
  • bots: prod-watch — the digest and the session modes (#1701, #1708) (b1f423b), references #2430 #2835 #2838 #2430 #2835 #2838

    whyArgus gains two run modes beside `watch`, on the one chain `plan` routes by four exclusive booleans (halted, step_back, collect, poll):
  • bundle: a regenerated catalogue in a pinned install is local state, not drift (7384a14), references #2689

    whyA router bot vendored under `.botz/<dep>` is a catalogue owner like any other, so every regeneration rewrites its `skills/iterion-bot-catalog.md` with the CONSUMER workspace's routing table — which is the point of vendoring a router. Measured at 1f5fde630: that write moved `bundle.ContentHashDir`, the verdict every pin check reads, so `bots sync` of such a dependency died on `installed "routerbot" failed post-install hash verification`, and `pkg/subbotsource` would have refused the consumer for…
  • connectors: the package loaders read io/fs, and the shipped packages travel in the binary (14ba0d8), references #2757

    whyA connector package is `connectors/<id>/`, a directory of YAML, so the only processes that could resolve one were those standing in a checkout of this repository: a published `iterion` served NO connector, and a `.bot` calling `forgejo.issue.comment` failed with "no connector catalog" on every machine that installed the binary instead of cloning the tree.
  • detect: a local gateway provider row, so iterion models answers openai_compatible/* on the host (f81da27), references #2646 #2646

    whypkg/backend/detect had no row for the OpenAI-compatible gateway, so `iterion models openai_compatible/team/m` read "iterion has no credential probe for provider" even with OPENAI_COMPATIBLE_BASE_URL set. The row is a PROVIDER (claw is the backend that drives it), available on the base URL alone — the key is optional, a gateway may sit behind network auth (compatgw.FromEnv's own reading) — with no SuggestedModel: a gateway's model id is the deployment's own naming, and the empty suggestion is…
  • dsl,secretguard: a secret is granted to the sinks it may reach (875c592), references #1949

    whyA `.bot` secret declares `sinks:` beside `hosts:` — prompt, agent, command, params, file, egress (the vocabulary lives in pkg/dsl/secretsink, a leaf the compiler, the guard and the runtime read) — and a secret that names none keeps every sink, exactly as before: the default is ALL and the restriction is opt-in (ADR-146, axis 2, decided by the operator).
  • dsl: a diagnostic about a property's value points at the property's line (970961f), references #1094

    whyThe parser records the span of every property line it reads — from the name to the end of the value, a block property to the end of its body — in the declaration's Properties table (ast.PropertySpans), on the workflow and on every node kind. A compile diagnostic about a property's VALUE (an invalid compress, a compaction threshold out of range, an unreadable allow: rule, a class-wide grant, an inert rule list, a sandbox mode or a network without one, the browser image Playwright needs, a…
  • feed-watch: the Mattermost digest and silence alerts carry the run footer (#2764) (f35560e), references mattermost#38480 #2430

    whyA Vigie digest now ends with the run link plus a Revi-style provenance block (Etape / Modele / Harness / Effort demande / Tokens for the synthesize step, and the run's own tokens) — Mattermost renders no details fold (mattermost#38480), so the block travels unfolded, whole on the last message, with its room reserved by the split; the max_messages ceiling trims the capped tail instead. Silence alerts carry the signature alone: no LLM step stands behind them.
  • log: the run logger redacts every record it keeps — one chokepoint, not N sink passes (92bdd2b), references #2385 #2458 #2458 #2458

    whyThe adversarial rounds of #2385 counted the open class: four sinks in hooks.go and six in executor_* printed a non-crossing producer's error raw, and six far-side arguments of hooks.go lines sat out of the relay's reach. Ten more per-site guards would leave the eleventh to drift, so the operator chose the chokepoint (option A on #2458, 2026-10-22): ADR-148 first, the redactor at the run logger second.
  • model: known values go back to their placeholders at the model boundary (bf79322), references #1949

    whyAxis 1 of ADR-146, decided by the operator as the model boundary alone: every task crosses into a backend through one seam (`ClawExecutor.executeDelegate`), which turns every known secret value in the rendered system and user prompts, the user content blocks and the preset focus back into its placeholder (`Guard.RedactForModel`, known values only, under the Layer 1 switch — placeholders off renders values by the operator's choice, and the boundary follows). A sentinel node A printed re-enters…
  • proc: one drain bound for every child process, and one override (1eec00f), references iterion#2851 #2469

    whyEvery shell-out bounded the drain of its child's output pipes with a literal and no way out, 500 ms to 10 s: exec.Cmd.Wait waits for the goroutines copying stdout and stderr, a copier ends only when EVERY holder of the pipe closes it, and a descendant the child left behind is such a holder — so that bound is what hands a node back, and also what loses the tail of a container's log. A load-bearing limit with no escape hatch is a defect (philosophy §1).
  • runtime: a dropped host bind is a run event, attachments no driver can deliver refuse the run (df92f93), references #2086

    whyOn a driver with no host filesystem the runtime drops the host bind mounts it declared. The runner's log said so; nothing else did, and a run that held attachments went on without them. Three things change:
  • runtime: a launch refuses a secret whose grants its sandbox cannot serve (91d4098), references #1949

    whyAxis 3 of ADR-146, decided by the operator as the documented status quo for the provider credential and an opt-in refusal through `sinks:`: a declared secret whose grants resolve at no sink this run serves fails the launch before any node runs — `egress` on an unsandboxed run or on a sandbox that substitutes nothing on the wire, `file` with no real driver, `agent` when every model node of a sandboxed run goes through the in-container claw runner — with a RuntimeError coded…
  • runtime: a loop clause opts out of the budget guard, and the decline names the way through (7cb7eff), references #2677

    whyThe back-edge affordability guard prices the next iteration at what the LAST one consumed — so a loop whose iterations shrink (a re-ask of the still-pending subset, a retry of the failed batch) is priced at its first, largest pass and declined on a stale measurement even when the next iteration would fit: a 43-item re-ask priced as the 104-item round, declined on duration, the run shipping with 43 criteria unanswered.
  • sandbox: a backend no published image can run is refused where the image is known, warned where it is not (4b8d57b), references #1 #1643

    whyThree of the seven backends run a CLI no image iterion publishes carries, and a sandboxed node on one of them died at `exec: <cli>: not found` — an explicit failure naming neither the cause nor the cure. The limit is the IMAGE's contents, not the backend, so an unconditional refusal would be artificial (philosophy #1): each plane now says exactly what it knows.
  • sandbox: a driver with no host filesystem receives the attachments as a copy, and a store with no root materialises them (eeb8e24), references #2086 #2086

    whyThe kubernetes driver has no host filesystem to bind the run's attachments from. Two seams carry them instead of dropping them:
  • server: a declared shared skill resolves on the launch plane too, and a miss stops the launch (69b46b5), references #2763 #2763

    whyUnit 2763.4 of #2763 — ADR-144's cloud twin, on the shape ARB-114 = A decided: the launch FREEZE resolves, and nothing durable is added.
  • server: a weighted admission gate bounds concurrent DSL readings' aggregate memory, and GOMEMLIMIT derives from the cgroup ceiling (3237af3), references #1690 #2693 #2693

    whyThe read budget (#1690) bounds ONE reading; nothing bounded the process across concurrent ones — three concurrent budget-edge /api/parse requests measured ~1.4 GiB against the chart's default 1Gi server limit, so one authenticated editor could OOM-kill the pod on demand, interrupting every run and stream it owns.
  • store: a StagingStore seam stages uploads on the run store, so the launch upload works in cloud (ed8f3c4), references #2086

    whyPOST /api/runs/uploads staged an upload in a directory under the run store's root; the cloud store has no root, and the endpoint answered 503 — the launch form could not attach anything in a cloud deployment.

Bug Fixes ​

  • attachments: close the adversarial review loop on the cloud-attachments stream (89a0618), references #2086

    whyThree reviewers on the three units (staging seam, sandbox seams, witnesses and readers) returned one critical, five highs, seven mediums and thirteen lows; each is fixed with a witness seen red by mutation (M47–M65) or assumed with its reason in the report.
  • auth,server: the desktop SSO ticket defers the login — no credential ever rests in the store (7059829), references #1668 #2635

    whyThe OIDC callback of a desktop flow stored the whole minted LoginResult — the plaintext refresh token included — in desktop_sso_tickets for the ticket's TTL, while every other session record stores only a hash; an unredeemed ticket left its session live past the ticket's own expiry, and the backup runbook's whole-database dumps kept that token for their whole retention (measured by #1668's rounds 2 and 4 against a real mongo:8.0).
  • backend: a known capability flag survives the sandbox hand-off, and the catalog tiers say what they answer (fd2ad48), references #2103

    whyThe record the host forwards to the sandbox (ITERION_OPENAI_COMPATIBLE_RESOLVED) carried the numbers only: a `tool_call:false` the operator's table declared read UNKNOWN inside the container while the record still claimed `source=operator` — the one disagreement across the IPC the forward exists to prevent. Writer and reader now share ONE struct (compatgw.ResolvedEnvRecord) instead of two mirrored literals, so the three `*bool,omitempty` flags cross by construction, and ResolveCatalog copies…
  • backend: the read paths price a gateway id quietly too, and the witnesses pin what the comments promise (ecac1cd), references #2103

    whyThe quiet read path closed the noise at ONE of the three paths it named. `modelcatalog.Build` — behind `iterion models` and GET /api/models — resolves every entry twice: the capabilities quietly, then the PRICE through cost.EffectiveRate → StaticPricer → the loud resolver. Listing three ids an operator was typing printed three warnings AND spent their per-id budget, so the launch that followed in the same process said nothing: the inversion the fix claimed to prevent.
  • botinstall,cli: the two things the local adversarial round found in the authoring-lock writers (5974d8c), references #2689 #2689

    whyThe round read the three units of #2689 against the question "what does this leave broken", and two answers stood:
  • botinstall: an install replaces a workspace copy file by file under each file's authoring lock (e877183), references #2689 #2826 #2689

    whyA `--force` install — and every `bots sync`, `update` and `localize` behind it, which all go through `Install` — removed the target directory whole (`os.RemoveAll`) and copied the source over it. Two defects in one pair of steps: no lock, so a save by the studio or the assistant of a file in that bundle was destroyed or torn (`--dest bots` points installs straight at authored bundles, and the authoring surface opens the materialized ones under `.botz` too — #2689 item 2); and the bundle was…
  • botregistry: the catalogue refresh replaces a stored file under its authoring lock (81bfe93), references #2826 #2689

    whyThe generated catalogue `<owner>/skills/iterion-bot-catalog.md` is a stored bot file: the studio, the assistant and the unit editor replace it through the server's authoring lock, while the regeneration — which runs after every bot create, edit and toggle, every install, and at EVERY run start — rewrote it whole with `store.WriteFileAtomic`. Atomicity is not exclusion: a save decided on the file's bytes, landing between the regeneration's listing and its rename, was replaced.
  • bots: skill-posted GitLab bodies escape quick actions, the server's own rewrite (47b6c8b), references #2413 #2422 #2422

    whyBots that post to GitLab themselves, with their own mounted forge_token, sent model-written text straight to the API — outside the server-side quick-action guard of #2413. GitLab runs `/merge`, `/approve`, `/label` … written at the start of a line, with the posting account's rights, API posts included; a model faithfully quoting hostile text from a diff, an issue or a dependency changelog at a line start ran it with the bot's rights.
  • bundle: a snapshot's required file is the launched entry, not the literal main.bot (18aa24b), references #1896 #1896

    whyA cloud launch of a loose catalogue file — bots/smoke/board_smoke.bot, no main.bot beside it — died at bot resolution ("bundle snapshot: parent main.bot is missing"): Snapshot.Validate hard-required <root>/main.bot, while the DSL side already promotes a root-level sibling to an entry of its bundle. The snapshot now names the launched ENTRY (Snapshot.Entry; empty still means <root>/main.bot, so every snapshot frozen before the field existed validates as it did): Validate requires that file, and…
  • claw: the run scratch is a read root of claw's read tools — rgaa-audit's briefs read on claw again (cff3ca2), references #2650

    whyOn claw, read_file/glob/grep refused any path outside the workspace, so a bot that keeps its run directory under ${PROJECT_SCRATCH_DIR} — out of the audited tree by design — lost every brief on claw while the same bot works on claude_code, whose Read is unbounded (run 01a15f60: 0 briefs read, 19 batches lost). The demo workaround (run_dir inside the workspace) costs resumability: a kubernetes teardown banks the scratch, not the workspace. Per the ticket's decision, this is option 1 — the…
  • claw: the scratch read root is pushed where the repo root and the sandbox are settled, and glob hands back a path read_file opens (cb016c7), references #2650 #2650

    whyAn independent review of the #2650 tip found two defects that left the ticket's own refusal in place. Each fix carries a witness seen RED with the production code mutated to the forbidden alternative, restored by the inverse patch with sha256 compared.
  • cli: a command that rewrites a bot's file takes the authoring lock and writes only over what it read (fed5fb3), references #2826 iterion#2839 #2689

    why`fmt -w`, `fmt --to`, `fix`, `dsl migrate` (its manifest floor raise included) and `import` read a bot's file and replaced it with no lock and no check: a save by the studio, the assistant or the unit editor landing between the read and the rename was dropped, and a save in flight was not seen at all. `store`-style atomicity was never the missing piece — exclusion was.
  • delegate: the exec that ends a sandboxed spawn joins the drain-bound class (b9ef6a7), references #2469 #2469

    whyThe exec that ends a spawn — the in-container files removed, the node session swept, the recorded CLI killed — read its report with no bound on the drain: `run.Command(...)` then `cmd.Output()`, WaitDelay zero unless a driver had set one of its own. Its 60 s deadline reaches the exec and nothing the exec forked, while exec.Cmd.Wait ends only when EVERY holder of the output pipe has closed it, so the subshells the sweep program runs in — and the `tr`, `grep` and `sleep` they fork over the /proc…
  • docker,test: the image build carries the connectors package, and the stray-property benchmark scales under the race detector (a0a7ba2), references #2757 #1094

    whyTwo reds CI found on main's T23 tip that no local gate runs: the go-builder stage of the Dockerfile copies cmd, pkg, internal and bots by name, so the connectors package the binary now imports (pkg/connection reads the shipped packages through connectors/embed.go) was outside the build context — the cloud-e2e image build failed on it; and the parser benchmark that parses a hundred thousand strays inside ten seconds took forty-two under the race detector, which multiplies the constant and not…
  • docs,test: the moved authoring lock lists its unsafe reads, and the refresh page keeps its byte budget (d1c6f95), references #2689

    whyTwo cross-wave guards refused the landing train on wave 123's content. The authoring lock's Windows file left the composition root without its entry in the unsafe-import allowlist: it now names its two reads (the NT object attributes' Length and the ACL entry's SID). The refresh page of the agents tree grew past its 10240-byte budget with the lock section: that section is the new tree page docs/authoring-lock.md, and the agents page keeps one line that points at it. Corpus pins regenerated.
  • docs: renumber the run-logger ADR to 149 — 148 landed on main first (0f9b6a1), references #2458

  • dsl: the deliverables and review_url rules point at their property too (ec18ece), references #1094

    whyThree value diagnostics landed on main after #1094's base and kept the node header as their position: the two deliverables rules (C317, C318) and the review_url that names an unknown node (C101). They now name their property like every other diagnostic about a property's value, so the finding sits on the line the author has to edit.
  • model: the provider walks read a tool node's recovery rungs the way dispatch spends them (cd0e378), references #2783 node#self_repair heal#agent_recovery heal#self_repair #2783 #2485 #2691

    whyir.RecoverySpec.Model was read by no provider walk: the provider a Verified Action's recovery rung spends was absent from ProviderResolution.Providers, Pins and AnthropicWireRoutes, so the providers allowlist (ADR-129 §5) could not check it and the two unfunded refusals (llm_fallback: none, ITERION_CLOUD_REQUIRE_LLM_CREDENTIAL) could not name it (#2783).
  • model: the self-repair rung has one reader, so no launch is refused for a rung dispatch skips (a7f2455), references #2783 heal#self_repair #2783

    why#2783 taught the launch walks to read a tool node's Verified Action recovery rungs the way dispatch spends them, and read rung 3 from `max_repair_attempts > 0` alone. Dispatch gates it on the RECIPE too: rung 3 asks a model for a corrected COMMAND and re-runs it, so a registry recipe — a bare `command:` resolved as a registered tool name — gives it nothing to correct, and `runVerifiedRecipe` reported it non-repairable. Priced anyway, that rung refused launches: a workflow carrying `recovery:…
  • notify: the completion callback keys on the persisted wall, and a skipped run settles (f41b47b), references #2707

    whyThe run-completion callback's predicate was IsTerminal, and failed_resumable is in that set — so a run parked on a provider usage window POSTed one callback per park (and per probe re-park) to a receiver that had asked to be told once, when the run is done. #2451's wall-keyed episode fixed the user notifications and the PR notice; this observer was left open on purpose, because fixing the predicate alone turns spam into silence: abandonRetry republishes only the outcome event, which the…
  • sandbox: the home a repository plants joins the env deny class, with the XDG dirs (b4ae2be), references #2318 #2316

    whyA found devcontainer.json could set HOME, XDG_CONFIG_HOME or XDG_DATA_HOME: the class let them through on the reading that the runtime's own HOME pin answered for them. It does not. The pin yields to a value already set (`!alreadySet`, sandbox_mounts.go:383), it takes HOME alone, and it exists only where host_state binds a home at all — so a repository under review pointed git at a gitconfig IT committed, where an `insteadOf` rewrites every URL the run's clone, fetch and push resolve (the…
  • sec-audit-source: pin deepsec 2.0.12 in the published sec image and fail the sanity step on drift (dbd2289), references #2350

    whyThe published sec image installed deepsec@${DEEPSEC_VERSION} with DEEPSEC_VERSION=latest behind a best-effort || echo, and the Layer 5 sanity step only logged deepsec's presence — so :edge served whatever npm had on the last base rebuild (measured on the current :edge: deepsec 2.2.6), across the 2.3.x release that changed init semantics (a bare init now throws MODEL_SELECTION_REQUIRED headless).
  • sec-audit-source: the deepsec assertion keeps one greppable override, so a local rebuild without npm still builds (f9b9999), references #2350

    whyThe Layer 5 assertion is right to refuse a published image whose pinned deepsec is absent or drifted, but it is also the recipe a local rebuild runs (docs/agents/ops/security-selfaudit.md, scripts/bench/ci-build-bench.sh). With the Layer 4 install degrading behind its `|| echo` on a machine that cannot reach npm or NodeSource, the refusal killed the whole image — every MANDATORY scanner installed, no image produced. A bound with no override is a defect (docs/philosophy.md); a load-bearing one…
  • secrets,server: the team emptiness gate counts user-scoped credentials, and a revoke that found nothing answers 404 (81f8431), references #1687

    whyteamResidue probed the credential stores through ListByTeam(teamID, ""), whose contract reads "" as "team-wide rows only" — the per-user resolution view: a team holding ONLY user-scoped credentials read as empty and was deleted with sealed credentials left behind in a tenant nothing can reach, the leak the org purge sweeper exists to prevent, arrived at through another door. And the api-key DELETE answered 204 on ErrApiKeyNotFound, so a revoke the tenant filter hid — the orphaned credential of…
  • server: the strict launch API rides a provision's engine-injected launch_vars (6239ff5), references #1757 #2416 #2416

    whyThe strict input check (#1757) refused every launch input naming no DSL var. A sovereign pool's provision stamps env-shaped dials — the ITERION_VIBE_* backend/provider/model ids — into the repo-bots integration's launch_vars, and those are answered by the engine's expansion through the bot-vars ladder (ADR-093), never by a DSL var: every API launch of such a bot was refused, and only the webhook path (no strict check) could run it.

4.6.0 (2026-10-29) ​

Features ​

  • auth: an org member reads the runs of teams flagged org-shared (ADR-148) (#2751) (0bcf0fb), references #2486

    whyThe run links the forge and Mattermost footers publish answer 404 to anyone who is not a member of the run's team. One sharing axis closes it: Team.RunVisibility ("org"), written only through an org-gated, double-audited route; at the run-by-id choke point a plain org member of the run team's parent org reads as a viewer, Via "org-shared", on the SAME store reads the org-admin-lineage arm already makes. Read only — the ladder is untouched, cdp/shell stay member-only, and passive run sockets…
  • author: the writer re-emits the document markers the author wrote (fa66001), references #1784 #2359

    whyA document whose comments hang on a `...` marker was rewritten into a marker-less one: the marker and its region disappeared with no refusal and no notice, and ADR-102 §5 recorded the loss as the rule rather than keeping its own promise. The writer now re-emits off the source the regions no node holds: a `---` opening the document, and the trailing region below its last content line — the markers, the comments and the blank lines written there, verbatim, their own indentation included. The…
  • auto-maintenance: agreement 2 authenticates the producer, and agreement 8 tells a bypass merge from a conforming one (9046876), references #1589

    whyA required check pinned by NAME alone is satisfied by any credential able to post a commit status, and agreement 2 only asked whether SOME integration_id was present. It now decides a triple — context x producing App x requiring ruleset — because dropping any leg answers a different question:
  • botscaffold: the goal-loop shape — a goal is a command's exit code and a cap (3d14112), references #1418

    whyLot 3's exemplar: `group goal(condition, cap)`, scaffoldable as `iterion bots create <slug> --template goal-loop`. Check (a tool, deterministic) → done when it exits 0, else advance (the agent) and check again, at most `goal_cap` times, then a typed `GOAL_NOT_REACHED`. The pair is a `group`, so one bot carries several goals (`use goal as deploy with { … }`) without copying the cluster, and nothing of the group reaches the runtime.
  • botsource: the whole-bot DELETE takes the version and the identity its caller read (8c4c728), references 1650/#2678 #2689

    whyRemoving a WHOLE bot was the one write of the family with no if-match at all: deleteBotSourceFor read the row by slug and deleted whatever that read answered, so a delete decided on a bundle a client read ten minutes ago removed the version another editor published since, with no error anywhere — while the per-file DELETE beside it has taken `?version=` and `?bot_id=` since #1650/#2678. The heaviest write of the family cannot be the one with the weakest guarantee.
  • bundle: a shared .bot fragment is a bundle dependency the install materializes (d1dc1cd), references #1418

    why`exports.fragments` / `dependencies.fragments` is the third instance of the two-sided key ADR-094 opened and ADR-144 extended: the producer publishes a `.bot` under `lib/`, the consumer declares what it may resolve and under which name its own `import "lib/<name>.bot"` reaches it. Resolution is a copy at install into the consumer's own `lib/`, so the parser, the unit loader, `E045` and the `lib/` confinement learn nothing, no new diagnostic code is minted, and every surface that reads the unit…
  • bundle: a shared skill is declared on both sides — exports.skills and dependencies.skills, with their release floor (fdf9d97), references #2763

    why`exports.skills` names what a bundle shares, `dependencies.skills` is the consumer's allow-list and the name its own `skills:` lists reference: the two-sided shape ADR-144 clause 1 decided, mirroring `exports.workflows` / `dependencies.workflows` for the other artefact kind. A producer-only key cannot express consumption, and the consumer is the side that must stop carrying bytes.
  • bundle: the one floors table learns manifest keys, and the taxonomy floor becomes a contract (192f847), references #2763

    why`syntaxFloors` now reads a FloorSubject — a bundle's executable sources AND the keys its own manifest declares — so a release floor can be asked for by a manifest key through the one table every floor surface already reads: `validate`'s C252, the push admission, the scaffold's own manifest and the release test. This is the seam ADR-144 clause 6 and ADR-145 clause 6 assign to "whichever key lands first".
  • ci: bake the pinned actions into the ARC runner image, from a generated manifest (6ccda6e), references iterion#2820 #2435

    whyAn ephemeral runner starts with an empty `_actions`, so every job downloads every action again from codeload.github.com — which answers 429 under load. On 2026-10-15 `test-unit`, `vendor-check` and `fmt-check` failed before any step ran and the merge queue ejected the pull request, the fourth time; a third party measured the same throttling with no ARC involved, so the remedy cannot rely on the service behaving. Baking does not; a mirror or a shared mutable cache would not.
  • cli: the reliability baseline reads the run store the pilot runs on (639edcb), references #1006 #1351 #1006

    whyStep 1 of the #1006 rollout — capture a baseline, promote, compare — was reachable only over the filesystem store `store.ResolveStoreDir` finds next to a working directory. The pilot runs in the cloud, where runs live in Mongo, so the before/after pair a promotion is decided on was being taken over a developer's local runs and compared with nothing.
  • connectors: install a connector package from a pinned commit (1480a1c), references #2757

    whyA git source fetches at INSTALL time and never at resolution: `iterion connectors install <git url | repository path> --ref <sha> [--path <dir>]` materializes the source at that commit, loads it with the overlay-applying loader a run uses, hashes the bytes that passed, and copies them into the tier LocalCatalogPaths names — asked rather than re-spelled, because an install that writes where resolution does not read is the one failure the command cannot report. A tier that dialled out while a run…
  • forge,server: re-authorise a needs_reauth OAuth connection in place (d04c996), references #2698

    whyA needs_reauth OAuth connection was a dead end: the connect wizard only ever Creates a new row, so the dead one kept the repository integrations (hook, minted project bot, managed secret) while its replacement started empty, and Disconnect deprovisions every repo. POST /api/teams/{id}/forge/connections/{conn_id}/reauthorize starts the same OAuth flow with the connection id pinned in the pending state; the callback, seeing the pin, verifies the authorisation completed as the connection's account…
  • lease: a term ends when the holder's passes keep reaching nothing (1498ce0), references #2685 #2685

    whyA term ended on lease loss or shutdown, never on the work failing, so a holder whose egress is broken renewed its lease through a store it could still reach while every pass failed and the healthy replicas never ran — the probe of #2685 measured 60 failed passes and 0 healthy passes over 20 TTLs, with every GitHub-App installation token and GitLab access token the forge sweep renews in the blast radius.
  • permission: the gate matches a path's spellings and a search's root (58b2f29), references iterion#1949 #1710

    whyA rule naming a directory was compared to a Grep/Glob call's search PATTERN only, so `deny: ["Grep(/run/iterion/secrets/**)"]` read as a bound and was inert: `{pattern: ".", path: "/run/iterion/secrets"}` read every mounted secret. And the matcher compared the raw string the model sent, so `/run//iterion/./secrets/x`, the same file named relatively, or a symlink into the directory walked past the deny that named it.
  • reliability: the pilot baseline publishes its divergence and counts the admissions it blocked (8b0df6e), references #1006

    why#1006's eight implementation tranches merged in September 2026; what the design still owed was the close-out it calls tranche 8 — state, against the code and not against the plan, which of its eleven acceptance criteria the tree meets. The ledger is now published in the rollout doc: one row per criterion, each with the owner, the witness that refuses its violation, and a verdict that is the measurement.
  • reliability: the rollout mode resolves in tiers, with the process env as a documented floor (2dd695d), references #2699 #1006

    whyG117 step 3 promotes admission to enforce BY LEVEL, with `report` as the documented hatch. A run-affecting setting belongs to a database level above the bot (ADR-132, #2699) and that resolver does not exist, so what ships here is the half that can be true today: the resolution is TIERED and the process environment is its documented FLOOR — the G65 pattern, declared now so the variable ships as a floor rather than as a value a later level silently overwrites.
  • rgaa-audit: hand the rendered layer's browser to the engine with browser_path (7ec4cf6), references SocialGouv/ultra11y#76 #76 #2348

    whyThe rendered layer could only run where Playwright's own Chromium (or Google Chrome at the vendor's path) was installed: the sandbox browser image ships Debian's /usr/bin/chromium, which the engine's resolution never finds, so the probe said « no browser » on the one image built for it.
  • rgaa-audit: pin ultra11y 5.49.0 — the engine honours browser_path, the trial covers the agents' non-applicabilities (d05763d), references SocialGouv/ultra11y#76 #2348

    why5.49.0 (SocialGouv/ultra11y#76, released 2026-10-29 10:16Z) launches the browser a caller names with --browser-path / ULTRA11Y_BROWSER_PATH, which is what the bot's prepare and render nodes hand it since the previous commit: the witness that stayed red on 5.45.0 is green on this pin. The pin moves in its four authored places (pin file, install lockfile regenerated from the registry, runner image ARG, the bot's engine_* defaults).
  • sandbox: the browser image names its Chromium for the rgaa-audit bot (46b4e0c), references #2348 #2701

    whyThe image ships Debian's /usr/bin/chromium, a binary Playwright's own resolution never finds, so a cloud rgaa-audit run on the one image built for its rendered layer probed « no browser ». ULTRA11Y_BROWSER_PATH names it once: the bot's prepare node reads it when no browser_path var is given and the engine launches exactly that file.
  • secretguard: carry the server-minted grant by environment, for authored bodies only (563926e), references #2033 #2033

    whyThe seam #2033 asks for: every store.ServerMintedSecretVars name renders as the guard's placeholder in a tool node's own `command:` or `postcondition:`, and MaterializeAuthoredShellEnv swaps the single-quoted placeholder for a reference to the CHILD process's environment. The bearer therefore never enters a command line — readable by any co-resident process through /proc/<pid>/cmdline for the whole life of the command, and copied into Kubernetes' pods/exec audit log — while every catalog bot…
  • secrets: a bot binding carries a kind, and both store twins refuse the name a bot already bound (6c7a08b), references #1908 #1908

    whyThe operator-side half of #1908 starts at the record: BotSecretBinding gains a `kind` discriminator and the `context` kind's payload — a PINNED source (git url, ref, path) plus the read credential it needs, by reference, never material. An absent discriminator reads as `secret`, which is the whole migration and a no-op: every document stored before kinds existed carries none, and a client that never sends one keeps the behaviour it has. The flat secret fields stay where they are because that is…
  • server,cli: the handover of a sick leader is readable where the sweep is (264bcaf), references #2685

    whyA handover that gives a term up on the work failing is invisible otherwise: an operator reading the watch-health answer saw `elected: true` beside a holder whose every pass failed and had nothing to tell the two apart, and after the lease moved, nothing said why. The state is the REPLICA's — the policy and its counters live in the campaign, not in the lease document — so it joins `elected` rather than `holder`, and `coordinator.handover` carries it: `armed`, `failing_passes` out of `passes`,…
  • server: a gate refusal carries its own identity, so its clear retires the record it consumed and no other (025f2d0), references #2174

    whyclearGateRefusal compare-and-swapped on At + AuditedSHA + Context, so two refusals agreeing on all three would see the fresher one swallowed by the consumed one's clear — and an episode left with no diagnosis to honor is the very thing the record exists to prevent. Unreachable on one replica, and the ticket says so: a refusal's At is the untruncated gateNow() (decisions truncate to the millisecond, refusals do not) and the token registry serializes its writes. This is a FORMAL closure, provable…
  • server: a PAT connection takes a replacement token in place (318d986), references #2488

    whyA personal-access-token connection had no way to take a new token, so a GitLab project/group access token — which MUST expire, 365 days out at most — ended the connection: revoked on the first 401, with a second connection and a per-repo re-enable as the only remedy, orphaning the old hook on the forge. PATCH /api/teams/{id}/forge/connections/{conn_id} now accepts {"pat": "<token>"} alone: the handler WhoAmIs the new token before writing anything, refuses (422) one that belongs to a different…
  • server: the engine posts the gate verdict on the merge-queue revision, and the Actions mirror becomes the documented fallback (644ffe5), references #1589

    whyA merge-queue entry is a revision no pull request owns: GitHub builds `gh-readonly-queue/<base>/pr-<n>-<base-sha>` and fires `merge_group`, which is not a pull request, so the review lane never runs and the required context is absent exactly where the merge decision is made. The event used to land in handleGitHubWebhook's default arm and be recorded as "unsupported X-GitHub-Event", filtered as noise next to ping and push.
  • server: the four elected nets report their verdict from the pass (2f5dad4), references #2685 iterion#2825 #2685

    whyThe handover of #2685 needs the pass to say what it reached, and every net threw that away: the token refresh discarded both counts and both errors of its sweep, the board sync logged each integration's failure and moved on, the assistant watch returned on a failed store read without a word to anyone, and the gate sweep returned only its cursor. A term could not end on the work failing because nothing told it the work had failed.
  • studio,docs: the connection card carries the repair gestures (6053420), references #2488 #2698 #2488 #2698

    whyThe card of a needs_reauth or degraded OAuth connection gains Re-authorise (redirects through the pinned flow; the wizard's ?connected= resume picks it up unchanged), and a PAT connection's card gains Replace token, both admin-only — until now the only gesture was Disconnect, which deprovisions every repository of the connection. The docs name the GitLab renewal recipe (Rotate on the forge, then Replace token) and the re-authorise flow, and the API table gains both routes. openapi.json, the…

Bug Fixes ​

  • auth: canAutoLink answers its failed reads instead of a permanent consent refusal (34bcba4), references #2655

    whycanAutoLink returned a bare bool, so each of its three reads — is the email's domain verified for this tenant, what grants does the matched account hold, is the team behind a privileged grant a personal one — turned a store failure into `false`, which the caller answered as ErrLinkRequiresConsent: a 409 whose remedy is "sign in with your password, then link". The account an SSO first login matches may have NO password, so that remedy is a dead end, and the refusal came back identically on every…
  • auth: org provisioning reads before its first write and undoes what it committed (cca8ed4), references #2655

    whyCreateOrgFor wrote the org row and its owner membership, then called CreateTeamFor — whose first act is three READS (the account, the org, the org membership). A read that failed there left a TEAM-LESS org holding the slug, repairable by a super-admin only, and the retry answered acme-corp-2. Two defects at one site: a read after the first irreversible write, and a write sequence with no undo.
  • backendcaps: the image-presence cells read the images, not the docs (7e6cdd8), references #1643 #1643

    whyThe seven image-presence cells were seeded from docs/backends.md:108-114, so the registry certified a prose claim: six backends read "unknown" and the seventh "unwired (no image)" because a table said so, not because anything measured an image. #1643 asks first for an inventory of what the images ship (G32), and an inventory that quotes a doc is worth nothing.
  • board: a card never carries the forge publish grant (a3dbfcb), references #1997 #2036

    whyThe publish grant a server mints for one run — the bearer and the three endpoints it is presented to (store.ServerMintedLaunchVars) — could rest on a board card's bot args, where a bot, an operator and a model all read it. #1997 closed the run surfaces and the pipeline-tasks API; the native board as a store and as a read surface stayed open.
  • botinstall: --force replaces an install, never work the installer never installed (edd8c21), references #2689 #2689

    whyA --force install REMOVES the target directory whole (os.RemoveAll) before copying the source over it. `--dest bots` is the committable location `bots install --help` itself names, and that is exactly where AUTHORED bundles live: pointed there, --force destroyed hand-written work with no lock, no version and no diff — the directory was simply gone (#2689 item 2). The flag's own help says it overwrites "an existing install", so this makes the code honour the contract the flag already documents.
  • bots: Nexie stops selling a durable brief she has no path to write (35567b2), references #2773 #2773

    why#2773 reported a contradiction inside one bundle: `nexie_system` forbade writing a workspace memory file while the bundle's skills and its manifest sold a cross-session `CONTEXT_BRIEF.md`. The ticket left the arbitration open because it read the write as reachable — "the native toolset is not bounded by that list".
  • bots: the publish grant reaches a script body by environment, not in the file (4093e2a), references #2033

    whyItem 2 of #2033: `bots/dep-update-guard`'s post_feedback node assigned `pub_token = {{vars.forge_publish_token}}` in a python `script:` body, and a script body is written to a temp file — pushed into the workspace by a copy-based sandbox — so the bearer sat in clear on disk for the node's lifetime.
  • cli: the cloud shard dispatch sends the route that launches a run (f4f8c54), references #2401

    why`iterion __scan-shards --mode=cloud` POSTed every shard to /api/v1/runs/launch, a path no mux registers: each shard answered 404 and cloud shard dispatch had been dead since it was written. The request body already matches the one route that launches a run field for field (file_path, source, run_id, vars, parent_run_id, shard_index, shard_count, shard_label all read by launchRunRequest), so the defect was the URL alone.
  • cli: the every-tenant reliability baseline bypasses the store's tenant filter explicitly (f768176), references #1006

    whyThe Mongo reliability source scoped a no-tenant baseline by leaving the context untouched, so the store's tenant filter panicked on the first listing — CI's mongo-conformance job reddened main on d51ce108e (run 40856575259, TestReliabilityReport_BaselineOverAMongoStore), a path the local gate never runs because it has no Mongo. The baseline over every tenant is an operator's read over the whole store: it now says so with store.WithoutTenantFilter, the documented bypass, instead of reaching the…
  • cloud: an org purge reaches every collection its deletion promised (f9776da), references #2401 #2401

    whyThree deletes that reported success and removed nothing, closed at their class rather than at the sites the ticket named.
  • cloudsched: the intent-schedule selection ranks by the oldest due intent, not lexically (c82d1d8), references #2724

    whyListIntentSchedules sorted the schedule ids lexically and truncated to the tick's 200 cap: with 201+ schedules carrying due intents, a lexical prefix that stays durably held (claimed, finished on hold, retried at every tick) re-occupies the whole selection and the 201st schedule is never attempted. Both store twins now rank a schedule by its OLDEST due intent's not_before (ties break on the id) — a fresh intent's zero not_before is the oldest there is — so the cap rotates toward the work nobody…
  • docs: the curated REST reference answers for the routes it names (415cf79), references #2401

    whyThe page's header promises its reader that "every endpoint listed here exists in pkg/server", and nothing held that half of the promise up — the same class as #2401's item 24 one layer over, where a page described a shard route the mux never registered.
  • forge: keep the minted publish grant off the trigger spine and the runner argv (e65e4e3), references #1997 #2033

    whyThe grant a server mints at launch still travelled in clear on two surfaces #1997 left open. emitForgeTriggerEvent mirrored the launch onto the trigger spine with the bearer in the event's PayloadVars — in-process locally, NATS in cloud, fanned out to every subscription that matches — and the client-driven custom ingress carried whatever a client sent under a minted var's name onto the same bus. Both drop store.ServerMintedLaunchVars now, the rule every client-driven write path already applies.
  • forge: key a publish grant by the sha256 of its bearer, with a dual read (211093f), references #2033

    whyItem 4 of #2033: a grant lived at `iterion:forgepub:tok:<bearer>`, so the key NAME was the credential. Anything that can SCAN, MONITOR or read the slowlog — an operator at a console, a sidecar, a backup of the keyspace — read live grants without ever reading a value. The key names sha256(bearer) now, which proves nothing a holder of the keyspace can present.
  • forge: the refresh sweep never retries a needs_reauth/revoked connection (f6f3b06), references #2698

    whyExpiringBefore filtered on kind and expiry only, so a needs_reauth row — which keeps its past expiry — was handed back to the refresher on every 10-minute tick: a guaranteed invalid_grant against the forge, a meaningless updated_at bump, and a sweep spending its term on rows it cannot help (observed on connection fc6afaf1, VAO/PIC, since 2026-10-22). Both twins (memory and Mongo) now exclude StatusNeedsReauth and StatusRevoked; a degraded row stays listed (the security-read lane owes it a pass)…
  • notify: the callback's final answer stops depending on a map walk (a89bba8), references #2401

    whyWith no callback_answer_node named, the completion callback probed Run.ArtifactIndex and posted the first final_answer it found. That index is a map, so "the first" is not a fact: a run whose nodes carry two answers posted whichever one the range happened to yield, and a redelivery of the SAME finished run could post the other one to the same receiver. The page documented the hole rather than closing it ("the scan has no ordering key ... which one wins is undefined").
  • queue/nats: an exhausted boot wait keeps the broker's last refusal in its error chain (1ad0c1d), references #2780

    whyWhen the budget runs out while an attempt is in flight, the dialer answers context.DeadlineExceeded; the exhausted error then wrapped only that, and a caller reading natsgo.ErrNoServers through errors.Is lost the one fact that says nothing is listening (CI race-shard (1) on 6c7a08b8f, run 40846686879, TestConnectWaitsForABrokerThatIsNotUpYet: want the library's refusal kept in the chain). The loop now keeps the library's last refusal and wraps both.
  • queue/nats: an expired boot attempt reports its deadline, not an invalid natsgo timeout (7698d72), references #2780

    whyUnder the race detector on a CI runner the attempt context could expire between the retry loop's check and the dial; natsgo then received a non-positive Timeout and answered "nats: timeout invalid", an option error that is not retryable, so Connect reported the library error alone instead of the bounded wait's wording (run 40829856051 on ba6f14619, TestConnectWaitsForABrokerThatIsNotUpYet). The dialer now answers context.DeadlineExceeded for an expired attempt — the class the loop already reads…
  • queue/nats: the loop's OTHER give-up keeps the refusal too (#2828) (fd7453a)

    why1ad0c1d9d taught the exhausted wait to keep natsgo's refusal when an attempt expires in flight. The loop gives up in a second place and that one still dropped it: the top-of-loop check, reached whenever the bound falls between the end of a pause and the check — a window a loaded runner opens wide. So `errors.Is(err, natsgo.ErrNoServers)` still answered differently depending on which exit the scheduler picked, which is the defect 1ad0c1d9d set out to remove.
  • rgaa-audit: the publication verdict holds the transcription judge's report to the plan in both directions (b30fb1d), references iterion#2388 iterion#2834 #2388

    why`publish_verify` compared the judge's own `created`/`existing` report with the plan and checked COVERAGE only, so a report that covered the plan with rows nobody planned passed clean: an `existing` claim naming a board issue the plan does not contain, a `created` list carrying a title the engine never planned, an object with neither `title` nor `name` counted as coverage under an empty string. The judge's own contract is "for EACH item of the plan", so a claim outside the plan is a…
  • runtime: a bundle default the workflow references fails the run when the bundle cannot deliver it (1c557cd), references #1908 #1908

    whypromoteBundleAttachmentDefaults folded "the bundle carries no attachments/ directory" into the same early return as "nothing is declared". A manifest that declared defaults the workflow references, in a bundle without that directory, therefore lost EVERY default of that bot in EVERY run the engine executes — schedules included — with no error and not one log line. The same fault one file at a time, a declared path missing from a directory that exists, already failed the run through os.Open: one…
  • runtime: the failure text a run records goes through the secret guard (9b4beee), references iterion#2822 #2037

    whyA `fail:` node's rendered `message:` reads the LIVE output map — which keeps real values on purpose, since it feeds `{{outputs.X}}` and the resume checkpoint — and the engine wrote that text straight onto the run document. Measured on a run whose tool reads a declared secret out of a file and whose refusal quotes it: `.error` held `cannot deploy: db_password: hunter2-…` in clear while `events.jsonl` and `run.log` already showed `__ITERION_SECRET_DB__`, because the event path is scrubbed at…
  • secrets,server: a credential pin no machine lane can see is refused at write, not dropped at launch (246cea0), references #2644

    whyA webhook's `key_overrides` names the BYOK key its runs must spend. The webhook fires under the synthetic identity `webhook:<id>`, the trigger spine under `trigger-spine`, and `secrets.Resolve` measures a key's visibility against the identity that spends it — so a pin naming a USER-scoped key is never even a candidate: `ListByTeam` drops a foreign `scope_user`, and `keyRank` ranks what survives 99, "never applies", pinned or not. The launch sealed the team's default key instead, and nothing…
  • secrets: run_secrets refuses a tenant-less read like its siblings (3942469), references #2401

    whyrun_secrets holds the sealed credential bundles and is addressed by a ref that is the document `_id`. Its filter helper's own comment claimed to mirror pkg/store/mongo's withTenantFilter — which panics on a ctx with no tenant unless the caller opted out — while it did the opposite and served the query unscoped. It was the one helper of a family of seven to do so: the api-key, binding, generic-secret, bot-source and plugin-source helpers all return an error instead.
  • security: SafeTransport pins no proxy — the SSRF guard decides egress (476a7ca), references #2338 #2338

    whyhttpdial.SafeTransport pinned the dial to a validated public-unicast IP but kept Proxy: http.ProxyFromEnvironment. Under an ambient PUBLIC proxy the guard validated the proxy's own address while the proxy picked the target — a private target reachable VIA the proxy, the dial leaving the guard's perimeter: the SSRF guard voided by its own transport. Under an ambient PRIVATE proxy every dial was refused outright.
  • security: the pinned delivery transports never consult an ambient proxy (c93b200), references #2337 #2337

    whyBehind an HTTP_PROXY carrying credentials, a plain-http target chosen by the run or the user received the operator proxy's Proxy-Authorization: both the completion webhook (pkg/notify/completion.go) and the studio preview proxy (pkg/server/runs_preview.go) built a transport with Proxy: http.ProxyFromEnvironment AND a DialContext that ignores the address it is handed to dial the pinned target. Under an ambient proxy, DialContext is called for the PROXY's address while the dialer connects to the…
  • server: a failed read answers its failure, never a fact (fc16ded), references #2655

    whyresolveConnector collapsed two different answers into oidc.ErrUnknownProvider: a provider row the store could not READ, and a client secret the sealer could not open, read exactly like a row that is not there. /start answered 404 "unknown provider" for all of them and /callback bannered unknown_provider with nothing logged, so a tenant whose provider store was down looked like a tenant whose SSO had been deleted — and that banner tells the user to sign in with a password an SSO-only account…
  • studio: a clickable row stops being a button that holds buttons (ec4a4a7), references #2766 #2766

    whyclickableRowProps promoted 14 containers to role="button" + tabindex, and four of them hold a focusable descendant — a button inside a button (axe nested-interactive, WCAG 4.1.2), where the row's Enter also raced the inner control's. The helper now carries the two shapes the surfaces need, so the decision lives at the chokepoint instead of being taken again per surface:
  • studio: a duplicate @group name is named to the author, and the group operations settle only the annotation the canvas drew (66e4ab7), references #1745

    whyThe dedupe in documentGroups made a hand-written duplicate invisible: the canvas rendered one box, and removeGroup / updateGroup — keyed on the NAME through mapDocumentComments — rewrote or deleted EVERY carrier of it, the author's undrawn line in a fragment they never opened included.
  • studio: a hidden run tab answers no window key and keeps no dialog over the tab on screen (ab2419d), references #1788 #1812

    whyRun tabs stay mounted while hidden so their stores and websockets keep accumulating, but nothing marked the subtree: useRunKeyboard's window listener moved a console nobody was looking at (j/k, the arrows, Escape, the g-chords), and a kit dialog a hidden tab had open stayed over the tab on screen — the #1788 class, on the run console.
  • studio: every page's document title names the page (RGAA 8.6) (3a17ec3), references #2516

    whyThe audit found 15 of 21 sampled routes sharing the identical title "workspace · iterion studio": useDocumentTitle's if/else chain named 8 routes and let everything else fall through to the bare project name, so /board, /bots, /secrets, /whats-next and eleven more were indistinguishable in the tab list and the browser history.
  • studio: no accessible name on a roleless element, guarded at source (#2767) (29e5def), references #2520

    whyARIA in HTML prohibits an accessible name on an element whose implicit role is generic — a bare span/div/p carrying aria-label or aria-labelledby — and axe reports it as aria-prohibited-attr (a span: a violation; a div: an incomplete), but only on the surfaces somebody mounted a strict entry for. The class is decidable from source, so this change fixes the remaining sites AND adds the guard that ends it:
  • studio: the file drop zones are labels, not rows that hold a picker (44b20a3), references #2766

    whyBoth drop zones spread clickableRowProps — role="button" + tabindex — around a focusable `sr-only` file input, and gave the zone the input's own `Upload <name>`: a control inside a control (axe nested-interactive, WCAG 4.1.2) announced twice under one name, with two activation paths to one picker. `sr-only` is precisely what keeps the input in the accessibility tree, which is why it is nested-interactive where a `hidden` input is not.
  • studio: the virtual start node renders first on the canvas (RGAA 10.3) (e3130be), references #2521

    whySecond landing — the first (3dca4d47) was reverted at f1b66ec9b because the moved block accidentally dropped the synthetic `fail` fill with it, and the expanded subbot lost its ::fail child. This time the hoist is an INSERT, not a move: the done/fail fills stay exactly where they were, and the subbot expansion suites stay green.

4.5.0 (2026-10-29) ​

Features ​

  • bench: reconcile the per-step usage feed against the turn checkpoints (6f2a1ab), references #1481 #2806 post-#2295 #1481 #2806

    why`bench discovery` reported the within-node split and its coverage, and a coverage percentage cannot say whether every producing site was honoured: a site that emits a zero-valued usage struct passes it. The report now compares the two INDEPENDENT recordings of the same spend field by field — one `llm_step_finished` event per model message against the turn checkpoints the node writes — so a site that stopped emitting, or that emitted twice, shows up as a signed gap on its own node.
  • ci: make the ARC canary's verdict a program a harness can redden (2f9b281), references iterion#2816 #983 #2816

    whyThe canary probes runner dispatch on arc-runners every 30 minutes and its watchdog decides whether iterion pages on its own scale set going deaf. That decision — the 25-minute threshold, the exemption that keeps a cancelled probe from alerting, what to do with a date the API did not answer — lived as inline workflow bash, so nothing in the tree could redden on it. The outage it exists to catch went unseen for eight minutes on 2026-09-29, and the nights of 2026-10-14..18 waited 5-8 hours for a…
  • ci: take the measurement cloud-e2e's routing has been waiting on (b3904d8), references #1955 #981 #981

    whySix of the nine Tests jobs run on the arc-runners scale set; `race` and `mongo-conformance` joined them on 2026-09-29 (#1955). `cloud-e2e` is the one left, and what keeps it GitHub-hosted is not a decision — it is a fact nobody has established: "kind comes up, kubectl cannot authenticate to it". The first hypothesis, an image whose user is not in the docker group, is refuted by the tree itself (`nats-conformance` and `mongo-conformance` reach the daemon from that same image), so the remaining…
  • claw: spawn real subagents at both production hosts, bounded by the node (59c79d3), references #1940

    whyclaw's `agent` tool reached no runner in production: with ClawDefaults.Subagent nil, claw's own executor answers {"status": "running"} and launches nothing, so the engine refused the call to avoid reporting work nobody did. Wire model.NewSubagentRunner at the two hosts that build the claw tool set — the in-process one (pkg/runview) and the in-container one (cmd/iterion, where tool.SandboxPlacementOf puts `agent`, and the default path for every sandboxed run) — so a delegation returns a real…
  • cli,studio: report what the run DID on every surface of the four records (8a68d93), references #1501 #1501

    whyUnit 1501.5 of #1501 — the surfaces half. The spine (units 1501.1/.2/.4) stamps the outcome tuple onto a cloud schedule, a trigger subscription and a board card; every reader still showed only the DECISION, so a record that fired and whose run died read exactly like one that fired and worked.
  • cli: validate --connectors resolves each action against the installed catalogue (f16ff5e), references #2762

    whyA compile checks the SHAPE of an `action:` id and says so in its own doc — whether the operation EXISTS is the connector package's answer, and nothing asked it. `--connectors` asks: every action node of a program that compiles is resolved against the package tiers of this install, and what they cannot serve is named — a connector no tier holds, with the tiers that were consulted, or an operation the installed package does not declare, with the operation and the version of the package that…
  • cloud: the boot wait for a late NATS broker, and a progress-deadline knob for the server (c6afec9), references iterion#2780 #2780

    whyTwo halves of iterion#2780, both measured on the wave-21 kind smoke runs where `helm install --wait` failed on pods that were Ready a minute later.
  • connection: a connection says what may be BELIEVED, not only what may be sent (dd69cae), references #2761 #2761

    whyA connection declares what it may be used FOR, and the set stopped at the two uses of the credential: `action` for a deterministic node, `agent` for the MCP facade. An inbound event is the same question read backwards — a delivery arrives claiming to be a vendor's, and something must have decided BEFORE it arrived that this connection is the one whose secret may vouch for it. That decision belongs where the credential's is, at the moment an operator entrusts the connection. `trigger` is that…
  • connection: the answer names the tier that served the package (0d41cd7), references #2769 #2757

    whyTwo tiers can hold the same connector id, at the same version, with the same scheme id and the same placement — what differs is what the operation DOES. So an operator debugging an operation they did not expect cannot tell which copy answered: not from the package (`spec.Package` carries no provenance) and not from the search either, since a layered lookup asks its tiers in order and only the one that answered knows it did.
  • connector: the connector package declares its inbound events (7ca4dbc), references #1072 #2761

    why`triggers.yaml` was named in pkg/connector/spec's own package layout and read by nothing — a format announced to package authors and ignored by the loader, which is the worse of the two positions. It is now a read part of the format, at the minimum that makes it true: a document with its OWN required schema_version (the overlay's precedent — a curated file borrowing the package constant lets a package-format bump silently widen what it may declare), the connector it belongs to, and triggers…
  • store: RunSource carries the webhook delivery (66e6323), references #1501 #1501

    why#1501 unit 1501.3 needs the run->delivery direction, and nothing had it: `webhooks.Delivery.RunID` points delivery->run while every consumer of the outcome class is keyed by RUN — the run-outcome spine loads the terminal run, then asks each record kind whether the run is theirs. The alternative, a fifth index on a hot TTL'd `webhook_deliveries` plus a by-run lookup, would be a second way to find one record (philosophy 5) and an index build on a live collection. Arbitrated as fleet ARB-41 option…
  • studio: a refused forge provision offers the gesture it names (3d052b6), references #2173

    whyThe provisioning endpoint refuses two launch_vars writes an operator can fix and names the fix in the refusal — a request that would drop pins it did not mention, and a repo whose two settings stores disagree. The studio could make neither gesture: forgeConnections never sent launch_vars_replace, so a 400 demanding it was a dead end, and the operator dropped to curl.
  • studio: an editor for a webhook's key_overrides credential pins (72c1ac4), references #2005 #2005 #2648

    whykey_overrides — the per-repo credential pin of #2005/ADR-131 — was typed in the studio's API layer and referenced by no .tsx: the only way to set it was `iterion remote webhooks update --data`. The webhooks tab gains a Credential pins dialog per webhook, with the stored count on its button.
  • webhooks,server: the delivery reports what its run did (b2102bc), references #1501

    whyUnit 1501.3, the last of #1501's five. The spine landed with three record kinds; the inbound delivery was the one the ticket could not reach, because it held no back-reference a run-keyed writer could follow. With `store.RunSource.DeliveryID` in place (previous commit, ARB-41 option B), the delivery becomes the FOURTH runOutcomeSink — the shape the spine documents for a new record kind, never an engine `if`.

Bug Fixes ​

  • ci: read a merge-queue rebuild's cause from its group's base, not a clock (d12a6c2), references #1967 #1979 #1979

    why`task ci:queue-stats` blamed a push for a rebuild only when the push's commit date fell within 90 seconds of it. GitHub rebuilt #1967's group 114 s after v3.217.0's commit (06:30:01Z then 06:31:55Z) and counted the build it discarded as the queue's own churn — the undercount the 2026-09-30 measurement named, on the one line the release hold exists to shrink.
  • claw: the sandbox crossing refuses an ambient credential header line (aa5db08), references #2813 #2244

    whyANTHROPIC_CUSTOM_HEADERS crossed into every claw sandbox whole, and the CLI applies that block LAST on every request it sends, for every provider (claw identity.go Identity.Apply) — so an ambient "Authorization: Bearer …" on the launcher authenticated the in-container runner at the vendor's gateway the engine had chosen, in place of the credential the engine resolved for the node. variable); it stayed open one backend away.
  • connection: the workspace tier is not consulted for a run whose code the tenant does not control (fb0544e), references #2810 #2769

    whyThe project tier — `<workspace>/connectors/<id>`, granted by ITERION_CONNECTOR_PROJECT_CATALOG=1 — outranks every other, and on a fork-lane run the workspace IS the outsider's checkout. #1802's refusal reads `spec.Effect.Mutating()` on the operation THE CATALOG RESOLVED, so a deployment that granted the tier and also served review forks let the reviewed tree ship `connectors/<id>/` declaring `effect: read` on a DELETE and have the guard read the attacker's own word for what the operation does.
  • corpusreading: the reading corpus pins no file git marks generated (#2819) (a7da89f), closes #2818, references #2750

    whyThe committed-reading golden pinned the three repository maps `iterion map gen` rewrites on nearly every landing (map-packages.md: 19 commits in 14 days; the golden itself: 125). A PR touching a Go package regenerates map-packages.md too, so its golden carried the one line main kept rewriting — a guaranteed merge conflict: #2750 was evicted from the merge queue three times in one afternoon on that line alone.
  • dsl: an expression is read on the read budget, the expressions of a bot together (4d63d80), references #2774 #2775 #2776 #2627

    whyAn expression — a compute field's, a quoted when's, a loop cap's, a fallback route's when: — lives inside one string token, which the lexer's read budget counts as a single record: a 10 MiB `a+a+…` passed the budget unseen, expr.Parse built millions of nodes (480 MiB retained), and the compiler's recursive walks then died on the goroutine stack limit — a fatal error, not a diagnostic, from one uploaded bot.
  • dsl: the deliverables rules are said once per node, the entries listed by excerpt (a3a0e37), references #2779

    whyC317 and C318 (validateDeliverables) were said once per entry, the first path of a collision quoted whole each time: a list is the one thing the read budget does not bound, so a 2.6 MB bot of refused entries ran `iterion validate --json` out of memory (414 MB of JSON before the OOM), and a thousand entries behind one long path were a thousand copies of it.
  • mcp: a launcher-started stdio server receives what it declares, not the launcher's environment (c94acbf), references #2809 #2018

    whyThe stdio transport handed every child the launcher's whole environment: os.Environ() merged under the server's own `env:` when that map was non-empty and, when it was empty, a nil cmd.Env — which Go resolves the same way. The CLI fills every unset variable from the nearest `.env`, a file that sits in the repository under review, so a planted NODE_OPTIONS, npm_config_registry or HTTPS_PROXY decided what the npx-based operator servers (firecrawl, codeindex) load and where they fetch from: the…
  • model: the credentials and OAuth-dir lookup installs give the previous reader back (7d29f50), references #2789 #2808

    why`credentialsLookupFn` and `oauthDirLookupFn` are process globals: pkg/runner installs a reader at boot, and a test that installs a double has to give the previous one back or every test running after it in the same binary resolves through the double. "Idempotent; the latest call wins" was not a restore contract — it was the clobber, stated in a doc comment.
  • server: the cost preview prices the whole unit, not the main read alone (c807c9c), references #1648

    whyThe chip asked `parser.Parse` for the candidate's main and compiled THAT file. A bot in several files carries `import "lib/x.bot"` lines the compiler refuses as such (C030, `the file imports N fragment(s) not merged into it`), so the preview answered `workflow_incomplete` and the chip rendered NOTHING for the one shape whose price is worth previewing — the price it did show was never the main's nodes alone, it was no price at all.

Performance Improvements ​

  • parser: the broken-list resync reads each consumed token once for its open-block stack (461d6bd), references #2771 #2768 #2626

    whyThe recovery of an inline list left open at the end of its line asked openBlocks for the stack of open block columns, and openBlocks replayed the whole consumed token stream on every call — quadratic in the number of broken lists: 40 000 agents each leaving a list open took 66 s of CPU to parse, on every route that parses an uploaded source. The stack is now kept current (openBlockStack): a call reads only the tokens consumed since the last one, an INDENT's column is read lazily off the first…

4.4.0 (2026-10-29) ​

Features ​

  • backend/model: a declared deliverable is announced in the run summary at its download address (a72f154), references #2690 #2690

    whyA tool node that hands over a file got a handle, not an address: the run carried `run_attachment_published` with an attachment NAME, and the only reader who ever saw the bytes was a human gate that happened to render its payload. The operator's ask on #2690 — "a downloadable artifact, surfaced in the main result summary like a PR link" — was unmet on the surface side even though the team-gated download route and its presign have existed all along (`/api/runs/{id}/attachments/{name}`).
  • backend/model: a declared file can be required, and its loss fails the node (9f28110), references #2668

    whyThe `attachment=` directive's loss became VISIBLE on the run (503d9917e): the operator now learns that a pod run produced a deliverable nobody can collect. Reading it is still not enough for a workflow whose POINT is the file — an audit report, a signed export: the node reports success and the run ends with the thing it exists to produce missing.
  • bots: copi's turn-1 posture reaches the model (08de8aa), references #2555

    why`turn_input.mode` travelled seed -> turn_state and stopped there: `copi_input` declared no `mode`, so the single `turn_state -> copi` edge carried none and no prompt rendered one. A `mode=debug` launch was therefore invisible to the model on the turn the operator chose it for — Terra re-derived a posture from the conversation instead — and three comments described a register that switched posture, which nothing did. Dropping the `vars.mode` delivery at the seed reddened nothing.
  • bots: every forge pusher proves its delivery or owns the paths it pushes (a8ba571), references #2436 #2437 #2436 #1595

    whyVetty and Billy prove, before they push, that the managed credential can deliver a diff touching .github/workflows/** (#2436). Nine other bundles push under the same credential and prove nothing, so a workflow-touching edit dies at the forge as a raw 403 whose cause the run never names.
  • bots: plan_revise carries the mission its plan node rendered (a330d5a), references #2482

    whyThe seven plan_revise nodes run `session: inherit_if_available`, which the engine may drop: applySessionContinuity marks the resolved id droppable and the executor degrades to a fresh session rather than failing the node. So the mission the plan node rendered — the operator's launch values — reached the reviser only when the session happened to survive, and the author answered a critique of a plan whose brief it could not see.
  • chart: a render gate refuses dead scheduling selectors, and every workload spreads its own replicas (fa58bbb), references #2012 #2012

    whyovh-prod held MongoDB and valkeyha on three nodes by chance: no anti-affinity on one, a dead selector on the other — `app.kubernetes.io/name In [valkey]` against pods labelled `valkeyha`, the sub-chart's alias — and neither helm lint nor the API server objects to a selector that matches nothing. This chart had the same class: the server passed an operator affinity through untouched and the runner had no scheduling knob at all.
  • chart: the datastore alert pack and the off-cluster copy of the artifact bucket (413bea6), references #2014 #2013 #2014 #2013 #2014

    whyTwo gaps of the cloud deployment, both read on ovh-prod on 30/09.
  • claudesdk: give the one-shot spawn a hook channel it can terminate on (0af3887), references spawn#2 #1672

    whypkg/backend/permission is enforced on claude_code by exactly one mechanism, a PreToolUse hook, and hooks reached the CLI only on the streaming Session path. claudesdk.Prompt — the one-shot path formatOutput uses for a node with an output_schema: — dropped cfg.hooks silently: configToProcess never read them and handleControlRequestOneShot answered anything but can_use_tool with an error. So the second spawn of a gated node, resuming the same transcript its untrusted content is in under…
  • delegate: the permission gate travels to the structured-output spawn (0135de3), references #1672 #1651 #2 #2782 #1672

    whyA claude_code node with an `output_schema:` spawns the CLI twice on one session under the same always-on bypassPermissions. The gate is a PreToolUse hook, so it bounded only the spawn it was registered on — the work pass — and the second spawn, the one that resumes the transcript the first pass's untrusted content is in, was answered by withholding the native roster instead (`gatedFormattingWithheld`).
  • dryrun: the preview reports what it answered a tool body's environment forms with (60843b3), references #1522 #2005 #1522

    whySlice 4 of 4 of #1522, and the last. ADR-130 §4 asks the preview to REPORT the reads it cannot answer rather than guess them, and the only place it said so was the attribution clause of a shell-syntax refusal — so every body an interpreter accepts, which is every body of a working bot, said nothing at all, and the half C188 declines to judge (a name only the engine's own environment supplies) was reported nowhere.
  • dsl: a codex node whose tools: list buys the workspace-write sandbox is told so (C316) (9193623), references #2427

    whyCodex runs its built-in shell outside the SDK's per-name callbacks, so a declared `tools:` list buys a sandbox MODE and nothing else: read-only when every name it carries reads, workspace-write otherwise — and then the list is a comment. Nothing said so at authoring time; the author learned it when the shared-worktree admission refused a fan-out. The compiler now warns on a node routed to codex — as its primary backend, through a `fallbacks:` route, or by a `${VAR:-codex}` default — whose list…
  • dsl: a node declares the files the run collects and serves as deliverables (3b34c77), references #2690

    why`deliverables: ["report.md", "out/annex.html"]` is the DECLARED twin of the `[iterion] attachment=` line a tool prints: the node names the files it produces, workspace-relative, each served under its base name — the filename contract G119 settled — on the three node kinds that run something in a workspace (agent, judge, tool). A human node's file comes from the operator's own upload and a compute node writes none, so there the parser refuses the property by name instead of dropping the…
  • dsl: name every run setting read from the environment at compile time (C315) (4f003e1), references #2699

    whyADR-132 section 6, stage 1 of its staged transition: the compiler names every dial a run would read a setting from — a route field, a var default, or a shell-bound tool body the engine expands — and changes nothing at runtime, so a deployment's pins keep working while the catalogue is de-dialled.
  • runview,model: the in-process lane holds the tenant's providers lock (7a48034), references #2665 #2665

    whyThe providers allowlist (ADR-129 §5) was enforced in two places the cloud path alone reached: the publisher's gate before it seals, and the runner's dispatch filter fed by RunMessage.LLMProviders (loop.go) — ExecutorSpec.LLMProviders had no other producer. A local server launch (startInProcess) resolved the same policy through the same lanes and enforced none of it: the lock a tenant wrote held on the queue and not in process, silently (#2665 item 7).
  • server,runview: the card lanes refuse a routing field their vars cannot decide (de39fa1), references #2665 #2665

    whyItem 2 of the #2665 residuals: the card lanes had zero shipped instances of the refusal a gate-only-certifies-what-the-run-spends doctrine asks for. A board card's vars are the delivery payload's own answer and no operator gesture follows the launch, so the board and ticket lanes now stamp LaunchSpec.RefuseUndecidedRoutingFields and the launch funnel refuses the first routing field — sorted by node, then field — whose {{vars.*}} span the launch's own vars reading cannot decide, naming the node,…
  • server: lift the run-outcome back-write to one spine the four records share (3794ba0), references #1426

    why#1426 gave a cloud schedule the outcome of its last run through an event-only subscriber. Rendering that class found three siblings that still stop at the DECISION: a trigger subscription that fired and whose run died read like one that fired and worked; a dispatcher card whose run died read as dispatched, its run seen only by the reaper's claim poller.
  • server: the card lanes hand a run the integration's var layers, operator names trusted (3cd4a7f), references #2665 #2665

    whyA carded run (board dispatch, pipeline admission) resolved its integration's routing block live by the carried id but none of the integration's two var layers: a docs-refresh card resolved backend from its declared default where the direct lane resolved claw from the operator layer, and a routing field dialled by an uncarried var could be refused under a providers list where the same integration's direct lane launched (ADR-129 §7's second lane-shape residual, #2665 item 1).
  • skills: declare one source per shared skill, with a gate that bites (acad6c2), references #2357

    whyA skill file lives with its bundle, so a skill several bundles need exists as several files. The doctrine was "duplicate, and add a TODO comment pointing at the peers" — a convention, not a mechanism. Measured at e44507a84: 20 file names shared over 72 copies, 16 of the 20 already diverged, five different spellings of the sync comment (TODO(skill-duplication) x5, TODO(skill-dedup) x4, "<!-- DUPLICATE -->", "<!-- DIVERGED copy -->", "<!-- TODO: duplicated from -->"), and exactly one family with…

Bug Fixes ​

  • backend/model: a declared file the run never received is recorded on the run (1b3898d), references #2668 #2668

    whyBoth stdout file verbs — `[iterion] attachment=` and `[iterion] preview_screenshot=` — stat and open the declared path on the HOST, and every exit that fails to publish left a single `logger.Warn` behind. On a sandbox pod that log is the one place the operator cannot read: the file lives in a container the host cannot stat, so a run could lose its whole deliverable and still look like it succeeded. #2668 paid it for real — a 1 h 45 RGAA report died with its pod.
  • backends: forward the routing settings claw's own providers read, and decide the web-search credentials by name (7802495), references #2802 #1804

    whyA sandboxed claw node rebuilds its registry from the env that crossed, and that registry builds claw's provider packages: whatever THEY read is read inside the container. The forwarding list only ever carried the names claw's provider TABLES spell, so four routing settings the launcher holds in its own environment never reached the runner — the host and its sandboxed twin on two different deployments, projects, regions or API contracts, with nothing said:
  • bots: Nexie stops promising a session memory the engine may drop (c0d15d1), references #2482

    why`session: inherit_if_available` is best-effort by contract — the executor marks the resolved id droppable and degrades to a fresh session instead of failing the node — yet Nexie's own prompt told her "your LLM session persists across turns, so you remember the whole conversation", her README called it "real memory", and her skill repeated it. Her whole input is `operator_message`, so a turn that lands on a cold pod has nothing else: the claim made her answer from a memory she did not have,…
  • bots: open Nexie sessions with an empty seed (e44507a), references #1897

    whyGive a bare whats-next launch a concrete opening request at the seed projection. Keep every supplied operator message unchanged so native slash commands and later chat turns retain their original prompt.
  • chart,compose: the dev Mongo starts on Linux 6.19+ — mongo 8.3.13 by digest, glibc keeps rseq (cc389a1), references #2084 #1411

    whymain went red on cloud-e2e at 05c188cd5 (CI witness run 40756099233): the dev Mongo of the chart exited at start on the runners' kernel with "MongoDB cannot start: Linux kernel versions 6.19 and newer has a known incompatibility with this version of MongoDB" (SERVER-121912): tcmalloc's per-CPU caches collide with the kernel's rseq. The image was the 8.3 digest of docker-compose.cloud.yml, landed by the Bitnami exit (D48).
  • chart,rendergate: adversarial round 1 — the gate judges what the API server applies, the chart reads its knobs as written (e54e3a7), references #2012

    whyThree adversarial reviewers (claude-opus-5-5) attacked the four commits of the wave with executed streams, renders, a PromQL parser and the upstream sources. Every finding below was reproduced before its fix and every fix carries a witness seen red by mutation, restored byte for byte.
  • chart,rendergate: adversarial round 2 — the H-10 findings (bfcc657), references #2012 #2013 #2014 #2084

    whyBackup: IterionArtifactBackupFailed pages a tier that never ran green (no last_successful series: an `unless` branch). PDB: "" and null are unset, the budget falls back to minAvailable 1, never to an empty one. Pools: a spelled nodeSelector / tolerations block is taken as written, even empty (hasKey, not sprig default). chart-render-diff asks chart-check-deps `--missing` for its listing (helm's blank line read as a missing dependency: exit 2 on every run). PromQL: a match is compiled (RE2) and…
  • chart,rendergate: matchLabelKeys fold on spread constraints too, and a listing helm cannot read never prunes a declared archive (7774eb7), references #2012

    whyThe bounded round on the round-1 fixes (one opus reviewer, executed on scratch streams and the real helm):
  • chart: one backup directory per release, no verbosity that prints the keys, and the backup rules read as written (91061b6), references #2013

    whyThe backup reviewer's final report (round 1): two more mediums and the lows it executed, each fixed with a witness seen red by mutation.
  • chart: the backup copy is checked as of its start, bounds its deletions, expires only dated directories, and tiers cannot share a prefix (dfa3056), references #2013

    whyRound 1's backup reviewer executed the rendered CronJob script under busybox sh with rclone shims and read rclone 1.71.1's source. Five defects, each fixed with a witness seen red by mutation then restored:
  • chart: the dev-stores policy admits the stores' own hairpin, and the app policy opens egress to the release's stores (e83a7f4), references #2084 #2012

    whyThe `<release>-dev-stores` NetworkPolicy excluded the stores from its peers: Mongo initiates its replica set against its own Service name, a hairpin that re-enters the pod with its own IP as the source, so on a CNI that enforces policies (kind's does) the set never initiated and the pod never turned Ready — kind smoke run 4 with networkPolicy.enabled=true red on `helm install` (Deployment ci-iterion-mongodb: Progress deadline exceeded, readiness probe "already initialized" for 14 minutes). The…
  • claude_code: restore the six tools the native roster forgot (13b2ed8), references #1404 #1651

    why`--disallowedTools` is derived from the roster, so on a node that declares a restricted `tools:` list and has no `permission:` gate the roster is the ONLY bound: a name it forgets is a tool the author did not grant and cannot remove. The roster had drifted to fourteen names while iterion's own second enumeration of the same surface — the display table it renders tool headers from — carried nineteen, and the CLI said so on the stderr of every claude_code node ("Permission deny rule X matches no…
  • cloudpublisher: a key the ceiling skips is still asked whether a reserve holds it (76efa26), references #2367

    whyThe credential walk's api-key predicate had two skip exits and only one recorded what the restore step reads. A key at its per-key concurrency ceiling returned before the evidence read, so a credential that is BOTH full and held by a budget-floor reservation left no floor-held note: at the end of the walk it looked exactly like a dead key, the fallback lookup remembered it, and the restore handed it to an UNRESERVED bot — the starvation the reserve exists to prevent.
  • cloudpublisher: the walk remembers every credential it passed over, per slot and per tier (36696db), references #2368 #2368

    whyA reserved credit masked a dead sibling. The credential walk remembered ONE skipped candidate per slot — `skippedAPIKeys[provider]`, `skippedForfaits[kind]`, with the org and platform tiers declining to record their own once the slot had been "seen" — while the floor-held memory is keyed by FINGERPRINT precisely so a dead credential and a reserved one are told apart. That asymmetry was the whole defect: when the remembered candidate was the one a reservation holds, the restore refused it…
  • diag: the global-memory write warning is C319 — C317 and C318 belong to the deliverables property (f84f790), references #2400 #2400

    whyWave 13 (deliverables:) took C317 and C318; the two branches meet in landing train T6, where TestDiagCodesAreUnique would reject a shared C317. Renumbered at every site of #2400 (code, catalogue row, docs, tests); no behaviour change.
  • dsl,claw: a session_slot named like another node is refused at compile time (8aa09cb), references #1547 #2460

    whyA persisted conversation is keyed on `session_slot:` when the node declares one and on the node id otherwise, in one keyspace, so a slot spelt like another node's id read and wrote that node's session: a `revise` pass with `session_slot: draft` replayed `draft`'s turns, its failed ones included, and compacted them on retry. #1547 named the collision at launch, a warning an author can miss; the compiler now refuses it where it is written (C314), so the collision cannot be compiled, and the…
  • dsl: lint the C315 env-dial walk — reflect.Pointer and the promoted field (b6cecf7)

    whyTwo golangci findings on the train-T2 integrated tree, both in the C315 commit (4f003e17f): govet asks for the inlined reflect.Pointer constant in the walk's test, staticcheck QF1008 asks for the promoted InteractionModel selector. Mechanical, no behaviour change; 0 issues after.
  • model: the provider walks read a human node's model the way dispatch spends it (d62f0cd), references #2691 #2691

    whyThe provider-allowlist walk and the anthropic-wire guard read a node's route through llmFieldsOf, which answers for agent, judge and llm-mode router nodes only. A human node carries a Model for its LLM interaction modes and was read as FIELDLESS: under any non-empty providers list a bot whose human node pins a model was refused whatever the list named, with a refusal naming subbots (#2691).
  • netproxy: a pinned host meets its pin under every spelling a resolver reads (d8a1d4b), references #2374

    whyA zoned IPv6 literal keyed the pin with the zone the pin's dial never carries, and the IPv4 notations only a system resolver reads (hex, octal, the one-to-three part forms) keyed as names — both reached the base dialer unpinned, the answer judged never the answer connected to. pinKey now drops the zone and normalises those spellings to the dotted quad they resolve to, on both sides of the pin.
  • netproxy: the default allowlist reaches Bedrock — an interior * names a regional endpoint (6a1c1bb), references #2342

    why`**.bedrock.amazonaws.com` matched no real host: Bedrock lives on `bedrock-runtime.<region>.amazonaws.com` (its FIPS twin and the `bedrock.<region>` control plane beside it), so a claude_code node routed to Bedrock under the default allowlist was refused at the egress — dead by default, unseen until someone used it. A leading wildcard cannot name a regional endpoint without opening the whole domain, so the rule grammar gains one shape, a single interior `*` (exactly one label between two fixed…
  • recovery: a node that ran out its own timeout is named by the site that froze it, not by the words it came back with (f25529a), references #2456 #2456

    whyThe third reader #2456 named still classified a cut by TEXT, and the two accounts a cut node gives both misled it. A sandboxed claw crossing reports what the far side saw — "signal: killed", a closed pipe — which falls to the catch-all one-shot retry; and when the in-node retry budget has room, the bound cuts the backoff and the loop hands up a BARE context.DeadlineExceeded, whose text is one of the network-transient needles. Measured through the real engine: the node runs 7 times over 195s of…
  • rendergate: a mapping key defined twice is a warning, not the gate's death (f1a30f5), references #2012 #2012

    whyRun on the real ovh-prod render of the GitOps repository, the gate died before judging anything: a ConfigMap of the published chart carries ITERION_CANONICAL_REDIRECT twice (the chart's native key and the same name under config.extraEnv), the API server keeps the last, and yaml.v3 refuses the document. A gate that refuses with it never reaches the selector it exists for — the valkeyha one followed in the same stream.
  • reviewtopology,server,cli: judge the frozen mono_family pin at every resume moment (ab620a8), references #2258 #2258 #1221

    whyA `mono_family` no credential serves refused the LAUNCH (#2258) and nothing else. A resumed attempt replays the inputs frozen at launch, so no second reading ever re-judged them: a run whose family lost its credential since came back, started, and died at its first review node — on the forge lane, a required check going dark on every pull request of the repo, with the 7-day cap on the runs listing hiding the dead runs (measured 2026-09-22).
  • runtime: serialize worktree registry mutations per repository (05c188c), references #2670

    whyCoordinate runtime creation, reconstruction and cleanup by the physical Git common directory using the existing resource semaphore. Release the gate after the command so isolated children still execute concurrently. Stage Git's half-written commondir state and keep independent repositories unblocked. Preserve meaningful whitespace in a separate Git directory.
  • runtime: the engine classifies a cut from its own context, not the crossing's words (3700e15), references #2456

    whyA sandboxed claw crossing the engine ends reports what the far side saw — a killed process, a closed pipe — and never the context error that ended it. Two readers still took the cut from the executor's chain alone: the budget arm, which let a node cut at the run's duration deadline fall to recovery and re-run instead of stopping the run as BUDGET_EXCEEDED, and the fan-out branch, which read a sibling retired by the first failure as a failure of its own and let its untyped error erase the typed…
  • runview,connection,cli: an action node is refused at admission, and the agent grant stops reading as a guarantee (11433e6), references #1072 #1072

    whyTwo honesty defects of the connector catalog (#1072, G101's (i) and (iv)); the cloud plane, the MCP facade and the three tiers stay out of this slice and are now real sub-issues.
  • secrets: the private-endpoint switch is the operator's, never a project .env's (cf2792c), references #2373

    whyITERION_LLM_ENDPOINT_ALLOW_PRIVATE crossed the sandbox only as the host's exact "1", but the runner inside the crossing auto-loads the workspace `.env`: a repository under review could plant the name and make the runner lax where the run proxy stayed strict. The switch is now read as the process inherited it (envtrust), in the host and in the runner alike, through the one function every reader — the gateway client, the claw backend's pre-flight, the retry chain, the run proxy's pin — already…
  • studio: a waiver covers the node it names, not the surface (4a43b0f)

    whyRound 3 of the adversarial review. Its headline finding is the mirror of the one the sentinels already answered: a waiver suppressed its rule across the WHOLE surface, so the pipeline drawer's `focus-order-semantics` waiver — for a window splitter — would have hidden the next roleless focusable added anywhere in that component, with the dead-waiver check still satisfied by the splitter. A waiver now names the node it covers by a substring of that node's own opening tag, exactly as the vendor…
  • studio: names that still collided, and a sentinel that would have drifted (a7f1a53)

    whyRound 4 of the adversarial review, and the pattern in its findings is one thing: a fixture kinder than the surface.
  • studio: the a11y net's naming scan, and the clamp it waives for (084532e), references #2591

    whyFour rounds of adversarial review on fd213ab4e, which landed with the first round applied. Every finding here is a defect the green gate could not see.
  • studio: the a11y net's own holes, found by reviewing it (050aa46), references #2770

    whyRound 1 of the adversarial review on the three commits above. The two findings it confirmed by probe were real holes in the net itself:
  • studio: the assistant dock and the editor toolbar answer their own strict axe entries (a01f7fd)

    whySame recipe as the Runs surfaces, two findings:
  • studio: the net declared more than it ran, and the net's own shapes (31a3b4d), references #2778 #2767

    whyRound 2 of the adversarial review. The headline finding is the one that mattered most: a DECLARED rule is not an EXECUTED rule.
  • studio: the Runs surfaces name every repeated control after the row it acts on (2d8d0c8), references #2766

    whyA run's detail panel announced "copy, copy, copy": one per tool payload, one per tool error, one per raw event, times every call on the node. `CopyButton` gains `describes` — what it copies, appended to the visible word, so the name starts with the label SC 2.5.3 asks for and ends with the row. `ToolCalls` builds that row once per card (`<tool> seq <n>`, the identity the card already shows) and hands it to every control it repeats, including the raw toggle and the payload block's…

4.3.0 (2026-10-28) ​

Features ​

  • deps: closure metric task + ADR-140 (language stays Go, sidecars behind seams) (#2743) (4dfee64)

    whytask deps:closure resolves the server binary's import closure and prints the non-stdlib, non-test source LOC with top contributors. Baseline on main 0b1de4062: 7,340,738 LOC across 847 packages; 68% is the modernc sqlite stack behind the codex SDK fork's session persistence.
  • server,runview: the credential preview takes the launch's vars (28a51d1), references #2665 #1757 #2665

    whyItem 3 of the #2665 residuals: CredentialPreviewRequest had no vars field, so no caller could preview a vars-carrying launch the way fill, restore and the gate read it. The request gains vars, honored on the personal source as the lane's own --var-style values (the operator owns them — ADR-129 §4 rule 2 trusts the CLI's --var names, so the preview trusts the same names for its routing pre-flights); the webhook source refuses caller values outright — its launch shape is the config layers plus…

Bug Fixes ​

  • dispatch: the catalogue check judges the committed tree, not the working copy (b061d55), references #2748

    whySince the zero-config dispatch mode the generated bot catalogue is a build artifact: git ships pkg/cli/templates/dispatch_bots/ empty (only .gitkeep, which keeps the all: embed pattern compilable on a clean checkout) and templates:dispatch-bots repopulates the working copy as a dep of build/test. The old check diffed a reconstructed catalogue against that working copy, so every clean checkout exited 201 before its first task check could say anything real.
  • server,ci: the watch-health test asserts the alarm channel; the mongo legs get runner headroom (#2755) (05c117d)

    whyTestAssistantWatchHealth_AFreeLeaseIsAStateNotAnAlarm asserted the raw Stale flag, which honestly reads true while the first post-election sweep is in flight; the alarm channel is sweepStaleIsAttention, which forgives that window. Under a loaded runner the window widened and the test failed twice on main. The assertion now checks the alarm channel.
  • studio: every Panels control names itself to a screen reader (RGAA 11.1) (70d7706), references #2591 #2591

    whyThe Panels slice of the #2591 surface triage (#2590's placeholder-only leads). Four controls had no accessible name at all: CommentsEditor's per-comment textarea, VarsEditor's bool Default select, and PromptEditor's non-compact body textarea whose visible sibling label was never associated — and axe 4.13 passes placeholder-only outright, so the render net could not see three of the four; VarsEditor's own axe entry was the one independent witness ([select-name], 3 nodes). The shared FormField…

Performance Improvements ​

  • runtime: fan-out boundary checkpoints coalesce into one durable write per window (38b15e5), references #561 #561

    whyRoutine fan-out boundaries (bounded-iteration crossings) persisted a full run record each: every save deep-cloned all branches under the invocation-wide save mutex and rewrote the run file — O(B²N) in branches and boundaries, serializing otherwise-parallel branch progress (#561).

4.2.0 (2026-10-28) ​

Features ​

  • bundle,botregistry: discovery claims what the bundle package claims, and a nested bundle is nothing (c538df1), references #2537

    whywalk's private two-file test is retired — bundle.DirClaim answers the claim (an iterion-shaped manifest, either spelling, or a skills/ directory; main.bot beside it opens it) and the walk, the localize path and the catalogue read that one verdict. A claimed directory without its entrypoint is a loud skipped source (a new non-blocking diagnostic kind, classified in the same change) whose subtree stays private; a manifest.yml consumer is a bundle under its own name — the demotion to a loose…
  • server,boardmongo: board issues join the by-id class — an issue is served from its team (c0b63c9), references #1849

    why#1849's lane (ADR-141): the choke point classifies the two board issue chains by the route the mux matched and resolves the issue's team team-blind through a new IssueTenantResolver capability — a cross-tenant _id-prefix read over the board collection (boardmongo.ResolveIssueTenant; cloud wires it beside CloudBoardFor and panics without it, self-hosted's single store keeps the class out). The caller's standing in the issue's team authorizes — reads for a viewer, mutations for a member, the run…
  • server: the localize path refuses a consumer discovery does not claim (72488ae), references #2537 #2537

    why#2537 slice A: the landed copy goes under plugins/<name> INSIDE the consumer's directory, and discovery only skips the subtree of a directory it claims — manifest.yaml (exact spelling) AND main.bot. A consumer failing that test is not claimed: the walk descends it, the copy leaks into the routing namespace as a top-level bot, shadows the .botz materialization, and the consumer drops from the catalogue. The localize refuses before anything moves, naming the defect — the spelling or the missing…

Bug Fixes ​

  • boardmongo: the issue-tenant resolve owns its timeout, the caller's context is not read (b8f04cb)

    whyThe SA4009 the gate's lint leg caught on the working tree AFTER c0b63c9fc was cut from its staged state: ResolveIssueTenant overwrote its ctx argument before the first use — the store mints its own deadline (ctxWithTimeout), so the parameter is gone and the wiring closure ignores the caller's.
  • routing: the launch fold refuses the split backend-provider pin the per-field composition certifies (7c331ee), references #2637 #2637

    whyThe per-entry write gate judges each override entry alone, while the launch resolves per field: {selector: X, backend: claw} in one entry and {selector: X, provider: zai} in another both validate, and the composed node override hands the dispatch exactly the pair the per-entry check exists to refuse — a pin certified at write time the harness ignores at dispatch, the spend following the default precedence (#2637).

4.1.0 (2026-10-27) ​

Features ​

  • admin: one cross-org runs view for the instance super-admin (#2148) (feba834)

    whyGET /api/admin/runs: every org's runs in one super-admin listing — org/team/state filters combine server-side, pagination rides a keyset cursor across orgs ((created_at, id) descending, the tie-break that makes one token page deterministically), each row names its team and org server-side, and runs stamped to a sovereign runner pool carry the stamp the console marks visually (A19). Read-only by design: opening ONE run rides the existing ADR-103 choke point, whose super-admin cross-team grant…
  • server: re-scope a request to its X-Iterion-Team header at the auth choke point (414e4c4), references #2662

    why#2662 slice 1 (server half): the access cookie stays the identity — a browser-wide artefact every studio tab shares — while the caller chooses the team a request acts on. The grant it costs is the standing a team switch would demand: identityInTeam recomputes the role in the named team, so the header never carries the active team's role across; a team without standing answers 403 naming the header (the resolveTenantScope contract); routes that scope by the team in their path ignore it; the…
  • server: the label door reads the catalogue's visibility switch (aa14cb7), references #2676

    why#2676: a label-triggered launch joins the card door's visibility judgment. One shared resolver — botVisibleForLaunch, launch precedence team > platform > baked — answers for BOTH provider doors through one filtered refusal arm (gateLabelBotVisibility): a bot disabled in its manifest or by the workspace overlay does not start on its label, a refused overlay starts nothing (fail closed, the card door's rule), and a bot no tier describes is not started either. A stored row answers through its own…
  • studio: a tab scopes its requests to the team it displays (b44001d), references #2662 #2662

    why#2662 slice 2 (studio half — with the server choke point already on main, this closes the ticket): the auth context publishes the team the tab last adopted at every adoption point (bootstrap, switch, identity re-read) and clears it on sign-out; the api client carries it as X-Iterion-Team on tenant-scoped requests. A switch in one tab no longer re-scopes another tab's actions: the request rides the browser-wide cookie — the identity — while scoping to what the tab displays, and the server…
  • studio: the cost chart exposes its figures as a data table (RGAA 1.6) (#2503) (419e215)

    whyThe cost-per-day chart hid every figure in hover-only <title> elements: non-mouse users (and screen readers, which get role=img + aria-label only) had no way to read the per-day per-workflow costs. CostChart now renders an sr-only data table under the svg - one row per day, the per-workflow figures and the total as real text.
  • studio: the cost chart exposes its figures as a data table (RGAA 1.6) (#2503) (a1cba14)

    whyThe cost-per-day chart hid every figure in hover-only <title> elements: non-mouse users (and screen readers, which get role=img + aria-label only) had no way to read the per-day per-workflow costs. CostChart now renders an sr-only data table under the svg — one row per day, the per-workflow figures and the total as real text.
  • studio: the plan-du-site page and the hoisted nav registry (RGAA 12.1) (#2531) (3102765)

    whyThe studio had one navigation system (the sidebar) and no site map. NavLinks hoists its link registry (NAV_REGISTRY_LOCAL/CLOUD, the gates stay at render time - a flattened registry would leak admin hrefs into non-eligible users' sitemaps), and the new /sitemap page renders it under group headings plus the admin console sections. The sidebar footer links the page from every authenticated surface (the collapsed rail gets the icon).

Bug Fixes ​

  • docs: escape tag-looking changelog prose before the vue compile (9460658), references #2509

    whyAn html_block that opens with <details swallows its body up to the next blank line, so the whole "why" prose rides inside one token and any tag-looking fragment in it (<tbody>, <run>, <Route ...>) reached the Vue compiler raw — a real tag name quoted in prose (the #2509 body's <tbody>) opens an element that never closes and aborts the docs build. The html rules now escape tag-by-tag and treat the changelog page (and any details block elsewhere) as prose: only details/summary stay markup,…
  • dsl: prompt bang refs render their value, the backend chain decides C018, stored siblings validate with their bundle, discovery parses each bot once (11f9e2b), references #1775 #1796 #2168 #1764 #1761 #1761 #1764 #1775 #1796 #2168

    whyAdversarial-Rounds: 1 (witnesses reddened on their own assertion, then green) Adversarial-Model: claude-fable-5-1
  • forge: a provision's identity is minted once and durable, and a redirected write is refused, not read (96b083c), references #2452 #2475 #2452 #2475

    whyTwo interleavings left a repo's webhook unable to verify its own deliveries. Two overlapping provisions of one repo each minted a fresh secret and wrote the forge-side hook and the stored config independently: whichever interleaving won decided the forge's secret, and the store could hold a different one, failing every delivery until a later provision succeeded alone (#2452). And a forge base that answers a write with a redirect turned a re-provision's hook update into a body-less GET: Go's…
  • model,delegate: a far-side log block is bounded on what the logger writes, and the claude_code thinking block asks the sink before it pays (56dd508), references #2470 #2458

    whyThe five far-side block sites (#2470) rendered the relayed text with the logger's continuation prefix on every line: a 1 MiB body of single-byte lines wrote ~7.5 MiB of run log, and past that the log held the only copy of the thinking text. The four claw relay sites (response text, tool input, raw tool input at debug, thinking) already share one choke point, so the bound lands in storeHooks.logFarSideBlock: BlockPreviewRendered at maxFieldSize, redact first so a secret straddling the cut is…
  • rgaa-audit: migrer vers ultra11y 5.45.0 et exporter le bilan complet (#2745) (c24baf5), references #2741

    whyConsume the split business report and technical annex throughout draft review and final validation. Publish only files produced by each fresh report generation, including screenshots, both HTML entries and page grids. Keep lexical paths consistent through symlinked run roots while preserving canonical containment, engine fingerprints, audit hashes and replay guards.
  • server: a file past the journal's read budget saves, and a journal limit answers 413 naming its knob (36636b0), references #480 #2661 #2661

    whyThe unit save read the destination under ITERION_ASSISTANT_AUTHORING_MAX_TOTAL_BYTES (#480) to compare it against its Before: a file past the budget could never be compared whole, so every save of one answered 500 "authoring file exceeds the read limit" while the SAME file through the single-file door answered 200 — that door reads its destination whole (#2661). The budget is the journal's reading bound, not a save's: the unit save now reads its destination whole like the single-file door does,…
  • server: the binding-level locks 422 names every holder of the lock primitive (95b38d2), references #2688 #2688

    whyThe 422 a binding surface (a schedule row, a forge integration) answers on routing.locks: sent the operator to two of the four holders — the author's manifest and the platform record — while the team and org routing records, the ones a team admin actually owns, went unnamed (#2688). The schedule decoder comment carried the same short list.
  • studio: clamped text keeps its full content recoverable (RGAA 10.12) (#2526) (0e00c76)

    whyClamped user-facing text (truncate, line-clamp) put descriptions and names permanently out of reach. ui/ClampText is the chokepoint: the clamp stays visual, the full text rides in an sr-only copy and the block reveals on keyboard focus. The four audited sites convert (RecentFilesPanel's descriptions, SidebarContext's search label, the bot name/id/description cards, the bot-catalog page button); the rest of the raw-truncate census (108 files) seeds the guard's drain allowlist - the canvas family…
  • studio: make the admin console pass its own studio leg (6577dfe)

    whyThe admin landing (feba8344a) left the studio leg red on main: the routes test mocked every console page except RunsAdminPage, so the real page rendered without an AuthProvider and crashed useAuth on the three /runs mounts; AdminNav's expected list lacked the Runs entry the route table declares; runsAdminPage.test.tsx called expectNoViolations without its label argument (tsc -b); and RunsAdminPage itself broke the a11y source guards — two raw <select> where the Select primitive is mandated,…
  • studio: the run-console links state their destination (RGAA 6.1) (#2510) (15475a2)

    whyTwo run-console links named themselves by a bare glyph: the browser pane's "open ↗" (the destination URL lived only in the tooltip) and the gate file's "Download" (which file, unclear when several gate files stack). Both carry aria-labels naming the action and the target (the current browser URL; the attachment's file name).
  • studio: the workflow switcher's Select carries an accessible name (RGAA 7.4) (#2513) (c9bfb16)

    whyThe toolbar's workflow Select swapped the canvas on change with no warning and no accessible name. It now announces itself (Active workflow - switching redraws the canvas) so assistive tech hears both the control and the consequence; the canvas redraw is the documented behaviour the label states.
  • test: the home guard fails a suite on test-key stores it gains, not on the operator's own writes (19bec72), references #2011 #2714 #2011 #2714

    whyThe real-home guard #2011 promised was never written; a blind one would fail every suite the operator's live studio writes into while it runs — the home is shared (#2714). The guard lands with its key filter from the start: hometest.Isolate snapshots the test-key stores under the real home's projects directory before the run and scans again after; an entry the run GAINED fails the suite naming it. The keys are exact forms (#2011): "-tmp-Test*" for a workdir under the default temp root,…

4.0.0 (2026-10-27) ​

⚠ BREAKING CHANGES ​

  • server: isolate corrupt identity rows and sanitize 5xx responses

Features ​

  • backendcaps: one declarative capability table for all seven backends, seeded cell-for-cell from HEAD (d24de5b)

    whyThe backendcap registry is the single declarative table of what each of the seven backends ships, refuses or lacks - name, severity as a column, the evidence, and the axis - seeded from the state at HEAD (the e2e live ledger's 57 targets: 55 never, 2 pass) with a guard proving zero verdict moves: a registry diff in a PR is an explicit verdict change, which is the review surface the upcoming parity doctrine will work against. Four enumeration sites now derive from the table (the route/detection…
  • ci: the test capability census - skips named, sleeps inventoried, CI publishes the state (6132671)

    whyThe repository's test capabilities were unmeasured: 757 skips whose reasons were never censused and 486 sleeps whose durations were never inventoried. The static census generator (scripts/ci/testcaps) scans the source tree without running tests or services and produces deterministic census.json and summary.md - two generations are byte identical - plus an optional runner.json observing literal prerequisites on the machine. The CI job runs the census on every test run and publishes the artifact…
  • mcp: a plugin MCP server's {{workspace}} is the run's workspace (#1800) (08ca164)

    whyPrepareWorkflow takes the project dir (for .mcp.json) and the workspace (for the {{workspace}} placeholders) as two separate, explicit inputs, threaded through the whole runview compile API; every launch surface passes the engine's workDir, and sub-bot children, dispatcher dispatches (per-run recompile, never the shared IR), and studio launches resolve {{workspace}} against the run's own workspace. Surfaces without a run pass "" explicitly.
  • release: the syntax floor accepts the symbolic pin "next" and the cut rewrites it (c55941b)

    whyA numeric ahead-pin rusts on a waiting branch: HoldSyntaxFloor demanded exactly the next minor above the newest changelog release, so every cut forced a manual re-pin. The syntax floor now accepts the literal "next" (strictly above every released version), the cut rewrites it through the same ApplyPin pass across floors.go, pkg/bundle and the bots' requires manifests - two "next" pins in one cut take one version - bundlelint and the catalogue stay green while a pin is symbolic, the missing…
  • studio-e2e: the studio Playwright e2e suite runs in CI, loudly (9c0d139)

    whyThree specs were red on a normal host and nothing reported it: the run-console locator matched both the log line and the session card, and the launch spec asserted a caption the per-node picker no longer renders (product decision documented in place). The run-console locator is now exact; the caption assertion moved to the section that still renders it. The suite now runs in the studio-e2e-advisory workflow on the pinned chromium, and the local task names its skip with the install command…
  • studio: single-key shortcuts are disablable and truthfully listed (RGAA 12.10) (30bd1f6)

    whyBare keypresses (?, /, v, h, j, k, the g-chords, and the board's c/e/x) triggered app-wide actions with no way to turn them off, and the help dialog hand-copied its list. lib/shortcuts.ts is now the one registry: the help renders its rows, every branch consults shortcutsEnabled(), and the ui store persists the setting (Settings > Appearance > Single-key shortcuts). Modifier combos and dialog dismissal (? on the open board help) stay live whatever the setting.
  • studio: StatusMessage, a status region assistive tech hears (RGAA 7.5) (#2514) (3c24b92)

    whyThe bot builder's draft-saved span toggled its own opacity while the text stayed static: role=live politeness announces nothing when no text is injected. StatusMessage is the shared primitive — a permanent role=status whose text changes — and the builder site feeds it the message conditionally. Kind stays in the text, never motion alone.
  • studio: the reduced-motion setting is an in-page control (RGAA 13.8) (8513012)

    whyThe OS-level prefers-reduced-motion reset stays; the criterion also asks for a mechanism ON the page. The ui store now carries motionReduced (persisted, mirrored as [data-motion=reduced] on the document root before first render), AppearanceTab exposes the Animations radiogroup, and the infinite animations gate on it:

Bug Fixes ​

  • bots: name the human behind an arming and let respect_human_arming protect it (b60e25c)

    whyThe dep-update guard queried enabledBy but never read it, so a maintainer's deliberate "merge when ready" was destroyed exactly like the bot's own previous arming - the manifest's own principle (arm_automerge must not undo what somebody else decided) contradicted the behavior. The arming query now resolves the owner, every reason that removes a standing commitment names it, and the new manifest var respect_human_arming (defaults to the manifest's stated principle) protects a human's arming from…
  • bots: name the human behind an arming and let respect_human_arming protect it (4d0fc31)

    whyThe dep-update guard queried enabledBy but never read it, so a maintainer's deliberate "merge when ready" was destroyed exactly like the bot's own previous arming - the manifest's own principle (arm_automerge must not undo what somebody else decided) contradicted the behavior. The arming query now resolves the owner, every reason that removes a standing commitment names it, and the new manifest var respect_human_arming (defaults to the manifest's stated principle) protects a human's arming from…
  • bots: the fourteen catalogue guards read catalogWorkflowFiles instead of globbing examples (067bfc0)

    whyThe fourteen catalogue guards globbed examples/ with their own pattern and no floor, so a layout rename that stopped a glob matching left each one green over a corpus it no longer reads. The guards now build on catalogWorkflowFiles (the one table of shipped-workflow roots with a per-root floor), and nine of them see the subbot children their globs never matched - the new verdicts triaged one by one.
  • budget,runtime: ceilings are absolute - the exit grace cannot spend past them, and raises cannot cross them (ea11a44)

    whyCapImposed meant "the clamp changed a value", so a run declared at or below the platform ceiling (or a donor allowance) kept CapImposed false and spent the full 10 percent exit grace past the ceiling it was under. The marker now means a ceiling is in force, the graced limit is min(cap*(1+r), ceiling) - the deadline included - and the donor half of the ceiling travels a new wire field (SchemaVersion 27, the first free number after 26; messages without the marker refuse and get republished by…
  • cli,sandbox: the schedule crontab carries no PATH line, and the container refusal names what it searched (c99aac7)

    whyrenderCronBlock wrote the install-time PATH into the crontab verbatim and Vixie/cronie silently cuts one env assignment at 1000 bytes, so every cron-launched run lost the system directories sitting in the tail. The crontab now invokes a POSIX wrapper the install writes beside it: the wrapper sets the sanitised PATH (empty and relative entries dropped, duplicates removed, missing system dirs appended, shortening trims from the tail while sparing system dirs) and names what it dropped. The…
  • dsl: resolve workflow auto without compiler host probes (db68f4c), references #2657

    whyApply BackendFallsThrough to the workflow default after template and environment resolution. Compile with an explicit target capability snapshot instead of probing the compiling host; preserve pessimistic preflight guards and defer unknown target availability to execution.
  • model,runtime: the reserved output namespace is engine-authoritative, and auto-answered interactions book their spend (ecee17b)

    whyA delegate output's reserved `_` keys were read straight out of the map the backend returned - which for non-claw backends is the model's own JSON - so a model could forge `_tokens: 99999999` to burn a declared budget or `_tokens: 0` to under-count past it. stampDelegateOutputMeta now strips the whole reserved namespace and rebuilds it from the harness's own counters, the router and the relay re-stamp behind it, and the claw runner's oversized fallback bounds the relay-added string scalars its…
  • platformcfg: classify ITERION_SCHEDULE_PATH_MAX_BYTES as infra (1b408d2)

    whyThe schedules-refusal landing added the cron-line PATH bound knob (pkg/cli/schedule_path.go) without the platformcfg classification - the infra census flagged it. Classified in botVarsInfraExact: the process's own posture, never a bot variable.
  • platformcfg: classify ITERION_SCHEDULE_PATH_MAX_BYTES as infra (43d1cee)

    whyThe schedules-refusal landing added the cron-line PATH bound knob (pkg/cli/schedule_path.go) without the platformcfg classification - the infra census flagged it. Classified in botVarsInfraExact: the process's own posture, never a bot variable.
  • preserve the deepsec export schema (#2592) (05d4733)

    whyAdversarial-Rounds: 7 local (announced 6, ceiling 20) Adversarial-Model: kimi-code
  • regenerate the maps and corpus golden after the catalogue-guards landing (a0522f2)

    whyThe catalogue-guards landing rebased cleanly (no conflicts) and pushed its pre-rebase goldens - the maps and corpus golden were stale on the new base. Regenerated on the landed state.
  • runner,dsl: a node the launch vars dead-end stops the pre-flight from parking behind it (9e5a31e)

    whyWhen the launch vars make every outgoing guard of a node false, the run dies at that node - but a cap on a route behind it still parked the run and armed a retry for work that cannot happen, and the compiled bounded-iteration fallback (C012) closed a cycle whose prune dropped the whole walk, misparking under the hard cap too. Both walks now share one notion of a node that cannot be left under the launch vars: CanReachVia does not walk through it (a spent bounded iteration is no crossing) and…
  • runtime,server,runview: an undecided effect carries its code on the four remaining routes (b679b63)

    whyThree routes still re-drove a run or masked an undecided effect while the first command ran, and the fourth (the engine's other sandbox execs) was inventoried. The fan-out's cancels and abandoned branches keep AMBIGUOUS_EFFECT through wrapContextErr (the drain preserves a recorded verdict instead of overwriting it with INTERRUPTED); a subbot child read back from its record keeps its FailureCode (the recovery refuses to retry the node and the reattachment keeps the binding); the cloud operator…
  • runtime,server,runview: an undecided effect carries its code on the four remaining routes (e61b84d)

    whyThree routes still re-drove a run or masked an undecided effect while the first command ran, and the fourth (the engine's other sandbox execs) was inventoried. The fan-out's cancels and abandoned branches keep AMBIGUOUS_EFFECT through wrapContextErr (the drain preserves a recorded verdict instead of overwriting it with INTERRUPTED); a subbot child read back from its record keeps its FailureCode (the recovery refuses to retry the node and the reattachment keeps the binding); the cloud operator…
  • server,bots: bind consumed reviews to their producer (adopts #2589) (bb0c926)

    whyThe merge-gate hand-off walked this PR's runs newest-first and returned the first producer that rendered, so any later review-producing bot fed the fixer instead of the gate's own reviewer. Adopts the parked PR: consumes.from optionally pins the eligible producer by bot id, Billy binds to review-pr with no cross-bot fallback, and the manifests declare the binding. The old producer-agnostic tests die with the fallback they asserted; the declarations test carries the multi-tier and ordering cases.
  • server,bots: land the consumed-review producer binding the phantom squash claimed (e361f77), references #2589

    whyThe squash efc89e9ea merged an empty diff: the adoption content was lost in a rebase-amend shuffle while its commit message travelled. This lands the real binding - consumes.from optionally pins the eligible producer by bot id, Billy binds to review-pr with no cross-bot fallback, the old producer-agnostic tests die with the fallback they asserted.
  • server: isolate corrupt identity rows and sanitize 5xx responses (bba1b10), references #2624 #2625 #2634

    whyDecode identity bulk reads per document and render corrupt organizations as unavailable while keeping healthy memberships usable in the Studio and CLI. Refuse quota-update acknowledgments until every enforced counter is updated.
  • studio: a disabled dropzone answers no key — the picker never opens (RGAA 7.1) (0551c46)

    why: clickableRowProps gains an optional { disabled }: the key handler refuses, tabIndex leaves the control out of the tab order, and the click target clears. The two dropzones pass their existing state (AttachmentFieldInput's disabled, GateFileInput's busy = disabled || uploading) — the mouse was already blocked by pointer-events-none; the keyboard now matches. The signature is additive: the other twelve call sites keep their behaviour.

  • studio: a11y chokepoints - named human armings, icon roles, live regions, separators, media alternatives (10f4872)

    whyThe a11y-chokepoints stream's eight tickets: shared primitives (clickableRowProps with keyboard handlers, LiveDot with role=status, the Radix icon wrapper's role=img, separators with role=separator, media alternatives with captions and fallback text) and their application across Runs, Canvas, Board, Library, Marketplace, Dispatcher, integrations and shared components. The OrgSwitcher team row's JSX closed a div over a span (repaired - tsc clean).
  • studio: a11y chokepoints - named human armings, icon roles, live regions, separators, media alternatives (5eeff73)

    whyThe a11y-chokepoints stream's eight tickets: shared primitives (clickableRowProps with keyboard handlers, LiveDot with role=status, the Radix icon wrapper's role=img, separators with role=separator, media alternatives with captions and fallback text) and their application across Runs, Canvas, Board, Library, Marketplace, Dispatcher, integrations and shared components. The OrgSwitcher team row's JSX closed a div over a span (repaired - tsc clean).
  • studio: closing the assistant dock hands focus back to its opener (RGAA 12.8) (b2b0c00)

    whyThe floating panel moved focus in on open but only cleared its timer on unmount - the opener's focus fell to <body> and the tab order restarted from the top of the page. The mount effect now remembers document.activeElement before deferring focus and restores it in the cleanup, the CommandPalette idiom; the restore-mount path (focusOnMount off) still touches nothing.
  • studio: each run group owns its tbody (RGAA 5.7) (#2509) (c32bc19)

    whyRunListView wrapped ALL groups in one <tbody>, so the rowgroup headers (scope=rowgroup) spanned nothing: assistive tech heard one undivided body with cosmetic dividers. The tbody ownership moves into RunRowGroup - each group's rows live in their own tbody under their rowgroup header, and the suppressed-header group (the "none" key) still owns its body.
  • studio: heal the a11y landings' studio debt - names, splitter jumps, types (c124472)

    why10f48724a landed the a11y chokepoints with a red studio suite and a red typecheck; this heals the class at each chokepoint:
  • studio: phantom colour classes resolve to real theme tokens (#2640) (4bbbcfe)

    whyThe denylist canary only sees named shapes: text-success-text / text-danger-text (two statusTone copies), text-muted-fg, bg-subtle, bg-surface-sunken, hover:bg-surface-hover, hover:text-info-hover, bg-surface — classes Tailwind generates no rule for, whose elements silently inherit their colour. broken-classes.test.ts gains the allowlist: every colour utility in the corpus must resolve to a --color token of app.css's @theme (the source the build compiles utilities from), with the non-colour…
  • studio: question option groups carry their legend (RGAA 11.5/11.6) (#2528 #2536) (7349d12)

    whyRadioInput and CheckboxInput rendered their option rows in a bare div: assistive tech heard a list of ungrouped radios with no group name. Both wrap in a fieldset whose sr-only legend is the question's label - the visible label stays beside the group in WizardForm, so the layout is unchanged and the accessible name is real text, not a reference to a distant id. One edit serves both criteria (11.5 the group, 11.6 its legend); #2536 closes as its duplicate.
  • studio: required fields say so — visible mention and aria-required (RGAA 11.10) (#2530) (3bfe7a3)

    whyFieldRow is the label+control chokepoint: a required field now carries the visible asterisk on its label, an sr-only (required) mention so the accessible name states it, and aria-required on the control through the render-prop. CommittedTextField plumbs it; the Workflow Settings form's two name fields (workflow name, resource name) mark required — they were required-by-validation only, silent to assistive tech. WizardForm's decorative asterisks each gain their sr-only mention beside them.
  • studio: text-fg-subtle never inherits onto a -soft tint (RGAA 3.2) (#2639) (82aa5cd)

    whyThe inherited-composition case was invisible to the variant canary: the tint sits on a banner, the subtle colour on a child span, and no single className ever holds both. _jsx.ts grows classedWithAncestry - the JSX ancestry walk (intrinsic chain only, component boundaries cut) - and the extended canary judges the composition. 61 sites across 29 files swap to text-fg-muted (the design rule's prescription; the interactive quiet controls keep their hover restore).
  • studio: the board column glyphs carry text alternatives (RGAA 13.5) (#2534) (b4fe8ec)

    whyThe header's bare ● and ✓ marked dispatch eligibility and terminality to sighted users alone - a screen reader heard nothing between the count and the menu. Each glyph is aria-hidden with an sr-only text alternative (the LiveDot doctrine: glyph decorative, meaning textual).
  • studio: the contextual header bar is the page's banner landmark (RGAA 9.2) (#2518) (8d5d79b)

    whyThe bar above <main> was a generic div, and in local identity it rendered nothing at all: the authenticated shell had ZERO banner landmark on every page. The bar is now the <header> landmark, always rendered - when the local-identity chip self-hides and no page content is pushed it collapses to zero height (h-0, no border) so the layout does not shift while the landmark stays.
  • studio: the DiagnosticBadge pulse drops its onAccent label under 3:1 at the trough (RGAA 3.2) (180cf1e), references #2535

    whyanimate-pulse animates opacity; at the trough the white-on-danger count text loses its contrast headroom. Both severities stay static, and a source-discipline canary bans the class (opacity-animating utilities) on any onAccent text element so the pairing cannot drift back. Softer text pairs pass at the trough; their remaining motion is governed by the reduced-motion gate (#2535).
  • studio: the primary navigation groups are real lists (RGAA 9.3) (#2519) (1db37ed)

    whyNavLinks rendered each group as a div of bare rows: assistive tech heard a flat stream of buttons with no list structure and no group names. Each group is now a ul labelled by its caption (aria-labelledby expanded, aria-label on the collapsed icon rail), every row is a li - including the editor/runs rows whose nested tab list stays inside.
  • studio: the resource remove button names itself properly (RGAA 11.1) (#2529) (66a5991)

    whyThe glyph-only remove button leaned on title for its accessible name — hover-only, announced inconsistently. It goes through the IconButton chokepoint, whose required label renders the aria-label (the visible ✕ stays). Verified the studio-wide sweep found no sibling raw glyph-only button.
  • studio: the run-detail title is the page h1 and the markdown slots one level under it (RGAA 9.1) (#2517) (721af27)

    whyTwo families in one ticket: RunHeader rendered the run's friendly name in a plain div (no heading on the run-detail page at all), and MarkdownText shifted every markdown heading down TWO levels (h1 -> h3), so a document hierarchy starting at h1 landed at h5 under a page that had no h1 to justify the drop. The title wrapper is now the page's h1 (the double-click rename affordance unchanged), and the markdown headings slot one level below it (h1 -> h2 ...).
  • studio: the runs table caption summarizes columns and grouping (RGAA 5.1) (#2508) (3825134)

    whyThe complex table's caption read "Runs" - one word for an eight-column table grouped by state. The caption now names the grouping and lists the columns, the summary assistive tech reads before the cells.
  • studio: the source-level naming guard for interactive elements (leg 2 of #2591) (b014cc4)

    whyThe net's 11.1 hole: axe 4.13 passes placeholder-only inputs outright, and the source scan is the only place the class is decidable. _interactive.ts walks every intrinsic interactive element and flags the ones with NO naming mechanism — static text, an expression child (the runtime-name case, covered by the colocated role-name tests), aria-label, aria-labelledby, title, an enclosing label, or the FieldRow htmlFor/id pair sharing one expression. input[type=hidden] and the display:none utility…
  • studio: the speaker chips reach assistive tech (RGAA 10.8) (#2525) (bb3ab72)

    whyThe persona chip and the You chip hid themselves with aria-hidden while carrying real information — who is speaking. Multi-bot docks make that text load-bearing (a Copi reply must not read as Nexie's). Both chips drop the attribute; they stay visible text with no other change.
  • webhooks,server: the delivery is acknowledged once claimed, and a launch_error delivery re-drives (e566e93)

    whyEvery webhook lane ran its launch on the request context, so a forge closing the delivery at its own clock cancelled the launch midway and the launch_error delivery sat there forever - no retry, no sweep, and the forge will not redeliver a closed connection. The lanes now acknowledge the delivery once claimed (202 + the delivery id) and run the launch on a context detached from the request, bounded and shutdown-aware like launchBookingContext. A launch_error delivery is re-driven by a…

3.286.3 (2026-10-24) ​

Bug Fixes ​

  • bind consumed reviews to their producer (#2589) (efc89e9), references #2592

    whyAdversarial-Rounds: 7 local (announced 6, ceiling 20) Adversarial-Model: kimi-code
  • delegate,secretguard,bots: undeclared ITERION literals pass tools byte-identical, and a placeholder-shaped review never gates (c9fc1c9)

    whyResolveSecretRef built a placeholder from any name it received: an unknown or normalized alias could resolve against a declared secret, and RedactOnly values leaked into that table. Resolution now consults the exact declared-name table; unknown names refuse. The witnesses run the real rewrite chain and pin the invariant - an undeclared ITERION literal survives a tool command byte for byte, and reintroducing a per-occurrence shape match reddens them.

3.286.2 (2026-10-24) ​

Bug Fixes ​

  • preserve the deepsec export schema (#2592) (bd9f649)

    whyAdversarial-Rounds: 7 local (announced 6, ceiling 20) Adversarial-Model: kimi-code

3.286.1 (2026-10-24) ​

Bug Fixes ​

  • proc,runview,runtime: the fixed install-path tier is injectable, and the sandbox resolver delegates to it (e40def0)

    whyTestLaunchDetached_NamesTheDroppedOverrides isolated PATH, but the production resolver reads the fixed install paths before PATH: on a host with ~/.local/bin/iterion the witness spawned the operator's real binary (proctest forgave the settling child) instead of refusing. The fixed tier moves behind proc.SetInstallPathCandidates — a test seam the witness holds empty — with ITERION_BIN and HOME pinned beside it. The duplicated locateHostIterionBinary in pkg/runtime now delegates to…

3.286.0 (2026-10-23) ​

Features ​

  • instrument: the metrics family — a /metrics surface, wired and scraped (9174dd9)

    whyThe family skill states the definition of done (a /metrics endpoint on the production surface, HTTP request count and latency by route template and status CLASS, process metrics, scrape wiring where the repo carries manifests, an env toggle with the DSN's strict opt-in semantics) with the cardinality budget that keeps the surface answerable. The three stack skills fold the recipes in: prometheus_client + the FastAPI instrumentator (instrument(app), latency with status), prom-client with real…
  • server,chart: the maintenance fence, the contract probes, and the values schema (c94fac3)

    whyExpose compiled queue/store contracts offline. Gate new admissions through the platform maintenance record while preserving existing-run resumes. Surface the dated refusal in the API and Studio, and validate/render-diff Helm values.

Bug Fixes ​

  • auth,server,studio,desktop: the identity tree is read, never trusted, and credentials derive only at the chokepoint (#2715) (60a5add), closes #1668

    whyThe org tree is read from the store, never rebuilt from per-document token decodes: /api/auth/me is the sole builder of the identity view, and every consumer reads that one tree (#2624's class).
  • bots: the shipped bots declare what they use, and the renovacy ledger exit carries its guard (f6a821d)

    whytool_search — the schema loader the author's own eight comment lines argue for ("Allowed, it can report tool availability honestly") — and nothing else: the bounded evidence surface the e2e contract pins for reflect stays bounded. The todo_write/task allow rules with no written argument are REMOVED, not declared... with one exception the contract itself forced: copilot's shared allow block keeps TodoWrite verbatim (the Copi permission contract pins it — the policy bounds what may RUN regardless…
  • bots: the shipped bots declare what they use, and the renovacy ledger exit carries its guard (357ba53)

    why#1653: the inert allow rules die by declaration, per the author's written argument — copilot's three reviewers gain tool_search (the schema loader that lets a reviewer report tool availability honestly: the eight comment lines the author wrote), nexie gains skill (its skills come back through the loading path), and the todo_write/task rules with no written argument are REMOVED instead of declared — the bounded evidence surface the e2e contract pins for reflect stays bounded, and the Copi…
  • ci,build: pkg/server joins the mongo-conformance job, and gen:check stops mutating (365083f)

    whyThe train's round-4 gate validated both edits on this tree; they were left unstaged by the amend ordering and follow now:
  • expose repo bot preview and updates (db51c1e)

    whyAdversarial-Rounds: 3 local (announced 6, ceiling 20) Adversarial-Model: kimi-code
  • release: the cut re-pins its own corpus edits in the release commit (dcbc495), closes #2607

    whyinternal/mdcode's committed-reading golden pins every tracked .md, .yaml and .yml; the release cut edits CHANGELOG.md and the chart and is pushed straight to main, so every cut left main red on the corpus witness until a hand regen merged (v3.273.0 through v3.276.0: four times in 30 hours).
  • repomap: the docs walk reads the index, and a deleted page no longer aborts it (a9229e7)

    whytask gen / gen:check: one aggregate generates what the repo commits (maps, OpenAPI, the dispatcher catalogue — deliberately NOT the reading golden, whose rewrite is review-only at the cut), the other asserts freshness, and check runs the checker before anything can regenerate over it.
  • server,trigger,cloudsched: the admission before the cancellation, and the atomic consumption receipt (0ed3c18)

    whyThe rva round 3 caught the last three holes in the hold semantics:
  • server,trigger: the fence defers instead of abandoning (87abda9)

    whyThe rva on the fence implementation caught the design flaw the tests missed: the maintenance refusal flowed into the retry consumers, so a pause during maintenance DESTROYED pending work — the trigger effects worker consumed one attempt per refusal (five refusals retired the effect for good), the board dispatch ended blocked, and the deferred webhook was deleted after exhaustion despite its 202. The gate held; the work did not survive the pause it was gated for.
  • trigger,cloudsched,server: the schedule exclusion spans the overlap read and the run persistence (710af81)

    whyThe cloudsched ticker excludes replicas per schedule (a renewable lease) from the overlap check through launch persistence: the publisher commits the run under a same-transaction ownership check on the lease, so an expired worker cannot persist behind its successor's overlap read. The local scheduler takes a file lock across journal reload, overlap, launch and outcome persistence, and no longer replays cached journal state.
  • trigger,cloudsched: the disarm batch respects the overlap policy, and the replacement credit waits for the transfer (e05cd10)

    whyThe rva round 4 caught the last two edges of the hold semantics: the disarm batch replayed every held intent straight past the overlap gate (a stack of held slots could all launch at disarm, defeating overlap:skip and keepalive), and the webhook replacement credited the predecessor's slot BEFORE the cancellation — a failed cancellation left two active runs against a ceiling of one.

3.285.1 (2026-10-23) ​

Bug Fixes ​

  • dsl,server,studio: the DSL reader refuses early and every bot-source write carries what a whole read issued (#2712) (b49fbed), closes #1690, references #2678

    whyThe reader of a bot no longer loads what it could not read whole. The budget bounds one bot's files together at 2^20 records — every word, value, punctuation mark, prompt line and line end is one, a comment two (commentRecords) — moved with ITERION_DSL_READ_BUDGET (classified infra in platformcfg), 0 removing the bound; the lexer spends it as it reads, so a bot past it is refused before the work it asks for, and the files of one bot share it (NewUnitBudget), so splitting a bot into fragments…

3.285.0 (2026-10-23) ​

Features ​

  • secrets,runner,cloudpublisher: the runner serves a run-held gateway credential and refuses ambient credentials on a stamped run (#2709) (abc3f71), references #2005 #2652

    whyPR-B'1 of #2005 (ADR-131) — the consumer half of the carriage, "consumer first, inert at v25": every change is a no-op on the bundles sealed today, and the queue schema is untouched (the bump to 27 ships with the producer, PR-C').

Bug Fixes ​

  • model: the claw recovery pass retries once at a lowered reasoning effort (#2716) (7c25d46), references SocialGouv/iterion#2710

    whyA long tool loop's structured recovery on a gateway model can come back empty at a high reasoning effort — four identical deaths on the vao pool (SocialGouv/iterion#2710), ~$5 of diff reading lost per death, the whole review restarting from scratch on every node recovery. The recovery pass now runs ONCE MORE at the lowered effort (the existing effortAfterCut lever, which refuses an already-lowered recovery) before the node fails: one billed call against a full restart. The first recovery's…

3.284.1 (2026-10-22) ​

Bug Fixes ​

  • bots: the claude reviewer slots declare their tool surface — on claw an empty list is no tools at all (#2706) (d74fda3), closes #2705, references SocialGouv/iterion#2705

    whyreview-pr's claude reviewer slots (reviewer_claude, reviewer_claude_glance) shipped with no `tools:` list. On claude_code that reads as "no restriction" and the CLI's native roster applies; on claw the list IS the whole surface, so the slots dispatched with tool_count 0 — the model could not run one git command and answered the output schema with a stub whose summary said "placeholder", which the pipeline merged, published ("Revi — aucun problème détecté") and used to green `revi/review` on the…
  • runtime: the unit of a usage park is the provider wall (#2708) (8143922), closes #2451 #2450, references #2451

    whyEvery reader of a usage park keyed on its own heuristic: the owner heard "Run failed" once per park (~25 pushes a day on a weekly wall), a probe's free re-park pushed too, a single wait could spend max_attempts x budget probe pods (120 at the defaults against 24 by design), and the pull-request notice gate could not tell the wall it was told about from a new one. The operator's decision (#2451, option A): the ANNOUNCED INSTANT is the unit — it is persisted on the retry row (`UsageParkWall`, fs…

3.284.0 (2026-10-22) ​

Features ​

  • server,forge: the Studio's board writes post as the repository's minted bot (#2704) (1c4f5a2), closes #2681, references #2630

    whyOn a GitLab connection that mints per-repository project bots, the review lane posted as the minted bot (#2630) while the board lane — a card pushed to a forge issue, a pull request opened or merged from a card — still posted as the person whose OAuth grant bootstrapped the connection; on a GitHub App connection the same gesture posted as the App. One Studio gesture, two identities.

3.283.0 (2026-10-22) ​

Features ​

  • server,lease,cli: the watch-health answer names the lease holder from any replica (#2695) (8fb71c3), closes #2680

    whyThe assistant sweep runs on one replica — the holder of the assistant-watch-sweep lease — and the watch-health route answered for whichever replica the load balancer hit: nine answers in ten said elected=false and nothing said who sweeps.
  • studio: a run's deliverables — rendered markdown at a shareable address, listed in the run summary, reachable in the Artifacts drawer (#2700) (8db90b1), references #2690 #2690

    whyThe Artifacts drawer's Files section sat under a `flex-1 min-h-0` that collapsed to zero below a tall list of published outputs: the operator saw "6 files" and no Download button. The section now keeps its natural height and the panel scrolls as a whole.

Bug Fixes ​

  • runner,chart: a sovereign pool's env dials are pinned ahead of the platform bot-vars record (#2702) (941aa79), closes #2697

    whyA pool pod's own env block names its gateway on every routing dial (ITERION_VIBE_MODEL_*, ITERION_VIBE_PROVIDER_*, the *_FALLBACK rescue dial). The platform bot-vars record is installed by the runner with precedence setting > pod env > .bot default, so a model id stored for the shared fleet's vendor reached every pool pod: route 1 carried the record's vendor id under the pool's gateway hint, the authored vendor rescue route carried the pool's gateway id under its vendor hint,…

3.282.0 (2026-10-22) ​

Features ​

  • dsl,model,dryrun,queue: a tool node declares what the engine expands in its bodies (#2694) (30dcd15), references #1522

    whyThe engine rewrites the braced environment forms of every tool body before the body reaches its reader, and an author had no way to decline. A `language: js` body whose `${count}` belongs to the language, a `${NAME:-default}` a shell is meant to resolve itself, a published `.bot` a third party must be able to read as what it executes: all three were silently rewritten, and the C188 lint had no remedy to point at.

3.281.3 (2026-10-22) ​

Bug Fixes ​

  • bots,runner: a mono-GPT review merges on the GPT family, and a supervisor watching only excluded nodes is not a route (#2684) (162a36f), closes #2467, references #1828 #1828

    why`mono_family` is resolved from the run's credentials, and claude is the preferred family (`reviewtopology.monoPreference`), so a launch resolves `gpt` only where no Claude credential serves. review-pr nonetheless sent such a run to `converge` and to the `pacer` supervisor, both pinned to the Anthropic wire, AFTER the GPT reviewer had already spent: the merge died, the run never reached `pr_gate`, and the `revi/review` gate stayed dark on a GPT-only tenant until someone set three env vars plus…
  • forge,server: the token refresh sweep runs on one elected replica, and a lost refresh race writes nothing (#2686) (273d077), closes #2682, references #2685

    whyA GitLab OAuth connection (PIC, team VAO) flipped to needs_reauth at 07:57:40Z while its row kept the previous expiry: three server pods had run their refresh sweep within five seconds — the ReplicaSet had just been rolled, so every replica's ticker was phase-aligned. GitLab rotates refresh tokens: whoever refreshes first consumes the token, the next replica's refresh is answered invalid_grant (ErrUnauthorized here), markRevoked flagged the connection and persisted the loser's stale copy over…

3.281.2 (2026-10-22) ​

Bug Fixes ​

  • claw: structured output through OpenAI-compatible gateways survives hidden reasoning (#2664) (298db1f), closes #2651

    whyA claw node with tools and an output schema on openai_compatible/scaleway/glm-5.x lost its lot on every measured failure (glm-5.3 3/3, glm-5.2 4/5 then 5/11): the tool loop's final turn and the schema-forced recovery pass both came back with output_tokens equal to max_tokens (claw's 8192 default, the node sets none) and empty content. The budget went to hidden reasoning that claw's chat-completions translator drops (reasoning_content), the gateway's finish_reason "length" reached iterion as a…
  • compare serialized ASTs for author fragments (#2572) (6ecb1f0)

    whyAdversarial-Rounds: 2 local (announced 3, ceiling 5) Adversarial-Model: kimi-code
  • enforce taxonomy release floors (#2573) (2229417)

    whyAdversarial-Rounds: 3 local (announced 8, ceiling 50) Adversarial-Model: kimi-code
  • studio: severity, text and control tokens hold their WCAG bars, and the guards read the JSX (#2638) (57e7a16), closes #2506 #2505, references #a6adb8 #667080 #a5b4fc #2527

    whyRGAA 3.2 / 3.3 in the studio: the token layer, the sites that bypassed it, and guards that read what ships instead of a regex over one JSX shape.

3.281.1 (2026-10-22) ​

Bug Fixes ​

  • model,dryrun: read a tool body's environment once, and preview what runs (#2672) (579c3f8), references #1607 #1522

    whyA tool body's `${NAME}` and `${NAME:-default}` forms were read by three executor sites, each calling `expandBracedEnv` on the engine's own environment — and by nothing at all in `pkg/dryrun`, which rendered the AUTHORED text and held THAT to `bash -n`. So `validate --exec` and its syntax verdict judged text the engine will not hand the shell, and a production deep-scan pass whose run-id guard reads a braced default has been rejected since 16/09 for want of this reading.
  • studio: keyboard focus is visible on every focusable element — a ≥3:1 focus-ring token, xyflow and virtuoso counter-rules (#2622) (e3d8391), closes #2524, references #2524 #2506 #2505 #4f46e5

    whyRGAA 10.7 / WCAG 2.4.7 (#2524): the audit's rendered probe found 28 occurrences where a Tab press produced no visible change. One class, three causes — the indicator was too faint, or reset by a library stylesheet, or reset by a library inline style:

3.281.0 (2026-10-22) ​

Features ​

  • secrets,server,cli,studio: a tenant's OpenAI-compatible gateway is a credential record, and a key can be restricted to repositories (#2671) (05caafa), references #2005 #2652 #2005

    whyA team that must spend its own LLM gateway (a PLIAGE LiteLLM endpoint in front of Scaleway models) had one option: a dedicated runner pool carrying the key in its pod environment, one pool and one forked bot per team. This is PR-A of #2005 (ADR-131): the RECORD half. Provider openai_compatible joins the BYOK vocabulary; its key carries base_url, the gateway root, canonical at write (https only, host lower-cased and in punycode, literal addresses as net.IP prints them, no default port, no…

3.280.0 (2026-10-22) ​

Features ​

  • server,cloudpublisher: the board and ticket launch lanes resolve the run's routing policy (#2667) (b3c4a2d), references #2000 #2006 #2000 #2005

    whyThe cloud board dispatcher (processBoardCard) and the pipeline admission lane (launchTicketNow — the Ready sweep and the operator's "launch now") were the only two of the six pkg/server launch lanes that resolved NO routing policy and no retry contract: a card- or ticket-launched run carried none of the tenant's override cells, none of its `providers:` allowlist and none of the bot's retry directive. Each lane now calls resolveRunLLMRoutePolicy and its retry twin on the team the launch is FOR…
  • warn on unchecked root vars subscripts (#2570) (c7b1774)

    whyAdversarial-Rounds: 2 local (announced 3, ceiling 5) Adversarial-Model: kimi-code

Bug Fixes ​

  • forge: the project-bot mint and rotation always name an expiry GitLab accepts (#2663) (00b2765), closes #2653

    whyThe first live enable through an OAuth project-bot connection (PIC GitLab v19.4.2-ee) was refused: `POST /projects/:id/access_tokens` answered 400 "expires_at is missing". The mint omitted `expires_at` so that GitLab would apply its default maximum, but ResourceAccessTokens::CreateService refuses a request without the key before any default applies — on every 19.x with the allow_resource_access_token_creation_without_expiry_date flag off, and on any instance enforcing token expiry — and the…
  • runtime,ir,model,dryrun: a node on auto reads the route default_backend names, pre-run as at dispatch (#2658) (23a3751), closes #2428, references #1727 #2426

    whyBoth pre-run readings of a node's tool surface — the catalogue's prompt-injection boundary (`ToolSurfaceWriteReason`) and shared-worktree admission (`isMutatingNodeCtx`) — kept their own copy of dispatch's step 1 and substituted the workflow's `default_backend:` only where the node's `backend:` expanded to NOTHING. A node spelling `backend: auto` was therefore read as a route left to the host, while `model.ClawExecutor.resolveBackendName` had already routed it through the workflow default: the…
  • sandbox: a sandboxed tool node proves its end instead of trusting the exec client (#2659) (709d588)

    whyIn a sandbox, a tool node's verdict was the local exec client's exit status (kubectl exec, docker exec). client-go reads an error stream that closed without a status as success, so a stream lost behind the client ends it with status 0 and whatever output had arrived: the node succeeded with {result: ""} while its command kept running in the container. Measured on a long gate node: it finished 52 minutes into a 2-3.5 h script with an empty result and no error, and the script went on applying…
  • server,store: the sweeps ask the store for the watchers, one replica sweeps, the hand-off asks for the PR's runs (#2654) (b3e8694), closes #2636

    whyMeasured on ovh-prod (21/10): 10 server pods at ~1 CPU each with the HPA pinned at its maximum, the Mongo primary at 6 CPU and 487 MB/s out, for zero runs watching a card. Store.ListRuns is a collection scan with an in-memory sort (filter on deleted_at, sort on created_at, no index), and two periodic sweeps ran it on every replica then loaded every run whole: the assistant run-watch sweep every 20 s, the orphan reconcile every 60 s (which, on the lock-less cloud store, dropped each run it…

3.279.1 (2026-10-22) ​

Bug Fixes ​

  • reconcile launch admission with resolved bots (#2554) (fd96290), references pre-#2613

    whyAdversarial-Rounds: 5 local (announced 3, ceiling 5) Adversarial-Model: kimi-code
  • retry private ultra11y npm installs (#2548) (bed5b66)

    whyAdversarial-Rounds: 2 local (announced 3, ceiling 5) Adversarial-Model: kimi-code

3.279.0 (2026-10-21) ​

Features ​

  • dsl,bots: C188 — a braced env read the engine answers before the shell (slice 1 of #1522) (#2656) (9808cd7)

    whyThe record for #1522, landed verbatim from the reviewed design (e2-expand-env-hatch at 5c9a8408f): the env_expansion property, the C188 lint where the engine's answering defeats an assignment, and the validate --exec truth duty. The docs map row is regenerated (task map:gen).

Bug Fixes ​

  • complete create-only authoring transactions (#2569) (1cd0d12)

    whyAdversarial-Rounds: 1 local (announced 3, ceiling 5) Adversarial-Model: kimi-code
  • keep long organization menus reachable (#2560) (a44e02a)

    whyAdversarial-Rounds: 1 local (announced 3, ceiling 5) Adversarial-Model: kimi-code
  • secrets,server: api-key updates patch the named fields and refuse rows from a newer build (#2652) (a02086c), references #2005 #2005

    whyAn api-key update replaced the whole Mongo document with the handler's copy of the row. Two things that copy cannot hold were lost on every PATCH: the columns this server build does not know (what a newer server writes on the same row during a rolling update, or leaves behind after a rollback — the gateway credential of #2005 adds the first such column), and the current value of the fields the request did not name (an is_default a concurrent ClearDefault had just lowered, the last_used_at the…
  • use a valid artifact node for cloud preflight (#2559) (f9eeaa8)

    whyAdversarial-Rounds: 1 local (announced 3, ceiling 5) Adversarial-Model: kimi-code

3.278.0 (2026-10-21) ​

Features ​

  • forge,server,studio: a GitLab OAuth bootstrap mints a project bot per repository (#2649) (36b397e), closes #2630

    whyConnecting a GitLab repository took three manual steps where GitHub takes one, because an OAuth grant acts as the person who authorised it: a connection made with it posts as that person and, through the bot-author guard, stops auto-reviewing that person's own merge requests. This makes OAuth the bootstrap and a project access token minted by iterion the durable bot identity.

Bug Fixes ​

  • preserve spend on oversized claw results (#2601) (8a33f6c)

    whyAdversarial-Rounds: 3 local (announced 3, ceiling 5) Adversarial-Model: kimi-code

3.277.3 (2026-10-21) ​

Bug Fixes ​

  • scope sandbox runner homes by run branch (#2599) (047859f)

    whyAdversarial-Rounds: 3 local (announced 3, ceiling 5) Adversarial-Model: kimi-code

3.277.2 (2026-10-21) ​

Bug Fixes ​

  • reset undo history after replacing a document (#2611) (576abfe)

    whyAdversarial-Rounds: 1 local (announced 3, ceiling 5) Adversarial-Model: kimi-code

3.277.1 (2026-10-21) ​

Bug Fixes ​

  • report missing GitLab review bodies (#2595) (b017fb7)

    whyAdversarial-Rounds: 2 local (announced 6, ceiling 20) Adversarial-Model: kimi-code
  • root Claw file tools at the run workspace (#2597) (8ab5777)

    whyAdversarial-Rounds: 1 local (announced 6, ceiling 20) Adversarial-Model: kimi-code

3.277.0 (2026-10-21) ​

Features ​

  • forge,cloudsched,server,studio: the integration record carries the routing policy, and every projection reads it (#2615) (92faa49), references #2006 #2006 #2607

    whySlice 3 of 6 (#2006, ADR-129): `RepoIntegration.Routing` lands beside LaunchVars and reaches every projection — webhook config, integration record, scheduled rows (with a RoutingFromIntegration provenance flag so an operator's row-level block and the integration's projection stay distinct), the park echo, the org-approval apply path, the credential preview's trusted-var narrowing, and a repo-scoped studio editor that renders parked saves and blast-radius warnings.
  • llmroute,model,cloudpublisher,runner: the tenant's provider allowlist holds at the gate and at dispatch (#2605) (1d41c59), references #2006 #2006

    whySlices 1-2 of the revi routing arc (#2006) stored the tenant's `providers:` allowlist and folded it into the launch view, but nothing enforced it: a launch still resolved rescue routes, sealed credentials and armed dispatch chains for providers the tenant forbids. This change makes a non-empty list an enforcement question at the walk (ADR-129 §5).

Bug Fixes ​

  • studio: drafts track their provenance, reloads reload the right file (#2614) (c1bbbda), closes #1790 #1791

    whyDraft tabs now record which document they were opened from via a WeakMap on the DocumentStore: a reload of the underlying file reloads the drafts that actually came from it, not every open draft. The Source buffer is only discarded once the install was accepted, and the reload banner offers "Load it" through the confirm hook. Manual loads go through replaceDocument with race guards so a stale read cannot clobber a newer buffer.

3.276.0 (2026-10-20) ​

Features ​

  • backend: claw nodes read the repository's AGENTS.md (#2616) (1821f43), closes #2078

    whyA claw node received no project instruction file while claude_code, codex and pi all read one — the parity doctrine asks for a capability to be wired or typed-refused, and the operator arbitrated for wiring it: claw now receives the repository's instruction root the way claude code does.

3.275.0 (2026-10-20) ​

Features ​

  • task: the demo task ships its token ceiling — RGAA_DEMO_TOKENS, 5M (#2593) (#2612) (52d7c1f)

    whyThe first real end-to-end run hit the bot's 3M TOKEN ceiling at 91% (2 729 032), in merge_refutations — a full run's wall is the token budget, not dollars. The task now ships --max-tokens 5M (RGAA_DEMO_TOKENS overrides), the colleague doc carries the resume line that finished the run for real, and the bot bilan records the path: egress-killed sandbox run (0 $), sandbox-off run, resume, FINISHED at 42% provisional with report integrity ok.

Bug Fixes ​

  • server,botregistry,cli: a launch's bot identity is the server's resolution, never the requester's label (#2613) (08a07d3), closes #1368, references #1363 #2607

    whyhandleLaunchRun seeded botID from strings.TrimSpace(req.BotID) and kept that value whenever the launch carried an inline source, so the requester-typed string — matching no catalog entry — flowed into spec.BotID and from there into three credential reads that treated it as an identity: the BYOK workload audience (secrets.Resolve), bot secret bindings (secrets.ResolveGenericWithBindings) and credpool pledges (credpool.Request.BotID → Pledge.AvailableForLaunch). A caller able to POST /api/runs…

3.274.0 (2026-10-20) ​

Features ​

  • dsl,cli: the YAML remedies key on the reader's structured cause, not its prose (#2604) (f98eefb), closes #2404, references #1783

    whyThe YAML remedies answered a reader's refusal by matching its message text. Nine adversarial rounds on #1783's extension found nothing but regressions of that design: one string answered four cause families (`cannot unmarshal` told apart only by the Go type the reading decodes into), a line count the quoted-span strip falsified, a clause scoped to the wrong key, a delimiter-paired span an author's backtick splits — each fix a second structural patch over the same text-matching seam.
  • task: one command produces the RGAA report — task demo:rgaa-audit (#2593) (#2598) (109c2b8), references #2587

    whyThe colleague-facing demo path (#2593): task demo:rgaa-audit builds the studio and the binary, resolves the pin-verified engine, installs the chromium the rendered scan needs, and runs rgaa-audit over this very studio — report FR, page grid, annex and ledger under .rgaa-demo/. The committed ledger keeps unchanged criteria from being re-adjudicated (~$20-30 a run; RGAA_DEMO_BUDGET overrides the ceiling).

Bug Fixes ​

  • studio: name every form control the audit left unnamed (RGAA 11.1) (#2556) (#2590) (b92c27b)

    whyPlaceholder-only search fields, bare TagInput chips, an sr-only file input, an inline rename input, and the wizard's select/free-text questions all rendered controls whose accessible name was empty — a placeholder is not a label (RGAA 11.1, ticket #2556).

3.273.0 (2026-10-20) ​

Features ​

  • mdcode: fence detection reads CommonMark's block phase (#2587) (4d76ba7), closes #2395, references #1723

    whyThe catalogue-fence reading behind FenceScanner was a line-by-line approximation with a stated limit: 123 of the 14 580 adversarial field shapes of the #1723 review still leaked past it through the real generator — every one carrying a fence delimiter four or more columns in, or held by a list marker the line-by-line reading cannot see as a container — and the source gate refused none of them, so guarantee (3), the gate refuses every shape the generator cannot repair, was false. Three candidate…

3.272.0 (2026-10-19) ​

Features ​

  • llmroute,runview,model: the routing policy carries typed overrides and a provider allowlist, folded once at launch (#2586) (36f7b66), references #2006

    whyThe routing policy could only tune classes of behaviour (model classes, provider triggers); an integration could not pin a specific routing cell, and nothing shared one resolved answer between the launch, the credential preview and the runner. This slice adds llmroute.Policy.Overrides — entry-wise per (selector, field) cells carried with overrides.<selector>.<field> provenance plus the block key — and .Providers, a first-setter-wins, never-merged, lockable allowlist, with their full validation:…

3.271.1 (2026-10-19) ​

Bug Fixes ​

  • rgaa-audit: the annex and GRID_INCOMPLETE name the page cells left open (#2449) (#2581) (a79eefe)

    whyThe engine's completeness check (`check --require-decided=pages`) holds per-page cells open — a criterion the trial pruned can leave a page cell « à évaluer » while the DOCUMENT decides the criterion (r4's 10.7, open on 18 pages) — and final_report used to read only the global undecided list: complete=false, undecided=0, nothing named, and under require_complete a GRID_INCOMPLETE that said « 0 criteria are still undecided ».

3.271.0 (2026-10-19) ​

Features ​

  • release: the cut warns on manifests its downward realignment strands (#2580) (943ac99), closes #2423

    whyA downward realignment is the one move that can strand a shipped bundle: the pin drops from the pending next minor to the patch being cut (3.180.0 -> 3.179.1), and a manifest whose requires.iterion copied the pending number now refuses runners that can read its sources — one release too HIGH, with no guard saying so. C252 and the push admission fire only when a declared floor sits BELOW what the sources need, and no after-the-fact arithmetic can tell an intended floor from a stale one.

3.270.3 (2026-10-19) ​

Bug Fixes ​

  • server,runner,model: the launch-time pre-flights read the run's vars through one RouteView (#2567) (de7ab88), references #2006 #2006 #668

    whyThe reviewed draft of 2026-10-12 (drafted as 126; 126 and 128 went to main's accessibility and studio pages since). Slice 1 of #2006 lands it with the RouteView; the number is claimed here so no other page takes it.

3.270.2 (2026-10-19) ​

Bug Fixes ​

  • model,delegate: a provider error captured as the model's result is named, not mislabeled structured-output invalid; claude_code's structured schema is strict-safe (#2500) (#2576) (b8cc9b0), closes #2527, references #2527 #2465

    whyThe rgaa-audit run of 16/10 filed six RGAA 11.1 occurrences; this fixes all six, and the label-association defect class they share:

3.270.1 (2026-10-18) ​

Bug Fixes ​

  • bots: copilot reads its own input schema, and the graph state travels around it (#2562) (790e385), closes #1740, references #1740

    whycopilot's `copi` entry node reused `turn_input` — the graph's per-turn state register, also `seed`'s output and `turn_state`'s input and output — as its LLM input. Seven of its fields are state the graph owns and Terra must never echo: the session anchors, the manager's raw scope exclusions, the scope-guard attempt, the clarification counters and the posture `mode`, whose prompt channel is the derived union (`effective_scope_exclusions`) instead of the manager's raw list. Every one of those…

3.270.0 (2026-10-18) ​

Features ​

  • permission,toolcatalog,ir: a class rule bounds every shell tool, and a check warns where a deny misses one (#2557) (8a2f8ce)

    whydeny: ["Bash"] never bounded claw's repl — a second shell whose name no alias row can close (allow: widens). Two operator-approved answers ship together, and the enumeration of code-executing tools lives in the REGISTRY, never in the permissions package.

Bug Fixes ​

  • studio: every audited form field carries its accessible name (#2527) (#2558) (fc4e942)

    whyThe rgaa-audit run of 16/10 filed six RGAA 11.1 occurrences; this fixes all six, and the label-association defect class they share:

3.269.0 (2026-10-17) ​

Features ​

  • modernize,assessment,campaign: a repository's suites run at every lot's gate (#2494) (bfce1c5)

    whyA lot converged over a test it broke. Its exit_gate — narrow by design — ran no test suite; the behavioural oracle watches served responses, not the repository's tests; nothing in the conjunction ran the unit or integration suites, which ran in the target's CI after the landing, where no gate reads them. Measured on a modernization campaign: a lot moved a deleting route from GET to POST, converged and landed — and the trunk's integration suite went red on a contract test still sending the GET…

Bug Fixes ​

  • claw: the crossing owns the runner's pipes, and a held stdout read ends at the drain bound, not the run's cancel (#2542) (db8815f), closes #2457

    whyThe launcher read the runner's stdout through StdoutPipe, whose own doc forbids cmd.Wait before the reads complete — exec closes the pipe's read end as the Wait's last act, and a pipe's buffered bytes die with its read end. That left the multiplexer's read unbounded: cmd.WaitDelay (clawRunnerWaitDelay) bounds the Wait, but a runner that died abnormally (an OOM kill, a failed result-envelope write) while a DESCENDANT still held the pipe parked the dispatch until the run's cancellation recovered…
  • server,studio,docs: no roster write reaches a member ranked above its author (#2493) (66d8362)

    why`requireRankOverMember` (`pkg/server/auth_authz.go`) reads a roster write's two ranks from the membership rows and answers 403 when the target outranks the author: the org roster's `PATCH`/`DELETE` and a team grant's `PATCH`/`DELETE`/`PUT` on `…/members/{user_id}`.

3.268.3 (2026-10-16) ​

Bug Fixes ​

  • botinstall,bots,server,studio: a catalog regeneration that fails reaches every consumer's user (#2496) (298552c), closes #2454

    whybotinstall.Install and botinstall.Remove threw the catalog regeneration error away (`_, _ = botregistry.RegenerateWhatsNextCatalog`), so `iterion bots install|sync|update|create`, the marketplace install/uninstall, the server install/upload routes and the studio's dependency sync all reported success while Nexie's catalog could keep advertising a removed bot or miss a new one.
  • dryrun,runtime: validate --exec decides a node's routes only on a fully specified launch (#2497) (6656b3f), closes #1778 #1778

    whyThe dry run invented a value for any declared var the caller did not supply, so its fan-out verdict read routes the launch had not decided: a fallback spelled `{{vars.fb}}` that resolves to `claw` was refused (#1778), a `when:`-gated route was counted where every launch drops it, and a free-form `backend:` read clean on the tool surface while claw's tool appends make the whole verdict.

3.268.2 (2026-10-16) ​

Bug Fixes ​

  • rgaa-audit: a retyped citation is folded with its anchor's text where the engine accepts both versions; the studio ledger's 1.3 citation healed (#2453) (#2490) (2275dc9), references SocialGouv/ultra11y#50

    whyultra11y 5.42.1's fold grounds a conformity's citation literally, then falls back to an evidence anchor it recognises in it; its trial grounds a conformity's citations as stored, with no fallback (SocialGouv/ultra11y#50). A citation the adjudicator retyped therefore lands at the fold and fails every later trial: r4's 1.3 (a closing backtick, 1 of the 515 citations of r2-r4) degraded the studio's trial, and the committed ledger carried it.

3.268.1 (2026-10-16) ​

Bug Fixes ​

  • bots: the plan author sees its own assumptions and risks, and the boundary names them (#2491) (5acee5c), references #1740

    whyA value on an LLM node's input reaches the model only if a channel of THAT node renders it, and a session is not a channel. The seven `plan_revise` nodes received the author's own `assumptions` and `risks` on their input and rendered neither: six answer the peer critique under `session: inherit_if_available`, which `applySessionContinuity` marks droppable and the retry path re-sends without, and feature-dev's node is deliberately `fresh` (its Kimi fallback has no cross-backend session handoff),…

3.268.0 (2026-10-16) ​

Features ​

  • bots: gitops-warden waits for its peer gates inside a bounded window (#2480) (e5e993d)

    whyThe warden read its pinned peer gates (revi/review, and next the devops' gitops-mr-review report) ONCE, at publish time: a peer still running at that instant held the arm until someone commented /warden again. On the team's gitops repos the CI jobs routinely finish after the verdict, so the "single gate" the operator chose could not arm on its own.

Bug Fixes ​

  • server: no org role is written above the writer's own, and a standing that drops takes the invitations it minted (#2484) (e505c33)

    whyTwo rules on the org rank. Both read the membership row from the store, never the token, which a removal does not rewrite.

3.267.0 (2026-10-16) ​

Features ​

  • studio,forge: pin a connection's webhook base and choose the merge-gate posture from the studio (#2479) (df0badc), closes #2445, references #2452 #2475

    whyA team admin can now wire a repo of a self-managed forge that refuses the deployment's public host as a hook URL (the PIC GitLab answers 422 "Invalid url given") from the studio alone:

Bug Fixes ​

  • claw: the sandbox crossing bounds its wait, reports the runner's stderr and redacts the far side (#2478) (cb64806), closes #2385, references #2456 #2457 #1547 #1945 #2456 #2457 #2458

    why`executeViaSandboxRunner` had three defects on the launcher's side of the crossing. `cmd.Wait` waits for the goroutine copying the runner's stderr, and that copier ends only when EVERY holder of the pipe has closed it — a descendant the sandbox driver's command left behind is such a holder — so an early abort that kills the runner, and the final drain of a node whose result envelope the multiplexer had already read, could park forever with no bound of their own. The runner's stderr, the…
  • runner,ir: the usage-cap pre-flight walks the run's topology, not the whole graph's (#2477) (4afb380), references #1828

    whyThe pre-flight's two narrowing walks appended every edge of the workflow whatever its `when` said, so a capped route standing on a branch the run's own launch had already excluded parked the run. `review-pr` launched with `mono_family=gpt` takes `topology -> reviewer_gpt` and never the unconditional `topology -> reviewer_claude` beside it, yet an operator cap on a spent Anthropic window refused to start the run, for those claude reviewers.

3.266.2 (2026-10-15) ​

Bug Fixes ​

  • bots,botregistry,server: the boundary walk is every shipped workflow, and 12 acting prompts join the class (#2476) (05098ec), closes #1722, references #1652

    whyThe package held two definitions of "every shipped workflow". The UNTRUSTED INPUT BOUNDARY walk, `shippedWorkflows`, read `bots/*/*.bot` plus the dispatcher's embedded fallback and excluded `examples/**` and `scripts/adhoc/**` by decision; `catalogWorkflowFiles`, fifteen lines away in the same package, counted them. `botregistry.DefaultPaths` is `[bots examples .botz]`, so `examples/` IS routable — by the zero-config dispatcher, the studio and the server — and twelve acting prompts there and…
  • runtime,bots: a run refreshes only the bot catalog its own bundle owns (#2474) (88e5ff6), closes #2120, references #2120

    whyThe engine regenerated the bot catalog at every run start, for every bundle under the workspace shipping iterion-bot-catalog-static.md, whatever the run was (runPersistWorkspace, pkg/runtime/engine_run.go). But a run mirrors only its OWN bundle's skills, so every other owner's copy was a file that run cannot read — and, in a git workspace, an uncommitted edit to a tracked file each time the running generator renders differently from the checkout. That is the standing case for a review run,…
  • sandbox: the session store keys by the node's session slot, not its bare node id (#2472) (fdf6950), closes #1547, references #2385

    whyThe in-process claw path addresses a node's conversation by its session key — the explicit `session_slot:` when there is one, else the node id — while every sandbox site addressed it by the bare `task.NodeID`: the pre-spawn replay seed, the host's mirror of the runner's captures, the runner's own local store, `ClawExecutor.Compact` and the schema re-ask. `task.SessionSlot` defaults to the node id, so the divergence bit exactly the nodes that declare a slot — the shared-conversation feature…

3.266.1 (2026-10-15) ​

Bug Fixes ​

  • backends,proc: what an agent node starts ends with the node (#2448) (8aad1cc)

    whyAn agent node's processes outlived it. The end of a delegation terminated the delegated CLI and nothing else, while the rule the engine injects into headless sessions tells agents to start anything longer with `nohup … &`. Measured on a modernization campaign: the agent node left the target's world running (application server, database, object store); one minute later the gate node booted its own world in the same workspace — the target derives its state directory and ports from the workspace…
  • rgaa-audit: the trial takes its conformities from the audit; the studio ledger takes r4's 102 verdicts (#2455) (be150f3), references SocialGouv/ultra11y#50 #2453 #2346 #2449 #2453 #2347

    whyr3 and r4 on iterion's studio (bilan in docs/bot-runs/rgaa-audit.md): the first complete audit (106/106 decided, 56 % then 52 %), the ledger replayed (r4: 93 verdicts for free, 20 $ instead of 42 $), and the second reader run on it.

3.266.0 (2026-10-14) ​

Features ​

  • bots,server: Vetty proves its delivery permission before pushing, and only an answered denial holds (#2436) (120435a), closes #1595, references #1585 #1597

    whyVetty (dep-update-guard) and Billy (branch-improve-loop) push to repositories they do not own with a managed GitHub App installation token. A Renovate bump that also realigns `.github/workflows/**` needs `workflows:write`, which that credential does not carry: the push died at the forge with a 403 after the run had already commented, so the pull request carried an alignment nobody could deliver and the operator read a git error instead of a cause.

Bug Fixes ​

  • runtime: a hand-cleared forfait is observed, and the probe that sees it spends no attempt (#2446) (2effbfc), closes #2247

    whyA retry armed for the instant a provider announced is invisible to every sweep until that instant: the due scan can only ever match `retry_state.retry_after <= now`. An operator who tops up or clears their forfait by hand reopens the window with no notice, so the run keeps waiting out a wall that has already fallen — the ticket measured four days on six review runs.

3.265.0 (2026-10-14) ​

Features ​

  • author,dsl: validate says when a document's .bot is another program (#2440) (9b4f244), closes #1763

    whyAn author document sits beside the .bot it was written from, and nothing said when the two had drifted apart: a launcher runs the .bot, the author reads the document, and `iterion validate` gave the document a green verdict while the program next to it was another one.

Bug Fixes ​

  • backends,bots: readonly: true beats a declared writing tool on claude_code, and 3 nodes drop the flag (#2443) (65886ed), closes #1816, references #1651

    whyOn claude_code the flag withheld the file-writing natives a node did not declare and KEPT the ones its own `tools:` list named, with a Warn. But `readonly:` is the node's access intent — the meaning it already has on codex (`codex.go:844` returns the `read-only` sandbox before any tool classification) and on pi (`pi.go:562` pins `--tools` to `read,grep,find,ls`), both unconditional, both beating the declaration — and it is also what admits a node into a fan-out branch as non-mutating, so a…
  • rgaa-audit: the studio dogfood's fixes — anchored recommendations, a truthful trial, a replay-safe ledger (#2439) (fa879e4), references SocialGouv/ultra11y#49 #2438 #2346 #2398

    whyFirst run of Acci v3 on iterion's own studio (bilan in docs/bot-runs/rgaa-audit.md): the rendered layer works end to end, and the run found four bot defects the fixture tests could not show.
  • server: a stale control listing refuses nothing, so a create-only loser answers 409 (#2441) (eca06a0), closes #2380

    whyBefore it takes a file's flock, an authoring save audits the private control directory of the file's own directory: it lists `.iterion/authoring`, then inspects every entry it listed — `control.root.Lstat`, plus an `O_RDONLY` open for the exclusion file, a lock and a record. A concurrent writer renames its `<uuid>.candidate` out of that directory (publication) or removes its `<uuid>.record` (completion) while the listing is already in hand: the inspection gets ENOENT, the audit refuses, and…

3.264.0 (2026-10-14) ​

Features ​

  • bots,runtime: a bot links the run that wrote it — run.url and the forge-lane guard (#2434) (b07e0a5), closes #2429

    whyAnything a bot posts on a forge now ends with the run that produced it: the evidence behind the text, and the way into the platform for a reader meeting this fleet on their own repository.

Bug Fixes ​

  • server: an invitation carries no org role above its creator's, and no team outside the org it names (#2433) (aa0f402)

    whyAccepting a team invitation writes the team grant AND, for a role on the org ladder, the org membership that role is mirrored up to (`pkg/auth` `grantOrgMembershipForTeam`: `admin`/`owner` to org **admin**, the others to org **member**). A mint therefore hands out ORG standing, while both minting routes were gated on team or org management alone and bounded the role by nothing their caller held. On `POST /api/orgs/{id}/invitations` the `team_id` the body names was never confirmed to belong to…
  • server: the shutdown joins the detached writes it spawns, and a census holds the class (#2432) (0139d4c), closes #1474, references #1257 #1345

    whypkg/server started three kinds of goroutine doing store I/O outside the shutdown join: goSafe (8 call sites — an audit insert, a last_used_at stamp, two invitation emails, the forge reviewer settle, a config-share touch), the OAuth connect kick, and the model-spec refresh flight. A SIGTERM during any of them loses the write, and unlike #1345's projection none of them has a reconciling sweep behind it: the audit row of a mutation answered 200 is simply gone.

3.263.3 (2026-10-14) ​

Bug Fixes ​

  • forge/gitlab: no line iterion posts to GitLab can run as a quick action (#2431) (1333865), closes #2413, references #2422

    whyGitLab runs a quick action — /merge, /approve, /close, /label, /assign … — written at column 0 of a note or of an issue or merge-request description, with the rights of the account that posts it, API posts included. What the server and the board dispatcher post to GitLab is model output over untrusted content (review findings and summaries, comments, bot-written descriptions): a prompt-injected finding carrying /merge would run with the bot's rights, past the merge gate's deterministic surface.

3.263.2 (2026-10-14) ​

Bug Fixes ​

  • runtime: one predicate answers both tool-surface questions; a codex tools: list is no bound (#2426) (380fe35), closes #1727, references #1652 #1652 #1651

    whyTwo questions ask "can this node act on what it reads" — the catalogue's prompt-injection contract, which decides who owes an UNTRUSTED INPUT BOUNDARY paragraph, and shared-worktree admission, which decides who may fan out onto one checkout — and they answered differently. `ToolSurfaceCanWrite` passed `sharedWorkspace=false`, which killed the route arm outright: a `backend: claude_code` node declaring `tools: [read_file]` was reported as unable to write, though that list becomes…

3.263.1 (2026-10-14) ​

Bug Fixes ​

  • backends: claude_code reads readonly:, and no declaration lands on both tool flags (#2409) (28f6fb5), closes #1673, references #1 #1 #1816 #1651 #1671

    whyA node's `tools:` list became `--allowedTools` while three rules that never consulted it added names to the `--disallowedTools` of the same spawn: headlessWithheldTools, the orchestration knob, and the declaration's own complement. Because claudeNativeForCanonical has no row for workflow/taskoutput/monitor, a declared Workflow, Task, Agent, TaskOutput or Monitor reached one argv on BOTH flags — twelve overlaps, on both spawns — so whether the tool was withheld rested on the CLI resolving deny…
  • compatgw: a provider-first gateway id resolves under its own qualifier (#2412) (c7d7f41)

    whyA LiteLLM gateway can name its models provider-first (`scaleway/glm-5.2`). Under OPENAI_COMPATIBLE_CATALOG_PROVIDER=scaleway the exact key became `scaleway/scaleway/glm-5.2` and every such id resolved to nothing, so the deployment had to carry each one in the operator table. Measured on the sovereign pool before that table: an unknown window, the 10K compaction floor, seven compactions and 853K tokens for a 59-file review.
  • dsl: fmt sees the comments written below a document's last content line (#2411) (4072abd), closes #1784, references #1784 #2328 #1760 #2328 #1783

    why`author.Comments` listed what yaml.v3 attaches to a node, plus the source reads for a flow collection's comments and for a directive line's. Below the document's last content line — around a `...` document-end marker, or under the last declaration — yaml.v3 attaches some of the lines written there and reads the rest without attaching them, so `fmt` compared two lists that both missed those lines: it rewrote the document and deleted them with no refusal and no notice, and `fmt --to bot` counted…
  • forge/gitlab: a review's inline counts are what the merge request holds, read back (#2419) (7c20ace)

    whyThe GitLab client counted a positioned discussion as posted from the create answer alone. Measured on a GitLab 17 instance (the sovereign Revi on !220): two discussions persisted behind error answers — counted lost, the review reported 0 inline comments, folded both findings into the summary a second time and raised "FORGE PUBLISH FAILED" over a merge request that held every thread. Nothing was logged: the error answers died in the client.
  • runtime: a subbot child works in its parent's tree, sandbox or not (#2421) (f50fac8), closes #1817

    whyThe `worktree: auto` in-place rule asked whether a shared sandbox existed while the comment above it stated the rule for a subbot child. The two part ways on a local unsandboxed `iterion run`: the child took a worktree of its own, and its commits landed on that worktree's branch — invisible to the parent that launched it to produce something in the parent's OWN tree.
  • server: a team grant cannot outlive the org membership it sits inside (#2406) (d207380), closes #1560

    whyAn org membership is the identity boundary a team grant sits inside, and the handlers that move those rows share no transaction — `pkg/identity` has none, in either store. The placement read the membership and THEN wrote the grant; the off-boarding swept the grants and THEN removed the membership. Either interleaving left a live team grant with nothing behind it: `SwitchTeam` reads the membership rather than trusting a claim, so the token it mints reads the team roster 200 while the org itself…

3.263.0 (2026-10-14) ​

Features ​

  • author: every YAML refusal names the spelling that avoids it, and each of its causes (#2405) (2063357), closes #1783, references #2404 #2404 #2404

    whyA YAML reader's own message says what it saw, never what to write. Six classes of message carried the spelling that avoids them; the rest reached a `.bot` author bare — an anchor sigil in a `&&` command, a block indicator with text on its key's line, a `- ` on a key's line, a `? ` opening a value, the escapes of a double-quoted regex or Windows path, the punctuation of a flow collection, a tag, a `%` directive, YAML's own depth bound, a control character. Two messages a reader gives for several…

Bug Fixes ​

  • auth: a stale user write no longer undoes a concurrent deactivation (#2394) (d2f61e8), closes #1862

    whyAn admin password reset read the user row, refused an already-disabled account, hashed a temporary password, then wrote the WHOLE row back — identity.Store.UpdateUser replaces the document by _id in both stores, under no precondition. A `PATCH status=disabled` committed inside that window was silently overwritten: the reset answered 200, revived the account to pending_password_change and handed out a usable temporary password. A second read before the write does not close it, it only moves the…

3.262.2 (2026-10-14) ​

Bug Fixes ​

  • test: the prod-watch cap witnesses age their fixtures from now, not from the calendar (#2418) (5ee7921), closes #2410, references #2161 #2362

    whyThe three Loki-cap witnesses seeded their records with last_seen 2026-09-30T00:00:00+00:00 while decide reads the wall clock. Retention deletes a record whose sighting passed forget_after_days — 14 days, 336 h (main.bot:4336, `gone_h >= forget_d * 24`) — so from 2026-09-30T00:00:00Z + 336 h = 2026-10-14T00:00:00Z the seeded records were gone at the first decide and the store's cut, the rule all three witnesses exist to judge, never saw them. Measured on the fix: sightings 335 h old green, 337 h…

3.262.1 (2026-10-13) ​

Bug Fixes ​

  • board: the forge issue sync pages its window, holds its mark on an incomplete read, and sweeps on one replica (#2402) (e171c1f), closes #2004

    whyThe forge→board issue sync read ONE page of 100 and advanced its high-water mark whatever happened. `since` is the only way back to an issue the sync did not see, so an issue whose update fell inside a window the mark jumped over — a rate-limit window, any list failure, or simply more than 100 updates in one five-minute window — was never offered again by anything, while the endpoint answered `{"synced":100}`. Nothing in the logs or on the board said an issue was missing: the loss was silent,…
  • bots: verify_probe refuses to reuse the empty gate its author declared (#2403) (116120b), closes #1707, references #1585 #1598

    whyThe reuse pre-check decided on shape alone (iteration > 0, non-empty file, `sh -n`), so the escape hatch a repo with genuinely nothing to build is told to write — a verify.sh that echoes that fact and exits 0 — became the run's standing verdict for every later pass: verify_run reported passed: true, skipped: false against a tree that may not compile, and skipped: false left no downstream reader, including the unattended-merge decision, able to tell it from a real pass.

3.262.0 (2026-10-13) ​

Features ​

  • bots: gitops-warden 0.2.0 — french verdicts, helm render, run link (#2389) (7b3c7bf)

    whyThe devops team's first two field feedbacks on the gate:

3.261.1 (2026-10-13) ​

Bug Fixes ​

  • bots,dsl: a dead input mapping is not a channel — 19 sites dropped, 4 unseen values now render (#2397) (e2c0acc), references #1740

    whyA key an edge's `with` maps onto an LLM node is written into that node's input whether the schema declares it or not, and reaches the model only if one of THAT node's own channels renders it. Nineteen sites — 24 fields — declared and received a value no channel of theirs rendered while the prompt already showed it under another reference, so each is dropped from the input schema AND from every `with` mapping: secured-renovacy x9 (`workspace_dir`), evolve x7, wiki-gen/author, and `hint_count` on…
  • bots,mdcode: a manifest's prose can no longer open a phantom fence in the generated catalogue (#2396) (56161db), closes #1723, references #1619 #2395

    whybots/review-pr's description was word-wrapped onto a line beginning with three backticks, and the bot catalogue splices that description as a block of its own: the wrap accident opened a fenced block nobody wrote. 1097 of the 1977 lines of bots/whats-next/skills/iterion-bot-catalog.md and 1059 of the 1601 lines of the issue-triage copy read as code — including issue-triage's own "NEVER invent a bot name" ritual. The catalogue is the routing table Nexie and Triagy read to pick an assignee, so…

3.261.0 (2026-10-13) ​

Features ​

  • rgaa-audit: rebuild Acci v3 on the pinned ultra11y engine (#2387) (d13bc05), references #2345

    whyThe accessibility bot no longer asks a model to find non-conformities. The ultra11y 5.42.1 engine — pinned by version and by the sha256 of its entry file, fingerprinted before anything of it runs and before every call — finds, gates and reports; iterion orchestrates: criteria the engine leaves open are adjudicated by read-only agents in parallel batches (one per theme), the engine's own fold is probed on a copy so its named refusals are re-asked once, the real audit is folded exactly once, a…

Bug Fixes ​

  • launch: the Run gate holds while a file of the unit does not parse or load (#2392) (d7cc76d), closes #1532, references #1326 #1934

    whyA bot in several files whose fragment does not parse re-opened the Run gate onto a launch the server refuses. The open answers `bindable: true` — the main parsed — and carries the fragment's parse errors as diagnostics; auto validation then validates the MERGED document, built from what parsed and so clean, and rewrites those diagnostics. The gate's only fact was gone, Run was re-enabled, and the launch re-parsed the raw source and refused after the form had been filled in.
  • review-pr: a question or ticket verdict naming a drifted file is workspace state; a published anchor stays repo-relative (#2393) (38b5956), closes #1674, references #2117 #1674 #1671 #1670 #1665 #1660 #2117 #1666

    why#2117 stripped the FINDINGS a review anchored on an uncommitted entry of its own workspace, but the three reviewer-authored free texts still reached the pull request verbatim: a `questions` line under "À confirmer", a `ticket_conformance` verdict under "Écarts au ticket", a `scanned_areas` entry inside "Périmètre revu". That is how #1671's first verdict asked the author to answer for two uncommitted catalogue regenerations the pull request never touched — the half of #1674 left open.
  • server: launching a run takes the member role on every human surface (#2391) (f7ef017), closes #2363, references #2364

    whyA team viewer, and the orthogonal config_editor capability, could launch a run: POST /api/runs passed only the quota gate, POST /api/v1/triggers/emit used that gate as a pre-check with no role, and the pipeline board's launch-now passed no gate at all — no role, no suspend check, no concurrency, rate or monthly cap. The documented contract (forge-permissions.md, cloud-rest-api.md) was member+. The queues a machine principal drains had the same hole one layer earlier: a viewer could write a card…

3.260.2 (2026-10-13) ​

Bug Fixes ​

  • runtime: the in-sandbox child-resource scripts refuse a symlinked .claude root (#2360) (6dd302f), closes #1824, references #2027

    whyThe three scripts the shared resource scope runs INSIDE a copy-based sandbox — snapshot, restore and reset in `child_sandbox_resources.go` — acted on `<workspace>/.claude` with nothing requiring it to be a directory. Through a symlink, `rm -rf "$1/<name>"` and `cp -a` reach the link's target: the reset deletes it, the restore rewrites it with the parent's bytes, the snapshot copies it into its backup. The host sites have refused this since #2027; this transport had no guard, and the agent…

3.260.1 (2026-10-13) ​

Bug Fixes ​

  • launch: a launch keeps its render out of the editor tab's source; it carries the program the operator saw (#2386) (ca8ceed), references #1793 #1790 #1789 #1790 #1793 #1789

    whyThe launch form reads the ACTIVE editor tab's document store (LaunchDocStoreProvider). Its inline branch — the toolbar's Run on a tab with no path, and the only launch cloud has — rendered that tab's document through /api/unparse and wrote the result into the tab's `currentSource`. A draft tab follows its conversation only while that field still holds the draft the assistant installed, so a render that was not byte-identical made the tab the author's: the assistant's next drafts stopped…

3.260.0 (2026-10-13) ​

Features ​

  • studio: the workflow inspector edits the tool-permission gate (#2383) (d4ed16f), closes #1734, references #1580 #1222

    whyThe node form's rule-list help told the author that a non-empty list REPLACES the workflow's list of its kind and to restate anything they still wanted — from a UI where the workflow's own mode and lists could not be edited at all. No studio code read or wrote `permission`, `allow`, `ask` or `deny` on a workflow block (grep on the base tree, rc=1), so the one instruction the control gives pointed outside the inspector, at the text view.

Bug Fixes ​

  • backends: a claude_code auth failure names the credential channel the spawn ran on (#2384) (49cb9dc), closes #1527

    whyA claude_code node routed to an anthropic-wire facade — `provider: zai`, the default precedence over the run's own z.ai/Moonshot key, or the ZAI_API_KEY shortcut — died on a rejected facade key with "check the forfait CLAUDE_CODE_OAUTH_TOKEN or the Anthropic API key": the two channels facadeRouteEnv had just cleared out of that very route. The remediation read task.ProviderHint, which every unpinned facade route leaves empty, so it sent the operator re-arming credentials that were never in the…
  • dsl: C011 permits the exit an iteration back-edge guards, declared after it (#2382) (e7f1413), closes #1386

    whyC011 refused any two edges from one node testing the same field with the same polarity, the bounded-iteration back-edge and the exit sharing its `when` included. An exhaustion exit could therefore not be guarded like its back-edge, and the one form left — a bare edge — carries the node's output onward even when the condition field is missing: a malformed `was_batch` walked into Phase 2 on the solo ledger instead of dying.

3.259.2 (2026-10-13) ​

Bug Fixes ​

  • claw: the agent tool is typed-refused instead of faking a start (#2376) (2969788), references #1 #1940

    whyclaw's `agent` executor validates its input and answers `{"agent_id": ..., "status": "running"}` while launching nothing — its spawning lives in claw's ConversationLoop, which iterion does not drive — and no iterion run wires a subagent runner: `pkg/runview/executor.go` and `cmd/iterion/claw_runner_proxy.go` both leave `ClawDefaults.Subagent` nil. A node that delegated waited on work nobody began, or reported on work nobody did, with no error and no event. Under `reasoning_effort: ultracode`…
  • runview: the fallback screen's sandbox probe reads the engine's repository, not the process cwd (#2378) (a418288), closes #2165, references #1405

    whyBuildExecutor screens an operator's `--fallback codex:…` on whether the run will be sandboxed, and probed runtime.RunWillBeSandboxed with EngineRepoRoot(spec.WorkDir) — a field five production surfaces leave unset while handing the ENGINE a different directory: the cloud runner's per-run clone, the runner and studio subbot children's inherited worktree, `iterion run`'s child run, and the CLI resume, whose engine roots at the run document's recorded RepoRoot and which no runtime.WithWorkDir grep…

3.259.1 (2026-10-13) ​

Bug Fixes ​

  • sandbox: the run's proxy pins the gateway's name against a rebinding answer (#2372) (4fa439c), closes #2336, references #2335

    whyThe in-container runner checks the OpenAI-compatible gateway's address as it resolves the name, then hands the run's network proxy the NAME (the proxy's policy matches names), and the proxy resolved it again on its own and connected wherever that second answer pointed. A resolver answering the proxy differently (DNS rebinding) landed the connection, the gateway credential with it, on the proxy host's private network: only loopback and link-local destinations were refused there.

3.259.0 (2026-10-12) ​

Features ​

  • budgetfloor: capacity can be RESERVED for a workload, not only capped (#1099) (fe32101), references #950 #950

    whyEvery budget mechanism in iterion is a ceiling. max_cost_usd, the org monthly caps, the per-credential meters, the window caps, the team concurrency and launch-rate caps, a pool pledge's spend/day — all answer "how much may this stop at?", none answers "how much is reserved for this, whatever else runs?".

Bug Fixes ​

  • modernize: a lot's contract is a directory, judged on every tree that lands (#2124) (db152fd)

    whylot_verify refused a lot that rewrote the plan and nothing else. The contract a lot is judged against is wider: the outcomes file (each `check` is the command the programme verdict runs), the brief (goals, owner decisions, permitted and forbidden changes), the arbitration doctrine a separate judge applies, and the defects register. A lot that rewrote an outcome's check to `true` and renamed a brief goal converged green.
  • studio: the admin console is nest-aware, sectioned and gate-tested (#2365) (77f5030), closes #1448 #1449

    whyThe `/admin` route group is nested (`<Route path="/admin" nest>`, 55a331b2c), which makes wouter report a location relative to `/admin` and resolve every href against it. Nothing inside the console was written for that. Measured against real wouter 3.10 with the real `<Router base="/studio">` and the real nest: at `/studio/admin/users` AdminNav highlighted "Organizations", clicking "Audit" navigated to `/studio/admin/admin/audit` — a blank page, the inner Switch matching nothing — and…
  • test: the resume and node-deadline tests witness facts, not a wall clock (#2361) (59ac333), closes #2161 #2160, references #1471

    whyTestResume_doesNotWaitOnAnotherRunsNode asserted `elapsed <= 2s` against a `resumeRefusalGrace` of 1s — one second of slack for scheduling, which a loaded runner closes (the ticket measured 2.782s). The wall clock is gone from the premise: the competing run's node now waits on a release file only the test writes, and the assertion is the happens-before the test always meant, that Resume came back while that node was still executing. Resume runs behind a 30s bound, so the forbidden behaviour…

3.258.1 (2026-10-12) ​

Bug Fixes ​

  • background: the pendingAt and waitedAt stamps ride the harness clock (#2341) (3c9198f)

    whyThe background harness stamped pendingAt (and its waitedAt sibling) from the wall clock while every decision around them reads the tracker's clock: under a scaled or paused clock the wait went negative or stale. Both stamps now come from the same clock the decisions use, and the sub-tests split per site (pendingAt and waitedAt redden independently).
  • brew: a tap run for an older or pre-release tag no longer walks the tap back (#2340) (f25a450)

    whypublish compared nothing: any later run for an older tag closed the newer tap PR and armed its own older formula. The run now reads the open tap PRs once, refuses when main already publishes newer (the window between the job's checkout and the publish is closed by the re-fetch), refuses a pre-release tag outright (sort -V orders -rc above its final), and closes only strictly older taps. A missing formula on main means nothing is published yet — the first publication works again.
  • gateway: the sandbox proxy road keeps SafeTransport's bounds and says what its guard still pins (#2335) (654cd30), references #2313 #2319

    whyThe gateway's proxy road (#2313) was rebuilt from a bare http.Transport: no dial timeout, no TLS handshake timeout, no idle reap. A proxy that never answers the connect (a rescheduled runner pod's stale IP) or that stalls the tunnel held the request, which carries no deadline of its own, past every bound the direct road keeps (5s to connect, 5s to handshake). The proxy road is now httpdial.SafeTransport itself, with its own Proxy and a dial through httpdial.PlainDialer, SafeTransport's dialer…
  • sandbox: the devcontainer deny class refuses the cloud switches, their credentials, and the rest of what steers a process iterion starts (#2339) (e84ac8d), references #2318 #2316

    whyA reviewed repository's devcontainer.json could plant the claude CLI's cloud-provider switches and the cloud credentials they authenticate with: containerEnv {"CLAUDE_CODE_USE_BEDROCK":"1","AWS_ACCESS_KEY_ID":…, "AWS_SECRET_ACCESS_KEY":…,"AWS_REGION":…} passed DeniedEnvKey — no suffix matched — and sent every prompt of the run's claude_code nodes to the planter's AWS account; the Vertex twin rides the iterion-default allowlist, which admits **.googleapis.com. The tests asserted names that match…
  • test: the dispatcher's bundle cache and the notify SSRF vetting stay off the operator's machine (#2326) (cd2dc94), closes #2080, references #2080 #2231 #2080

    whyTestEngineRunnerPromotesBareMainBot opened a `.botz` through NewEngineRunner, whose bundle.Open call hardcoded the default cache root — os.UserCacheDir()/iterion/bundles. Measured before the fix: one run of the test created a content-hash slot under the operator's real ~/.cache/iterion/bundles — the exact class #2016 closed for ~/.iterion, one layer down, in a dir that did not even exist before the suite ran.

3.258.0 (2026-10-12) ​

Features ​

  • ambient: ambient_context applies or refuses on opencode, kimi and grok (#2327) (6d5705e)

    whyOne verdict per (backend, policy) in pkg/backend/ambient — Honoured requires a live measure, Refused may rest on the source; a declared value is applied or refused, never ignored (new compiler error C187, per route, fallback defaults included); refusal sits at the top of buildTask before any effect, terminal and classified. codex's home overlay generalises to the three backends and the shadow permission builds from it without a special case; the precedence level travels through IOTask. ADR-119…
  • docs-refresh: coverage audit — the reference PDFs become a measured goal (#2139) (9e89ba2)

    whyWhen the run carries reference context, the reference PDFs' sections are extracted into a deterministic inventory (coverage_inventory — the goal as data), and a cheap read-only second-model auditor (coverage_audit) measures the produced docs against it: every inventory section gets a covered / partial / gap verdict by reading the files, never by taking the campaign's word. Gaps route back through the campaign (coverage_loop, bounded 2) with an actionable gap list; the MR body carries the audit…
  • dsl: fmt keeps a document's comments; validate reads launch vars as written; braced-env expansion is linear (#2328) (43fc603), references #1603 #1607 #1760

    whyAdversarial-Rounds: 1 local (announced 7, ceiling 20) Adversarial-Model: claude-opus-5[1m]
  • launch: a constrained var outside its constraint is refused at launch, before the run exists (#2329) (cfd9ff3), references #1757

    whyrunview.GateLaunchVars judges the literal launch values with the engine's own predicate at both entry points — cloud SubmitLaunch (after topology injection, before any line or publish) and in-process starts — and leaves environment-dependent values to the runner, which alone can read them. The refusal is a typed 400, the same contract as the #1757 gate next to it.
  • opencode: session resume — inherit, fork and schema re-asks resume the tour (#2332) (bcf642c)

    whyopencode joins the session-resume backends: --session <id> on inherit, inherit_if_available and fork; an unknown id fails loudly (1.19) and the documented degrade arms through ClassifyFallback. A bare --fork before `run` swallows the subcommand (yargs) — emitted as --fork=true, measured on 6 argv shapes. The composed system prompt rides the resumed tour. Proven against the real opencode 1.19.0 (clilive, no network, no credential), red-first, 7/7 mutations killed. session: persist stays unwired:…

3.257.4 (2026-10-12) ​

Bug Fixes ​

  • netproxy: the proxy refuses loopback destinations; !host lifts the model match (#2050) (#2151) (dcbc965)

    whyA proxied request to a loopback or link-local address dialed the PROXY HOST's own local services — the egress allowlist governs destinations, and the proxy host's local services are not among them. The dial wrapper now refuses them (loopback, link-local, localhost names) before it dials; the one sanctioned alias, host.docker.internal, is rewritten to the host gateway beforehand and unaffected, so the per-run MCP listeners keep working. docs/secrets.md's allowlist promise is now exact.
  • sandbox: a sandboxed claw node spends the host's ChatGPT forfait ITERION_OPENAI_USE_OAUTH=1 orders (#2325) (22c867a), references #2324

    whyITERION_OPENAI_USE_OAUTH=1 makes claw's in-process OpenAI factory spend the host's ChatGPT forfait over OPENAI_API_KEY; a sandboxed claw node never got that forfait unless the run's own credentials carried one, so the host spent the forfait while its sandboxed twin spent the metered key.

3.257.3 (2026-10-12) ​

Bug Fixes ​

  • sandbox: the runner's workspace .env can no longer pick a forwarded credential's endpoint (#2321) (b4fbc70), references #2315

    whyEndpoint names in providerEndpointEnvVars the host leaves unset cross as explicit empties; the runner's applyDotEnv honours LookupEnv, so the empty blocks the workspace .env line — the repository under review no longer decides which infrastructure a forwarded credential dials (#2315). Keys keep the skip (an empty key reads as an invalid auth attempt), and the endpoint list is pinned to the forwarding list by a pairing test.
  • test: suites that spawn login shells run under a HOME of their own (#2231) (#2322) (41b8f32), references #882 #2291

    whyThe five-line `##` comment over extend_result's `notice` sat twice in a row since #882, a paste debris of the class #2291 cleared on main. A sweep of the tracked tree for that class — conflict markers, commit- subject fragments, rebase debris, doubled lines and blocks — found no other instance. Comment only: the compiled program, `iterion validate` and the canonical form are unchanged; no version bump.
  • webhooks: a cleared AuthorAllowlist survives the Mongo document replace (#2323) (0df479b), closes #2175, references #2175 #1588 #1588 #2175 #1588 pre-#2175 #2175 #2175 #2175

    whywebhooks.Config.AuthorAllowlist carried bson:",omitempty" while MongoConfigStore.Update is a full-document replace, so the operator's deliberate clear — a non-nil empty slice through the webhook PATCH — was dropped on write and read back nil, indistinguishable from never-set. The bson tag drops omitempty (the json tag keeps it: the API shape is unchanged); HoldLabels/LabelAllowlist set this exact precedent in #1588. Every reader of the field treats nil and [] alike (len==0 / range → allow-all),…

3.257.2 (2026-10-09) ​

Bug Fixes ​

  • backend: the gateway dials a policy-isolated sandbox through its proxy (#2313) (a6bb0f9)

    whyMeasured in prod (2026-10-08): every honorabilite review — routed to openai_compatible/scaleway/glm-5.2 — parked on `dial tcp 79.137.10.7:443: i/o timeout` while the same runner pod's own curls to the same endpoint succeeded 30/30. The chain: the sandboxed review runs in a per-run pod whose NetworkPolicy only allows egress through the runner's network proxy, and the gateway client (pkg/backend/compatgw) builds its transport with Proxy nil BY DESIGN — an ambient HTTPS_PROXY is never trusted for…

3.257.1 (2026-10-09) ​

Bug Fixes ​

  • claw: the sandboxed runner reads the host's xAI endpoint, stream watchdog and retry budget (#2311) (5773150), references #2292 #2292

    whyA sandboxed claw node runs its provider calls in `iterion __claw-runner`, which rebuilds its registry and claw backend from the env forwardableProviderEnv hands it and from the task envelope, nothing else. Three of the host's settings never reached it, so the host honoured them and the sandboxed twin silently did not:

3.257.0 (2026-10-08) ​

Features ​

  • cloudpublisher: a team's LLM fallback policy — the sovereign posture (#2309) (81d3fd3)

    whyTeam.LLMFallback names how the credential walk falls back for the team's runs. Empty or 'platform' keeps the shared tiers as always; 'none' is the sovereign posture: the walk consults only the team's own credentials (its BYOK keys, its OAuth forfaits), no shared tier (org, pool, platform) ever seals into the team's bundle, and an LLM route nothing of the team's funds refuses the launch by name — every pinned provider must hold a credential of the team's own, because the shared tiers would have…

Bug Fixes ​

  • sandbox: a repo's devcontainer.json can no longer re-route LLM egress or shadow operator env (#2310) (c53c25e), references #2303

    whyIn sandbox auto mode the target repository's devcontainer containerEnv/ remoteEnv reached the container unfiltered, beat the operator's env overlay, and the found-devcontainer branch dropped the workflow's own network:/env: blocks — a repo under review could re-route every LLM call to its own collector. Now: an env deny class removed at the single repo-to-spec seam (sandbox_env_denied event), the workflow's sandbox block carries over field-by-field, the operator's overlay overwrites…

3.256.0 (2026-10-08) ​

Features ​

  • server,forge: the rate-limit resource joins the budget key; the #2296 assumptions get witnesses that bite (#2306) (9e8385c)

    whyThe two surviving-mutant witnesses the round could not kill: an hour that began before counting did now has a witness for its "counted from" marker and for the stop falling exactly on the hour's end, and flush has one proving it hands the hour out and empties the tally — a second flush reports nothing, post-flush work is the hour's only remaining content. Both behaviors were already right; the witnesses were missing.

Bug Fixes ​

  • runner: a delegate_error books its cost and tokens in RunTotals (#2241) (1e5284d), closes #1548, references #992 #1385

    whyThe org-metering accumulator read delegate_finished alone, so a delegation that died (timeout, rate limit, transport error) was unbilled — the org's monthly bucket and the credential-pool ledger under-read what the credential actually spent. The error case mirrors the finished one: the delegate's own cost figure (omitted when the price table did not know the model — unknown, never free), one aggregate token count to the aggregate direction (#992), the claw summarised guard so a loop whose…

3.255.0 (2026-10-08) ​

Features ​

  • bots: gitops-warden — the gitops merge gate bundle (#2235 slice 2) (#2248) (c0622da)

    whyA gating bot's deterministic verdict can now act: the publish endpoint gains an optional `verdict` block executed by the server after the review and the gate status land — approve → request reviewers → arm merge-when-pipeline-succeeds, arm skipped on a failed approval.

3.254.1 (2026-10-08) ​

Bug Fixes ​

  • server,docs: the legacy knob's runbook says it is gone; the dead seams go with it (#2298) (c53f235)

    whyThe round's findings, all non-behavioral: browser-security.md still documented the env knob in the present tense as the instant reversal, when this deletion removed its reader — the reversal is `git revert` of the commit, and the doc says so now; the truncated comment remnant at the deletion site is gone; the acceptLegacy parameter — always false since its last true caller died — is removed from sessionCookie and its six callers (the host-prefixed branch refuses the bare name outright); and the…

3.254.0 (2026-10-08) ​

Features ​

  • forge: report each App installation's lowest rate-limit budget hourly (#2296) (ab9c8e3), closes #2105, references #1995

    whyThe forge transport received X-RateLimit-* on every GitHub answer and read them only on a refusal, so an installation's budget was seen once spent (#1995), never while it drained. The forge client's counting transport now reads the budget each answer reports, and the hourly report gains a line:

3.253.0 (2026-10-08) ​

Features ​

  • bench: discovery reports the within-node split between orientation and work (#2295) (381c449), references #1481

    whyUsage landed once per node, at node end — the report said so itself ("the intra-node split … is not reported"). The turn checkpoints carry per-turn usage AND per-turn tool summaries, so the split is turn-granular and the report carries it: a mutating node's turns attribute to orientation before its first mutating tool call and to work from that turn on (the boundary is sticky; a turn mixing reads and a write counts whole to work). The corpus aggregates the split with its coverage — how many…

Bug Fixes ​

  • review-pr: the claude slots' backend is tunable like every other slot's (#2294) (b0a1e26)

    whyThe claude slots were the only nodes with a hard-wired backend — the gpt/emit slots already carry ITERION_VIBE_BACKEND_* env-forms (claw by default). A sovereign pool pod carries ONLY its regional gateway credential, so its claude slots must route to claw (the backend that serves openai_compatible/* routes); hard-wired, the slot went to a z.ai/Anthropic wire the pool cannot serve and parked the run on an auth recovery — measured on the first pool review (prod, 2026-10-08).

3.252.0 (2026-10-08) ​

Features ​

  • credpool: fine spend attribution for the fallback door's leases (#2255) (#2279) (d8a7d00)

    whyThe arbitration's answer: per-credential attribution IS derivable - the runner's metrics emitter already accumulates per (backend, model, fingerprint) - so a door-served run books only the slice that ran on the donor's credential, and the consent copy slims down.
  • supervise: the turn-boundary step event carries the message's token accounting (#2287) (73dade3), references #1481

    whyThe transcript observer emitted its turn-boundary llm_step_finished with nil data — the per-message usage the claude_code transcript already carries (input, output, cache read, cache creation) was decoded past and dropped, so a supervisor session's spend attributed to nothing. The event carries it now under the model hooks' field names, and a usage-less message emits the bare event as before (#1481).

3.251.0 (2026-10-07) ​

Features ​

  • openai: an operator knob opts the OPENAI_BASE_URL factory into stream usage (#2283) (832d437), closes #2085

    whyStreaming calls through a custom OPENAI_BASE_URL never requested stream_options.include_usage, so their tokens stayed invisible to the usage_unreported accounting and to max_tokens/max_cost_usd. The new ITERION_OPENAI_STREAM_USAGE knob (classified infra, spend-adjacent) opts the openai factory into OpenAIStreamUsage on both forwarding sites (env and BYOK); unset keeps today's behavior exactly — providers that reject stream_options are untouched.

3.250.1 (2026-10-07) ​

Bug Fixes ​

  • chart: the pool pod takes the authenticated Mongo URI by key reference (#2282) (4cb7a2e)

    whyThird prod-found break on the first pool boot: the runner's Mongo credentials ride ITERION_MONGO_URI in the auth Secret (envFrom on the shared runner), which the pool pod deliberately does not mount whole. It now takes that key by specific reference — the pod's Mongo privileges are the deployment's own (the accepted residual, D14 F4: the pods sit inside the install's network boundary).

3.250.0 (2026-10-07) ​

Bug Fixes ​

  • queue: a pool-serving Connect brings up the pool's own topology (#2278) (1bf50bc), references SocialGouv/iterion#2029

    whyThe first pool pod CrashLooped twice in prod: past the JWT (fixed in the chart), the boot's EnsureSchema created a shared-subject stream under the pool's name, and the install's shared stream refused the connection with subjects-overlap (API 10065). natsq.Config gains Pool: Connect brings up the pool's own stream pair (exact pool subjects) plus the shared KV buckets, refuses an explicit stream name alongside, and the shared topology is never touched. The KV part of ensureSchema is extracted so…

3.249.1 (2026-10-07) ​

Bug Fixes ​

  • chart: the pool pod takes ITERION_JWT_SECRET by key reference (#2276) (02b551b)

    whyThe cloud-mode runner validates the JWT secret at boot; a pool pod booted into CrashLoopBackOff without it. It now takes that ONE key from the deployment's existing auth Secret by specific key reference — the ring (ITERION_SECRETS_KEY*) stays out of the pod (P4-b), the pool still carries the gateway credential only in its env.

3.249.0 (2026-10-07) ​

Features ​

  • chart: sovereign runner pools — one runner deployment per pool (P8/D10') (#2273) (c3796b4)

    whyrunnerPools[] renders a runner Deployment per pool, pinned three ways: ITERION_RUNNER_POOL (the admission stamp the pod checks every delivery against), the pool's JetStream stream + DLQ (rendered from the pool name, overriding the shared configmap), and the pool's regional gateway credential (OPENAI_COMPATIBLE_BASE_URL configured, the API key from a manually-managed Secret — the chart never renders credentials).

3.248.0 (2026-10-07) ​

Features ​

  • queue: control-plane commands carry the run's admitted identity (P6/D14) (#2270) (5be28b3)

    whyThe cancel and steer commands rode shared core-NATS subjects with no identity — a pod could publish or act on another pool's run commands. The emitting server stamps the run's admitted identity (headers, from the document's frozen stamp it just read) and the pod holding the run verifies the stamp against the message it admitted before acting: a command of another tenant or pool, or unstamped, is ignored with an error log. The subjects stay shared — the guard lives at the pod, the one place that…

Bug Fixes ​

  • dsl: a mis-indented top-level declaration survives a broken inline list (#2272) (3fb3f4b), closes #2259

    whyA keyword that names no property anywhere — secrets, presets, prompt, mcp_server, dsl — is legal nowhere but the top level. Mis-indented after a broken inline list, it was refused with the enclosing block's own noise (E012 unknown property, or the edge-syntax fight on the header forms) and the whole block was swallowed with it.

3.247.0 (2026-10-07) ​

Features ​

  • pisdk: report a subtree sweep that burns its whole wait budget (#2269) (0426727), closes #2141

    whyClose()'s orphan sweep kills the process group then joins it for up to 2s. When the group is still resolvable at the end of that budget — a zombie pinned to a parent that never reaps it, an orphan under a PID-1 container, or a group probe failing with a non-ESRCH error — the expiry was silent, indistinguishable from success: every Close paid the full wait without a word, and the zombie-orphan class stayed in the dark.

3.246.0 (2026-10-07) ​

Features ​

  • queue: the run lease carries the run's admitted identity (P5-c/D14) (#2266) (8bfe6a4)

    whyThe distributed run lease (NATS KV, per runID) carried no identity — a pod could acquire, refresh or release another pool's run lease, the revision guard attributing nothing. The lease now carries the tenant (and pool, when stamped) of the message the holder actually admitted, stamped from the delivery context on both the run's own lock and the engine path whose subbot children lock their own runs. Acquire fails closed without an admitted identity; a failed CAS write is classified by one read —…

3.245.0 (2026-10-07) ​

Features ​

  • budget: on_exceeded: pause parks a run at its cap, resumably (#2263) (4775ecc), references #560

    whyA per-run budget ceiling ended through the failure path — and the 90% block preempts every cap crossing a node-sized step can produce, so a run approaching its cap never even reached the choice. The declared policy now has a second member:

3.244.0 (2026-10-06) ​

Features ​

  • llmroute: the transcript handoff - recorder, seal, attach, render (#2210 slice 4) (#2237) (a0fba82)

    whyWhat a cross-harness switch hands the incoming harness (ADR-121 § Delivery 2). S3 said the switch; S4 records the outgoing node's work, seals it at the crossing, attaches it to the incoming element, and renders it - reuse continues the node mid-work, restart re-runs the original prompt with the transcript as reference.
  • reviewtopology: a pinned mono_family no credential serves refuses the launch (#2258) (be4837a)

    whyA launch whose configuration cannot succeed does not start. The resolver answered "is there a route for this family on this tenant?" only after the first review node died — a whole repo's required check went dark while every run failed identically, and the 7-day runs listing hid the cause (2026-09-22). An operator pin on mono_family naming a family the credential set cannot serve now refuses the launch, naming the pinned family, the available ones and the fix; the silent substitution it…
  • store: IR blob reads are mediated by the run's tenant (P5-b/D14 F7) (#2260) (a1d9191)

    whyAn IR blob (or bundle snapshot) read resolves the run id embedded in its key and loads that run under the caller's tenant filter before touching the bucket: a foreign tenant's key refuses with the same not-found a missing blob would return, and an unattributed caller fails closed rather than reading the bucket. The snapshot shape ("<runID>-bundle-<digest>") falls back to the owning run before the boundary. The S3 credential remains deployment-wide — the accepted residual (the pods sit inside…

3.243.0 (2026-10-06) ​

Features ​

  • forge: merge-gate gestures — approve, arm MWPS, request reviewers (ADR-123) (#2238) (c7f9df1)

    whyA gating bot's deterministic verdict can now act: the publish endpoint gains an optional `verdict` block executed by the server after the review and the gate status land — approve → request reviewers → arm merge-when-pipeline-succeeds, arm skipped on a failed approval.
  • server,identity: the trigger relay verifies run events; one-team-per-pool backstopped at the store (P5-a/D14) (#2252) (5aa234e)

    whyThe trigger bus is a cross-tenant relay: a run-outcome event names a tenant and, through a matching subscription, launches a run under it with payload-derived vars. The publisher is not only the server — the cloud runner publishes its own runs' outcomes with the platform credential — so a compromised pod could forge iterion.events.run.finished.<victim> and fire the victim's subscriptions.

Bug Fixes ​

  • dsl: a broken inline list rescues the ancestor property and the off-stack sibling (#2081) (#2256) (6d5bba3)

    whyTwo deferred rescues of the broken-inline-list recovery (#2081). A less-indented line an ANCESTOR block still owns — the parser replays the consumed INDENT/DEDENT history (openBlockColumns) to tell it from a line nothing open owns — is landed on, and each block loop between the list and the ancestor is handed the closing DEDENT it waits for, spliced ahead of the ancestor line. An off-stack dedent in the remainder (an Error after pops: the misaligned line leaves no level behind, so no DEDENT…
  • dsl: a C137 on a group member is fixed at the group's literal, one edit serving every use (#2257) (e4dec50)

    whyA tool declared inside a `group` is instantiated by `use` under the dotted id `<prefix>.<member>`, a name the source has not: `iterion fix` left the quoted reference to the author. The id now resolves through the AST (the use's group, the tool it declares), the member's literal is located inside `group <g>:` in the token stream, and ONE edit at it remedies the C137 of every instantiation — the proof counts the removed diagnostics per (node, reference), since the same literal raises one…

3.242.0 (2026-10-06) ​

Features ​

  • cli: resume every usage-blocked run in one command; list by failure code (#2249) (19189fc)

    why#2247's manual half. A forfait reset is never observed by the parked runs: their retries arm at the provider's ANNOUNCED reset (days out), so the operator who resets early wakes each run by hand, one id at a time. 'iterion remote runs resume --usage-blocked' sweeps every failed_resumable run carrying failure_code USAGE_LIMIT_BLOCKED and resumes each through the normal endpoint — the full admission gate and the runner's usage preflight, the authoritative probe: a run whose window is still shut…
  • contracts: C310 warns when a contract port binds a node that runs more than once (#2243) (b15a920), closes #1536

    whyA fan-out body executes its nodes once per branch (per item under fan_out_each) under the same node id, and the contract port capture keeps one entry per node id — a declared output port bound into the body projects whichever branch execution finished last, one branch's output, not the collection. bindOutput now warns beside C304 when the producer sits in execBranchBodyNodes' set; the convergence node — where the branches join and execution is single again — stays silent, and so does the trunk.…
  • credpool: the routing fallback door - consent, probe, consult, seal (#2210 slice 5) (#2251) (f783a64)

    whyA donor who marks their pledge fallback_use consents to serve a FALLBACK rung of a run that holds its own credential - the run's routing policy needing a kind the run lacks (ADR-121, Delivery 2 slice 5). The whole-bundle gate is untouched; with no opted-in donor the delivery costs one indexed probe and changes nothing else.

3.241.0 (2026-10-06) ​

Features ​

  • dsl: refuse the unread loop cap on a router edge (C309); walk run/loop refs in compute and when expressions (#2239) (3070832), closes #1314 #1506, references #1314 #1506 #1506 #1386 #1506

    whyC309 (#1314): a round_robin or non-multi llm router's outgoing edge carrying 'as name(N)' compiled and the cap was never read at run time — execRoundRobin alternates over its unconditional edges and execLLMRouter takes the target the model named, neither through evaluateEdgesWithLoopsRS. The compiler now refuses the shape (error, symmetric to C244), skipped on llm multi: true where C244 already rejects it. Renumbered from the drafted C153 — main's C153 is DiagUnknownRunMember.
  • secrets,queue,cloud: sovereign sealing — key ring off the pods, one team per pool (P4/D13, ADR-123) (#2236) (4676f05)

    whyThe ring leaves the runner pod. The publisher generates a fresh 32-byte DEK per run, seals the bundle under it (same tenant/pool/run AAD), and puts the DEK on the RunMessage (schema v24) — it transits only the run's own pool stream. The runner builds the per-run sealer at claim time; a "dek"-stamped record whose message carries no key is corrupt and refuses with a typed error (errors.Is-able). The two older cohorts stay readable: id-less records through the legacy path, ring-id records through…

Bug Fixes ​

  • backends: ANTHROPIC_CUSTOM_HEADERS cleared on engine-owned routes; a facade-pinned auth failure names the provider (#2242) (9b95cb2), closes #1476 #1527, references #1527

    why#1476: an ambient ANTHROPIC_CUSTOM_HEADERS — applied LAST by the CLI (claw identity.go) — rode every route the engine claims to own: an ambient 'Authorization: Bearer …' handed the facade's gateway a foreign credential. The suppression map and facadeRouteEnv now clear it. The shared clearedAnthropicChannels deliberately does NOT: on a bound-direct route the header is the operator's gateway config and stays theirs, and the anthropic-hint env-only path keeps its inherit contract.
  • runtime: library skills mirror both discovery shapes; the pruner reads the recorded write form (#2240) (1ee4a45), closes #1478 #1526, references post-#1500

    why#1478: the bundle and plugin tiers write <name>/SKILL.md AND the flat <name>.md alias; the library tier still wrote the directory form only, so a bot Reading .claude/skills/<lib-name>.md NOENT'd on a library-referenced skill. Both library mirrors (local and cloud-injected) now route through mirrorFileSkill — identical shape and marker layout across the three tiers. The owned path is computed through skillDestDirForm so the case convention and marker layout match what was actually written. Port…

3.240.0 (2026-10-06) ​

Features ​

  • llmroute: the cross_harness field, the screen third state, the dispatch marker (#2210 slice 3) (#2226) (160654f)

    whyDelivery 2 of adaptive routing, slice 3 of 5 (ADR-121 § Delivery 2, the session contract's THIRD state): `cross_harness: off|reuse|restart` — the policy field, the launch screen's relaxation, the wire, and the dispatch marker that says the switch.
  • secrets: run bundles carry a key id and bind to tenant, pool and run (P4/D13-a) (#2225) (8b693a2)

    whyA run-secrets record names the ring key that sealed it, and the AAD binds the ciphertext to the run's full identity — tenant, pool, run — so a bundle served outside its own identity context refuses to decrypt instead of handing over credentials. The binding makes the runner's tenant check cryptographic rather than a field comparison.

3.239.1 (2026-10-06) ​

Bug Fixes ​

  • delegate: the wrap-up take is bounded on the answer wait — no more 15-minute zombie sessions (#2202) (#2227) (29b70d8)

    whyOnce iterion sent the background wrap-up, the lifecycle armed no timer until the CLI's replay showed a turn took it: timer() returned bgTimerNone while wrapUpSent && !msgTaken, and a CLI that never took the wrap-up parked the session until the 15-minute hot watchdog — one zombie session per occurrence in production.

3.239.0 (2026-10-05) ​

Features ​

  • llmroute: the model class table and per-level overrides (#2210 slice 2) (#2222) (a64fcd4)

    whyDelivery 2 of adaptive routing, slice 2 of 5 (ADR-121 § Delivery 2): the model class table — top/standard/fast per family, shipped in pkg/llmroute beside the crossing default it generalizes — and every policy level's ENTRY-WISE per class×family overrides of it.
  • runview,runner: sovereign pools refuse server-side LLM content surfaces (P3/D12) (#2221) (9b8542a)

    whyThe three server-side auxiliary LLM surfaces — the merge-conflict resolver, declared supervisors, the session board — send content derived from a run to a model resolved in the server process. On a run stamped to a sovereign runner pool, that content now crosses to a vendor only never: each surface consults the run document's frozen pool stamp and refuses with a typed error naming the remedy, unless its model is itself gateway-routed (the operator provided the pool's openai_compatible…

3.238.0 (2026-10-05) ​

Features ​

  • llmroute: the org and team routing-policy levels (#2210 slice 1) (#2217) (a426004), references #2000 #2126

    whyDelivery 2 of adaptive routing (epic #2000), slice 1 of 5, on the ADR-121 foundation (#2126): the two greenfield TENANT levels of the routing policy — platform < org < team < shipped bot < binding < run — with their store, their fold layers, their control surface, and the ADR amendment owning the whole delivery-2 contract.

3.237.0 (2026-10-05) ​

Features ​

  • cloud: pool-aware DLQ surface, the broker routing bite, and the pools doc (P1b-iii) (#2216) (c4725c2), references #2029

    whyThe runner side of the sovereign pools (#2029, plan v2.1 D4'), rebased on the current main (the P1b-i topology it builds on landed through a parallel session):

3.236.0 (2026-10-05) ​

Features ​

  • dryrun: never-mapped {{input.X}} at compile, path-only case its own kind, bounded loops crossed three times (#1455 #1307 #1457) (#2187) (771c79a)

    whyC308 (#1455): an {{input.X}} a node body reads (prompt, tool command, script or action param, compute expr, fail message) that NO incoming edge maps is empty on every path - a compile-time warning, no --exec needed, the hint naming every incoming edge of the node. Entry node aside (the run payload the compiler cannot know), subbot/emit `with:` aside (C149's forwarding channel), `_`-injected fields aside. The catalogue compiles with zero instances today: the two adr-cartograph cases were already…
  • llmroute: the mid-run ledger filter, the session contract, and the re-pick verification (#2126 slice 4) (#2199) (026d350)

    whyThe route cooldown ledger arms AUTH on a policy run: the run's RESOLVED trigger set is threaded to the executor (ExecutorSpec.LLMRouteTriggers), and an auth refusal records a RUN-LONG cooldown (7 days, inside the ledger's 8-day plausibility guard) — the selection's own scenario (a claude_code primary on a chatgpt-only tenant) fails auth-typed ONCE, and every later node skips the doomed spawn instead of paying it again, judges and authored-route agents included. transient_exhausted deliberately…
  • runner: pool admission — a pool pod serves ONE pool (P1b-ii) (#2212) (3558558), references #2029

    whyThe runner side of the sovereign pools (#2029, plan v2.1 D4'), rebased on the current main (the P1b-i topology it builds on landed through a parallel session):
  • studio: bot-roles / sandbox / bot-vars / platform-credentials admin screens (#1443) (#1461) (65d5bfa), references #1441 #1442 #1448 #1442 #1463

    whyThe remaining four platform-settings consoles, on the T1 API layer (#1441) and sharing the T2 screen pattern (#1442). Each reads env-default + stored override + effective + origin and edits the override with the server's merge semantics (absent field = unchanged, blank/remove = clear).
  • studio: clear usage-readings action on the credential spend screen (#1445) (#1470) (b4005fa), references #1441 #1444

    whyThe operator escape hatch (admin_usage_readings_routes.go): when a provider reset a usage window early, the stale reading keeps refusing every run of that credential pre-flight and can never refresh. Clearing it by fingerprint unsticks that one credential without touching the global caps. No UI reached it before.
  • studio: RequireSuperAdmin route guard for /admin/* (#1447) (#1467) (55a331b)

    whyThe /admin/* routes were mounted as bare component routes with no route-level authorization; each page repeated its own is_super_admin + cloud-mode check and nothing guarded the group. This adds one guard around the whole console.

Bug Fixes ​

  • backends: claude_code and codex classifiers read only the transport tier off stderr (#2142) (#2184) (1336d37), references #1645

    why#1645 scoped the shared CLI-agent retry loop to the transport tier on stderr, but the two per-backend classifiers kept matching prose network signatures against the CLI's WHOLE stderr: a crash dump quoting source text that carries a signature (the measured Bun "JSON Parse error: Unexpected EOF" class) re-typed a DETERMINISTIC failure as transient and burned the retry budget. claude_code's retypeNetworkError and codex's terminal-failure check now read matchesTransportStderr off stderr —…
  • delegate: the background wait-budget fire re-reads the tracker — a stale fire no longer parks the session (#2197) (#2207) (b04051d), references #2201 #2202

    whyMerge-group run 37232043905 saw a scripted session produce one turn then make no progress for its entire 845s wedge net: the bgTimerWait handler acted on the state the timer was armed for without re-reading the tracker, which the SDK reader feeds AHEAD of the select loop. A fire landing after the wave had already come back sent a spurious wrap-up naming zero tasks; wrapUpSent && !msgTaken then arms no timer at all, and the session parked until the wedge net.
  • fork: judge supplied inputs at first resume; refuse an untrusted run whose sandbox resolves to none (#1743 #1773) (#2193) (35a83b7), references #1608 #1564 #874

    whyA forked child is parked cancelled and executed by Resume, and Resume deliberately never re-judges stored values — so the values an operator supplied to `fork --new-inputs` crossed no var-constraint gate at all: the same value Engine.Run refuses typed ran to completion through the fork path.
  • runtime,bots: bot scripts resolve binaries and data blocks from engine-owned sources, never the workspace (#1737, #1799) (#2188) (00410b6), references #1797

    why#1799 — the class: a catalog bot's script executing a binary out of the (untrusted) workspace.
  • studio: a hidden editor tab answers no global shortcut and shows nothing over the tab on screen (#1788) (#1813) (6a2845a), references #1793 #1755 #1662 #1755 #1735 #1735 #1830 #1755 #1830

    whyEvery hydrated editor tab stays mounted, and each answered the window: Ctrl+Z on one tab undid the others, Ctrl+S saved them, Ctrl+O and the file picker opened into them. EditorView now provides its `active` flag to its subtree (useEditorTabActive, "no" outside any EditorView), and what acts on anything global asks it first: the Toolbar's shortcuts and its FilePicker, the Canvas's Cmd+K, the Arrange / Fit-view slot and the one-shot "centre this node" request. The unload warning stays per tab.

3.235.0 (2026-10-04) ​

Features ​

  • cloud: the sovereign-pool registry, reconciler and per-pool queue topology (P1b-i) (#2192) (2c2f8d5), references #2029

    whyP1b-i foundation (#2029, plan v2.1 D2'/D4'): one run stream + one DLQ stream + one durable consumer per sovereign pool, next to the shared default pair. Exact per-pool subjects mean a pool run cannot land on the shared stream and a stale default consumer cannot claim it; a pre-pool binary never rewrites a stream it does not know, so the mixed-fleet topology hazard shrinks to the shared pair.

3.234.1 (2026-10-04) ​

Bug Fixes ​

  • llmroute: two revi mediums — the escape reads the effective model, and the preview folds the run level (#2180) (3e6bcd4)

    whyladderEscapes mapped the node's DECLARED model while the materializing screen maps the EFFECTIVE one (the launch's model override wins): a node whose override mapped nothing kept a deleted park its run then needed — the escape now reads the override first, exactly as the materializer's nodeModel callback does (R27b623).

3.234.0 (2026-10-04) ​

Features ​

  • cloud: the sovereign runner-pool identity, end to end on the carriers (#2182) (041864a), references #2029

    whyP1a of #2029 (plan v2.1 D1'/D3', F2/F10): a team mapped to a runner pool gets that fact onto every carrier, and nothing routes around it.

3.233.1 (2026-10-04) ​

Bug Fixes ​

  • gate: one verdict-order authority per forge check; provision never silently reverts operator settings (#1588, #1590, #1632) (#2176) (2ac864d)

    whyEvery gate status — publish, GH/GL approve, the reconciler's synthetic — crosses a single per-(forge,repo,sha,context) verdict-order authority, anchored at the run's terminal instant, so a verdict decided after the run died always wins (#1590). A refused verdict is recorded on its grant and the reconciler answers the current head with the refusal diagnosis instead of deadlocking on relaunch (#1632). Provision reads BOTH operator-settings stores: launch_vars must agree or the adopt refuses with…
  • sandbox: answer "is this run sandboxed?" from the driver, not the mode (#1564) (#2167) (9ae7c4d), closes #2166

    whyWorkflowSandboxActive answered from the resolved MODE alone, so a sandbox: auto run degraded to the host (no driver) was read as sandboxed: its as: file secrets were never materialized and a codex --fallback stage was refused on a run executing on the host.

3.233.0 (2026-10-04) ​

Features ​

  • llmroute: the run level, the launch-time pair selection, and the screened program rewrite (#2126 slice 3) (#2172) (a316112)

    whyThe launch-time selection materializes as a computed run-fallback LADDER traveling the operator's --fallback path end to end — the publish serializes source, the runner re-compiles and re-applies the screen, so a publisher-side IR rewrite would be invisible to everything downstream.

3.232.1 (2026-10-04) ​

Bug Fixes ​

  • bundle: promote root-level sibling entries, bound the upward bundle search at the repository (#1367) (#2169) (7eb84fd)

    whyA root-level sibling .bot opened as a file (iterion validate bots/golden-master/extend.bot — the bundle README's documented invocation) compiled bare: no manifest, no skills, no C250/C252 — the floor in the manifest simply did not apply. And a loose .bot could be attributed to any marked ancestor above its repository (a ~/.claude carrying a skills/ directory took the floor of a file under an unbundled repo).

3.232.0 (2026-10-04) ​

Features ​

  • llmroute: the shipped-bot and binding levels of the adaptive-routing policy (#2126 slice 2) (#2164) (5c8c345)

    whyThe bot manifest gains the author's `routing:` block — declared as a manifest-local RoutingSpec following the RetrySpec convention, so the manifest's YAML surface stays independent of the shared struct's evolution — validated beside retry at decode: a typo fails the load, not a run days later. The binding level rides the launch-surface provisioning records, mirroring each surface's REAL retry coverage: the schedule row (full write path: create validates, update uses the platform's RawMessage…

3.231.0 (2026-10-03) ​

Features ​

  • llmroute: the adaptive-routing policy record, its fold, and the platform level (#2126 slice 1, #1999) (#2159) (f21e902)

    whyThe adaptive-routing contract (ADR-121) lands as pkg/llmroute: a pure policy record resolving retrypolicy-style, field by field, first setter wins, with the provenance map answering "which level decided this". The lock vetoes the setters ABOVE its level and the field is decided at or below it — the author's lock stops the binding, the platform's lock binds every tenant-configurable level — pinning the default under a "<level>_lock" provenance. strict is monotone where nobody locked it (any…

Bug Fixes ​

  • model: a vendor default no credential can serve is refused up front (#2113) (50fe7b8)

    whyD6-R2: when an element's model falls through to a hard-coded vendor default, the dispatch is refused up front if no credential can serve that vendor's wire — a 401 loop with the knob invisible becomes a named, terminal refusal the chain can walk on from. The anthropicFunding probe reads exactly what the anthropic factory reads: the run's ctx keys and forfaits, the env pair (AUTH_TOKEN gated off on a z.ai/bigmodel base URL), and this host's Claude subscription under the factory's own gates.

3.230.0 (2026-10-03) ​

Features ​

  • bots: prod-watch — the grafana-alerts lane (#2138) (#2152) (2bd8c97)

    whyThe Alertmanager built into the configured Grafana is polled — ONE GET of the alerts firing right now; the rules stay authored in Grafana's own UI and Argus aggregates (complement of the direct lanes, not a replacement).

3.229.0 (2026-10-03) ​

Features ​

  • models: the gateway catalog — operator table, exact models.dev lookup, offline snapshot (#2028) (#2111) (4e24ee1)

    why(Rebuilt by content onto the current main; the original commit's message and trailers govern — see 273fedc on feat/gateway-catalog.)

3.228.0 (2026-10-03) ​

Features ​

  • backends: an OpenAI-compatible gateway served from the environment (#2028) (#2102) (7219fa6)

    why`openai_compatible/<gateway model id>` names a model an OpenAI-compatible gateway serves — a LiteLLM router, a vLLM farm — with the endpoint taken from the deployment's own environment: OPENAI_COMPATIBLE_BASE_URL names it, OPENAI_COMPATIBLE_API_KEY authenticates it. The gateway id reaches the wire verbatim; routing identity, metering and refusals hold end to end.

3.227.1 (2026-10-03) ​

Bug Fixes ​

  • backends: grok's effort dial served by the studio picker; retry classifier stops trusting free-form stderr (#1642, #1645) (#2145) (7696c88)

    whyThe studio picker 400ed on grok while C177 stays silent on a grok route carrying reasoning_effort. New static arm mirrors what grokMapEffort passes verbatim; ir.EffortDialBackends lets a parity test assert every compiler-credited backend is served (kimi stays the 400 control).
  • model: env text widens when it resolves to nothing here; the gateway skip answers on the raw prefix (#2121) (#2132) (07de867), references #2109

    whyTwo narrowing defects of the #2109 gateway classification, found by the revi gate:
  • studio,bots: pin en-US on locale-free toLocaleString; docs-refresh defaults gain AGENTS.md (#1562, #2079) (#2146) (6149512), references #2071

    whyStudio already pins en-US in lib/format.ts for deterministic output; the 17 bare calls were the outliers and made task studio:check red on any non-en host. CadenceCard's next-fire render routes through formatDateTime like the Schedules view.
  • test: deflake four load-sensitive CI tests — pisdk group sweep, join budgets, store sync (#1646, #1981, #1982, #2125) (#2144) (1b4119e)

    whyA grandchild of pi (a stdio MCP server in its process group) kept writing into t.TempDir() after a graceful Close, racing the test cleanup. Close now kills and joins the group on every path, not just the kill-timeout one, with a /proc start-time guard so a recycled pid's group is never touched.

3.227.0 (2026-10-02) ​

Features ​

  • runtime: a resume restores the scratch its run banked at teardown (#1972) (e71a2a0)

    whyNodes keep working state under ${PROJECT_SCRATCH_DIR}: the verify.sh an agent writes and a later gate runs (eleven catalog bots), a measurement a later node reads, source clones kept out of the judged tree. On kubernetes the scratch lives in the container and dies with it, a resume always starts a new pod, and at park only the workspace travels, as commits. Measured: an assessment run wrote its floor measurement, parked, resumed on a new pod and died MEASUREMENT_REFUSED on the file it had…

3.226.0 (2026-10-02) ​

Features ​

  • bots: prod-watch — the ledgers rotate past a bound (#2064) (#2129) (a6cb80d), references 2093/#2112

    whyalertlog.jsonl and ticks.jsonl grew for ever (a flooded fold note's line carries ~3 KB; ~50 MB a year in the tree AND in the ops repo's git history). A file past ledger.max_bytes (2 MB, 0 = off) is abandoned under a generation name (alertlog-<gen>.jsonl) and a fresh one starts the same tick; the newest ledger.keep (8, 0 = all) rotations stay in the working tree, older ones leave it — git history keeps every version, the pruning only untracks.

3.225.1 (2026-10-02) ​

Bug Fixes ​

  • backends: claw honors provider: "anthropic" and fires settings hooks when sandboxed (#1715, #1718, #1713) (#2115) (52ec25a)

    why#1718 (confirmed, fixed): ClawBackend.Execute resolved its model through the registry without ever stamping task.ProviderHint, so a node pinned to provider: "anthropic" spent on z.ai whenever ZAI_API_KEY was set and no direct Anthropic credential was around — invisibly, until an error happened to carry z.ai's error code. The hint is now stamped at Execute (re-stamped in-container from the IOTask copy) and ResolveWithContext routes anthropic+hint through resolveAnthropicDirect: skips the z.ai…

3.225.0 (2026-10-02) ​

Features ​

  • bots: the six-verb navigation spine — manifest category + tags, grouped views everywhere (#1520) (fbfabb9)

    whyAdversarial-Rounds: 1 (plan review) Adversarial-Model: claude-opus-5 (via cswap, read-only)

Bug Fixes ​

  • ci: authoring saves tolerate a .git Git itself refuses; raw fsnotify .Add/.AddWith pinned by a typed inventory (#2048, #2082) (#2118) (1c2cf9f)

    why#2048: authoring/journal tests flaked under host load, never the same one — root cause characterized: validateAuthoringGitExclusion walks ancestors Lstat-ing .git, and on a loaded multi-session host the shared temp root can hold a .git Git refuses (empty scratch init, dangling gitfile from a racing worktree add/remove). git ls-files then exits 128 "not a git repository" and EVERY authoring save was refused — red bursts under load, green in isolation. When Git can see no repository, no file can…

3.224.1 (2026-10-02) ​

Bug Fixes ​

  • runtime: the mirror records what it writes, so a tracked .claude/ edit is banked instead of destroyed; every .claude writer refuses a symlink (#1571, #2060, #2061) (#2122) (75097df), closes #1364, references #1364 #1558 #2044 #1364

    why#1571: treenoise classified by path alone — a run whose only deliverable was a tracked file under .claude/ (a bot editing the repo's own tracked settings.json) saw runOutputPaths return nothing, the finalize log "nothing to bank", and the worktree removed --force: the deliverable destroyed silently. "tracked => work" was not the fix (the mirror rewrites .claude/skills/** at every run start — counting that would wip-bank every converged run, the #1364 failure). The mirror now records what it…

3.224.0 (2026-10-02) ​

Features ​

  • delegate: claude_code sessions honour their background work (#1936) (#2091) (b90eb5d), references #2017 #2032 #1940 #1940

    whyA claude_code session stays open until the CLI itself says its background work is back and delivered, under per-wave and turn-source budgets that ask the agent for its report rather than cut it; a session ledger tells whoever resumes a transcript what its earlier process lost. Found along the way and fixed: secret values reaching the model or persisting outside the run — tool outputs back to placeholders, inputs iterion keeps in placeholder form, reads of the CLI's task outputs, scrubbed…

Bug Fixes ​

  • runtime,revi: fan-out aggregate keeps the root-cause branch error; review workspaces report anomalies instead of phantom findings (#1669, #1666) (#2117) (4ec5db5)

    why#1669: on the merge queue, a fan-out whose PauseRun store failed ended with an error naming the sibling's "run cancelled: context canceled" — the root cause invisible to the operator, the test intermittent on collection order. processConvergence now falls back to a *RuntimeError{EXECUTION_FAILED, Cause: root} when rootCauseBranchErr finds a branch that failed by itself (first by branch-id, order-independent), and classifiesBranchErr only attaches errors with no classification of their own —…

3.223.0 (2026-10-02) ​

Features ​

  • budget: a call whose usage the provider never reported is counted, not free (#2028) (#2094) (407962f)

    whyA claw call whose usage the provider did not report in full — a stream cut before its final account, a provider that sends no usage (a custom OPENAI_BASE_URL is not asked for it), a request the cold-stream watchdog abandoned unanswered — used to be booked as its lower bound at best, as nothing at worst. Its count now travels with the spend and the ceilings say so.
  • credentials: a run whose routes are all env-funded acquires no LLM credential (#2038) (#2109) (e14a4ae)

    whyA run whose model routes are ALL `openai_compatible` — the gateway funded by the runner's environment — still walked every credential tier: BYOK keys resolved for every provider, the pool leased a donor, everything sealed. The bundle was already populated when the guards in `wantsFor` and `spendableProviders` ran, and it shaped the review topology downstream.

Bug Fixes ​

  • bots: prod-watch — the Loki lane joins the minting-lane store cut (#2057) (#2112) (4f6f2f3), references #2042

    whyLog lines carry user input, and `template()` mints one template per distinct value: the Loki lane's records grew without bound (the same defect #2042 fixed for the Sentry lane — ~134k records at the defaults, past GitHub's 100 MB state-file limit).

3.222.0 (2026-10-02) ​

Features ​

  • golden-master: JSON request bodies and header-borne anti-forgery tokens (#2066) (0c0f7ff)

    whyThe harness could only send forms, so no corpus entry could observe a route that reads a JSON body: the request left urlencoded or empty, and the reference recorded the refusal of a request nobody declared. The token was read from an `<input>` only, through a 300-character window that could return a neighbouring tag's value, sent as a form field only, and on the write lane only: an http-lane POST never received it.

3.221.1 (2026-10-01) ​

Bug Fixes ​

  • netproxy: model requests keep their placeholders, inspected requests stay on their tunnel, plain HTTP is inspected (#2031) (9abcadd)

    whyThe sandbox egress proxy's inspection now tells model API traffic from tool traffic, and applies one inspection to every request it forwards:

3.221.0 (2026-10-01) ​

Features ​

  • backends: ambient_context — what a node inherits besides its prompt, per policy, on every backend (ADR-119) (#2101) (788f3d7)

    whyambient_context: none | workspace | operator | all — on an agent/judge node or the workflow, resolved run override > node > workflow > ITERION_AMBIENT_CONTEXT > workspace (ADR-119). The zero value is the default, and a resolved policy rides every delegate.Task.
  • credentials: facade_default auto spans the run's tiers; tier keeps the per-tier rule (#1998) (#2096) (35f80c6), references #1956 #1956

    whyAfter #1956, `facade_default: auto` decided per tier: a team whose own Claude forfait was closed, or an org's, fell through to a later tier's z.ai key whenever that tier held no Claude credential — and the team's unpinned claude nodes were served GLM in silence under the claude label. `auto` now answers for the RUN: a Claude-native credential held by ANY tier of the run, in any window state (sealed, or skipped because closed), keeps every tier's facade key off the wire's default, and the run…

3.220.2 (2026-10-01) ​

Bug Fixes ​

  • server,fswatch: ghost team spellings refused on mutating routes; inotify watch refusals enriched (#2076) (c3e9d1b), closes #2046 #1554, references #1931 #2043 pre-#2046

    why#1931 canonicalized resolvable /api/teams/{id} spellings to the team UUID but deliberately left unresolvable ones their old semantics: a super-admin passes canManageTeam and a mutating call wrote rows keyed by the ghost spelling, invisible to every UUID reader. canonicalizeTeamPathValue now reports whether {id} resolved; a state-changing call with a spelling that resolves to nothing gets a 404 naming it — only for callers who pass canManageTeam, so members keep their uniform 403 and the refusal…

3.220.1 (2026-10-01) ​

Bug Fixes ​

  • bots: prod-watch — bound the Sentry incident store; min_level decides NEW only (#2042) (#2093) (d89267e), references #2057

    whyAnyone holding a public DSN mints Sentry issues, and every issue the lane read became a record kept for forget_after_days: a sustained flood grew state.json past GitHub's 100 MB file limit (~134k records at the defaults).
  • merge-gate: defer a verdict the forge refuses for a while (#2002) (#2092) (7d5ee02)

    whyLot 3 of #2002. On 2026-09-30 the App installation's REST budget ran out every hour, and a merge-gate verdict the forge refused for the limit was dropped: once the limit lifted, the run's silence read as a death — the reconciler answered the head with a synthetic "review died" failure (23 that day) and the relaunch lane paid for a second review of a revision already judged (17) — while a relaunch still launching was escalated as dead (6 false escalations).

3.220.0 (2026-10-01) ​

Features ​

  • cloud: SeaweedFS as the object store of the dev stack, its S3 bench, ADR-107; the store fixes the prod cutover surfaced (#1943) (#2089) (32ff141)

    whyMinIO no longer distributes its community edition (quay.io 401, the Docker Hub repository gone, dl.min.io 410, the repository archived), so `task cloud:up` could no longer pull its image. Production moves to SeaweedFS (SocialGouv/infra-apps, #1943); the dev stack follows it.

Bug Fixes ​

  • dsl: a broken inline list keeps the property after it; the root cut refuses the filesystem root loudly (#2074) (fc16f4b), closes #1630 #2047

    whyA bracket list broken across lines whose closer dedents (the YAML-habit shape: `tools: [bash,\n]`) used to hand the DEDENT to the enclosing block, closing it; the orphan `]` errored at top level and the next property was reparsed there and lost. parseBracketElems now resyncs on the line-end give-up arm: scan ahead (PeekAt only), skip junk continuation at a deeper column, land on the sibling property at the list's own column. Two verified bail shapes keep main's recovery byte-identical where…
  • runtime,treenoise: the .claude-symlink refusal covers the plugin and single-skill mirrors too; doc the remedy (#2068) (881bf63), closes #2044 #2045 2060/#2061, references #1569 #2060 #2061

    why#1569 added refuseAClaudeSymlink for the run-start bundle mirror, but three sibling writers could still write through a workspace .claude symlink (a link planted mid-run, caught only at the next start/resume):

3.219.5 (2026-10-01) ​

Bug Fixes ​

  • sandbox: authority comes only from the iterion home the operator chose (#2063) (#2073) (60c1bc5), references #2008 #2041

    whyFollow-up of #2008 and #2041. Three places still let a location the operator did not choose carry the operator's authority, and the review found two more of the same class.

3.219.4 (2026-10-01) ​

Bug Fixes ​

  • security: a run's forge publish grant stays on the server, and a Layer-0 secret resolves nowhere (#1997) (#2053) (f082ca8)

    whyThe server mints a short-lived forge publish grant per run so a review bot posts through the server's live forge client instead of a workspace token. That grant rode the run's launch vars as a plain value: it was readable on the run's read surfaces, it reached the records of every descendant run, and a workflow could turn it back into plaintext by declaring a secret over the var. Three changes, each with its own guarantee.

3.219.3 (2026-10-01) ​

Bug Fixes ​

  • model: one routing identity from spec to wire, meter and ledger (#2028) (#2065) (a3613d8)

    whyA model spec is `provider/model`, and its prefix was stripped twice: once by iterion's callers, again inside claw's OpenAI provider. A model id with its own slash lost part of it on the wire. The meter had the mirror problem: it re-derived a route from whatever id the backend reported, so pi on OpenRouter, which reports "anthropic/claude-sonnet-4.5", was charged to the Anthropic credential.

3.219.2 (2026-10-01) ​

Bug Fixes ​

  • delegate: pin claude_code request routing in the flag settings layer (#2059) (94ff16b)

    whyThe Claude Code CLI copies the env block of every settings source it loads over its own environment, at startup and again whenever a settings file changes. claude_code spawns load the project scope from the task's workdir, which is the target repository, so the repository's committed .claude/settings.json could set the variables that route the CLI's requests (the API endpoint, a proxy, a provider switch, a CA) and point the run's requests, and the API key or subscription token they carry, at…
  • merge-gate: forget settled gating runs, record the verdict on its grant (#2002) (#2056) (4b9f215), references #2034

    whyLot 2 of #2002. With the sweep elected (lot 1, #2034), what the one sweeping replica still spends goes on runs with nothing left to repair: over one busy repo's eight-day window, 95 % of the gating runs sat on a merged or closed pull request, re-read every minute for an hour and on every deep pass for 192 h — 440–880 forge reads per run. Measured in prod after lot 1: the elected sweeper alone sent 1 130 and 1 618 REST requests in the hours ending 2026-10-01T07:00Z and 08:00Z, of the fleet's ~1…
  • test: the Go suite stops leaking into the operator's machine (#2016) (#2041) (c985084)

    whyMeasured on one developer machine: 130 966 test run stores in ~/.iterion/projects, a throw-away engine workdir per test (10 970 in three days), a 126 MB e2e binary per run, the desktop project registry rewritten by 49 tests, claude CLI backups written into package directories.

3.219.1 (2026-10-01) ​

Bug Fixes ​

  • bots: gates that cannot lie — vetty refusals, Revi gate pin, Endy skipped-suite (#2009) (f407a66), references #1633 #1635 #1636 #1711 #1598

    why#1633's scenario end to end: pass 1 arms, the forge moves the PR into the merge queue (autoMergeRequest goes null), a later pass holds. The refusal path already stands the queue entry down (0dc863906), but no witness drove that exact path — the existing dequeue case reaches it through the unaudited-head refusal, not the verdict refusal the ticket describes. This pins it: hold + queued PR -> dequeuePullRequest on the wire, and the reason says the PR was removed from the merge queue.
  • runtime,worktreepool: .claude symlink refused at mirror time; one -z porcelain parser (#2027) (a05cfba), references #1569 #1569 #1577

    whyA workspace whose .claude is a symlink to another top-level directory had the run-start mirror write THROUGH the link: the files landed under the link's target, a path no tree-noise entry names, and the wip bank staged them as the run's work. The mirror now stops before the first write with a typed claudeSymlinkError naming the link, the resolved target and the remedy. The pre-existing pin that ACCEPTED an in-workspace link is flipped to pin the refusal (refs #1569, candidates weighed in the…
  • server: team {id} canonicalized to UUID at route resolution; no host path in launch/validate/example bodies (#2043) (81d9887), references #1970 #1970 #1931 #1931 #1970 #1970 #1970 #1970 #1931 #1931 #1970

    whyFollow-up of #1934: the under-root absolute-path cut now covers the remaining client-visible surfaces — compileForLaunch refusals (parse, missing include, bundle prompts, MCP prepare), marshalIRFromSpec (launch and resume), serveDiskExample's flat include error, and handleValidate's bundle-prompts 422. The cut lives in pkg/dsl/unit (RelDiagnostics/RelError/RelText/RelName); pkg/server's relUnitDiagnostics delegates to it, one rule for every boundary.

3.219.0 (2026-10-01) ​

Features ​

  • bots: prod-watch Sentry lane — new, regressed and escalating issues through the deterministic chassis (#1702) (#1986) (8ab89a7), references #1698 #1704

    whyThe Sentry lane of Argus (bots/prod-watch, #1702): one project and environment of a Sentry instance watched through the same deterministic, zero-LLM chassis as the Loki / Prometheus / probe lanes — redaction before anything is derived, the incident lifecycle, webhook delivery, git-backed state. Config-disabled by default (`sentry` absent or null).

3.218.6 (2026-09-30) ​

Bug Fixes ​

  • merge-gate: elect the gate sweeper — one replica sweeps, not ten (#2002) (#2034) (c502eb4), references #1995

    whyThe merge-gate sweeper ran unelected on every server replica. At the HPA's ten replicas its re-offers — a forge read each — exhausted the GitHub App installation's hourly REST budget 31–43 min after every reset on 2026-09-30: verdicts left unposted, synthetic "review died" failures, paid relaunches (diagnosis in #1995). This is lot 1 of #2002: the election only; what the gate decides does not change.

3.218.5 (2026-09-30) ​

Bug Fixes ​

  • platformcfg: a stored bot var never sets a name the platform keeps for itself (#1655) (#2032) (9c7787c), references #2024 #2025 #2019

    whyThe bot-var deny list named a few enforcement switches and missed the class. Every ITERION_ name the engine reads is now classified from what it does, under one rule written above botVarsInfraExact: a bot var tunes how a bot's runs behave, never a name that lifts or weakens a guard (security, spend and quota ceilings, bounds on untrusted input, workspace safety), configures the process or the fleet, names an outside endpoint or identity, or is written by the engine for a child process.

3.218.4 (2026-09-30) ​

Bug Fixes ​

  • forge: a rate-limited answer is a wait, not a missing grant (#1994) (#2021) (1fd6315)

    whyGitHub answers its API rate limits 403 (primary: X-RateLimit-Remaining 0; secondary: Retry-After), and the transport mapped every 403 to ErrForbidden: the merge gate reported "forge: insufficient scope" on a rate limit, and an App status write noted the statuses grant as withheld for the rest of the token's life.

3.218.3 (2026-09-30) ​

Bug Fixes ​

  • credentials: a key the run did not bind names no credential of it (#1996) (#2019) (086fb85)

    whyA sandboxed claude_code session on the pod's ambient Anthropic key got that key forwarded explicitly into its env, so its source label read "anthropic-direct": the usage meter charged its readings to the bundle's head credential (a healthy z.ai key, benched by a refusal it never saw), while the runner's pre-flight, composed host-side, read "anthropic-env" — the two disagreed. A node's own ExtraEnv key or config dir was mislabelled the same way, on the host too.

3.218.2 (2026-09-30) ​

Bug Fixes ​

  • delegate: run claude_code subagents in the foreground on every spawn (#2017) (fcc9e03), references #1953 #2 #1 1/#2 #N #N #N #2 #N #1 #2

    whyA documentation-campaign node on the claude_code backend (CLI 2.1.280, claude-opus-5-5, reasoning effort max, so not ultracode) lost its auditors' findings on three of the four passes of one run:
  • sandbox: the launcher starts only the operator's MCP servers on a sandboxed claw node (#1945) (#2008) (d359758)

    whyA sandboxed claw node advertises its resolved tools to the in-container runner, and the launcher executes every call the runner forwards. Where a tool may execute is now an explicit, exhaustive classification (tool.SandboxPlacementOf), and the launcher enforces it:

3.218.1 (2026-09-30) ​

Bug Fixes ​

  • runner: the run's lease outlives its engine's teardown (#1991) (b133e76)

    whyThe heartbeat that refreshes a run's NATS lease ran on the run's context and stopped at its cancellation. A cancelled run — a drain, an operator's cancel, an interruption — still unwinds after that: the engine exports the workspace (up to 5 minutes) and banks the scratch (up to 5 more), past the 60-second lease. A sibling that received the redelivery then took the lock and resumed from a timeline that teardown had not finished writing: a resume that ran without the scratch its predecessor was…

3.218.0 (2026-09-30) ​

Features ​

  • credentials: shared tiers spend forfaits first, route- and path-aware (#1956) (298f1e9)

    whyA shared tier (org, platform) holding a forfait and an API key on one wire family now gives the family to the forfait; the key funds only the routes that name its provider. One policy snapshot per resolution orders it:

3.217.5 (2026-09-30) ​

Bug Fixes ​

  • runner: a resume refused before its claim goes back to the status it came from (#1988) (0958eb6)

    whyA cloud resume flips the run to queued before it is published. When it is refused before anything claims the run — by the engine (RESUME_INVALID: a copy-based subbot child resumed on its own, an incompatible artifact contract; LAUNCH_FAILED: admission denied) or by the runner itself (IR_UNLOADABLE, BOT_REQUIRES_NEWER_ENGINE) — the verdict is deterministic: the runner acks it and never redelivers. The engine's refusals wrote no status, so the run sat queued until the orphan sweeper, past the…

3.217.4 (2026-09-30) ​

Bug Fixes ​

  • runtime: a review gate resumes from the cloud's queued pre-flip (#1984) (68dd3d3)

    whyThe cloud publisher flips a paused run to `queued` before its resume reaches a runner, and Resume's queued case routes a pending interaction to the pause path. Three of the pause path's claims accepted `queued`; the review gate's claimed from `paused_waiting_human` alone. Every cloud resume of a review gate (approve_merge, request_changes, a reply) was therefore refused as a "duplicate resume" — a plain error the runner naks, redelivered until the DLQ parked the run.
  • runtime: a wait_all failure quotes the branch that failed, not the sibling it cancelled (#1990) (106678a)

    whyA branch whose failure cancels its siblings is the cause of a wait_all failure; the cancelled siblings carry no verdict of their own. The code and the cause of the aggregate already skipped them (stoppedBranch), but the message quoted the first failed branch by id: when the failing branch sorted after a sibling it cancelled, the run's failure read "run cancelled: context canceled". The spent-budget refusal it quotes and the undecided effect it carries were picked in completion order.

3.217.3 (2026-09-30) ​

Bug Fixes ​

  • runner: a lent slot follows the worker's re-stamp, and stops only at the donor's re-connect (#1976) (bac754f), references #1958

    whyA pool-lent slot was held to the fingerprint it was sealed with. The refresh worker re-stamps a record's fingerprint on its own rotations — an unstamped record stamped, a subscription identified as an account, an account demoted to a local meter — so on such a rotation the lent follow refused the record and ended, at the moment the worker's exchange revoked the token the borrower held: its next spawn failed "OAuth access token has been revoked". Reachable for any pledged record connected…

3.217.2 (2026-09-30) ​

Bug Fixes ​

  • instrument: lessons from the graal campaign — redaction-net review, a verified capture helper, no docker in verify.sh (#1929) (27b7cd5)

    whyLessons from the graal observability campaign, landed in the instrument (Obsy) chassis and its ten verify-build siblings.
  • tool-nodes: python runs isolated in every tool node (#1968) (df7eb09)

    whyA tool node runs with the workspace as its working directory. `python3 -c` (and -m, and stdin) puts that directory first on sys.path, as python puts a script file's own directory, and the engine lands a `language: py` script in the workspace under a copy-based sandbox. A json.py, hashlib.py or subprocess.py at the root of the judged tree, git-ignored or not, replaced the standard module inside the node: a gate whose verdict the tree it judges writes (shown on a docs gate, where a forged corpus…

3.217.1 (2026-09-30) ​

Bug Fixes ​

  • runner: one refresher per OAuth forfait record, the runner follows it (#1958) (c1cfa10)

    whyA claude_code forfait is a store record the server's OAuthRefreshWorker rotates, and the runner also exchanged the refresh token of its run-local copy (refreshAnthropicLoop). An exchange revokes the access token every other holder of the grant still uses. Measured on a team record: a live delegate refused "OAuth access token has been revoked" 16 s after the worker rotated the record.
  • server: cut the unit root off every diagnostic a client is answered (#1934) (#1971) (5e4c9c1), references #1933

    why#1933 cut the root off the claim probe's diagnostics, the one place a probe-forwarded diagnostic crosses. The same under-root leak sat at four sibling sites, each forwarding a DISK load's diagnostics — positioned under absolute workspace paths — to the client:

3.217.0 (2026-09-30) ​

Features ​

  • review-pr: opus 5.5 on the Claude forfait, GLM 5.3 rescue, labels from the serving wire (#1946) (85747ae), references #1924 #1939

    whyreview-pr 0.9.12 / revi-converse 0.1.5. The claude slot (both tiers), converge and converse_agent run claude_code + claude-opus-5-5 pinned to `provider: anthropic` through dials (ITERION_VIBE_PROVIDER_CLAUDE / _EMIT), with a metered GLM route (provider zai, ITERION_VIBE_MODEL_CLAUDE_FALLBACK, default glm-5.3) on usage_window/auth/unavailable. Unpinned, a claude id reached the z.ai facade whenever a z.ai key held the wire and was served GLM in silence (#1924). The GPT slot runs claw…

3.216.0 (2026-09-29) ​

Features ​

  • product-docs: the route checks judge by the net route table, resolved once at the run base (#1951) (9b25ef5)

    whygolden-master states its routes through config.json `routes_probe`, a command it replays at every gate, and commits no artifact for them. The docs gates read only a committed <net>/routes.txt, so on a net built the usual way the route checks ran DEGRADED with zero declared routes: the "every declared route is cited" rule and the map's grounding had nothing to hold the documentation to. Committing the table instead would go stale the moment a change adds a route, and the gates would stop…

3.215.0 (2026-09-29) ​

Features ​

  • dsl: type-consistency for whole-ref with: mappings, routing fields, and constrained env defaults (#1604 #1605 #1610) (#1926) (4fd951a), references #1285 #1320 #1389 #1921 pre-#1915 #1915 #1921 #1921 #1921 #1921 #1919

    whyAn edge with: mapping that is exactly one {{vars.<name>}} reference passes the value through with its type, and since #1285 a string[]/json var IS a list on the default path as much as on the override one — so the list (or object) arrives WHOLE on a field declared string, compiles clean, and nothing checks a with: value's type at run time: the arity simply moves (a tool whose command reads the field executes once per element).

3.214.1 (2026-09-29) ​

Bug Fixes ​

  • delegate: retry a forfait token renewed under a running CLI, do not bench it (#1960) (6f7fb16)

    whyA claude_code CLI holds the forfait access token it was spawned with for its whole life. When the store's refresh worker rotates the record, the provider revokes that token, and the runner writes the rotation into the forfait file. Measured: a live delegate refused "OAuth access token has been revoked" 16 s after the worker rotated its team record. The delegate read that render as a dead credential: ErrAuthFailed paused the run for a human re-auth, and the auth evidence it filed benched a…

3.214.0 (2026-09-29) ​

Features ​

  • dsl: the writer's multi-line forms, E055 separator notice, surgical WriteManifest (#1612 #1663 #1349) (#1919) (928271d)

    whyA strict render folded every value holding a newline onto one escaped "…\n…" line — 470 504 characters on bots/golden-master/main.bot — which pkg/dsl/canon then refused by name, leaving 40 shipped files unformattable (.fmt-refused). The writer now gives a value holding a newline the `key: |` block scalar form when the reader gives it back exactly (ends with a newline, no leading blank line, no whitespace-only line, no later line de-denting below the first, no carriage return), and the backtick…

3.213.0 (2026-09-29) ​

Features ​

  • dsl: C183 — enum membership is compile-checked for literal values (#1916) (#1928) (b680e37), references #1525 #1915

    whyA compute field declared `string [enum: ...]` fed by a static string literal outside the enum (`s: "'zzz'"`) used to compile clean and fail SCHEMA_VALIDATION at the node — the enum arm of checkFieldType (pkg/backend/model/validate.go). C183 names it at compile time, as a warning per the prefer-warn stance: a scalar literal is held against a `string` field's enum, an all-string list literal against a `string[]` field's per-element enum; anything the compiler cannot fully evaluate stays silent,…

3.212.2 (2026-09-29) ​

Bug Fixes ​

  • delegate: ultracode nodes collect their background subagents (#1953) (34fe9e0)

    whyThe ultracode instruction grants standing consent to fan work out across parallel subagents, and says nothing of the one rule a non-interactive session imposes on it: a subagent launched in the background reports only when the model waits on it, and nothing reaches the model once it produces its final output. Measured on a documentation campaign node, five passes out of five: seven subagents spawned in the background, no TaskOutput call, the model's last words "Waiting for the auditors now……
  • sandbox: a workspace export survives a writer racing its archive (#1952) (b080b18)

    whyExportWorkspace dropped the whole export on any non-zero exit of the in-pod tar. GNU tar exits 1 with "file changed as we read it" when a file changes while it is archived — a git process still finishing in the pod at park time — even though the archive is complete. Measured in a run parked on a usage window: "tar: ./.git: file changed as we read it … exit status 1", the export abandoned, the run's in-pod commits left invisible on the host.
  • server: answer a probe refusal with the unit's relative paths (#1918) (#1933) (29ec953)

    whyThe claim probe parses every file under its absolute path, and a claim it refuses is a 422 whose body is the diagnostic — disclosing the server's directory layout to any studio client. finishClaimed, the one place every probe-forwarded diagnostic crosses (save, cloud write-back and render alike), now cuts the unit's root off each diagnostic's position field and message text: every name a probe diagnostic can carry is Join(Root, Rel), so the cut answers both and no Name→Rel table maps a string…

3.212.1 (2026-09-29) ​

Bug Fixes ​

  • dsl: the launch fallback screen resolves a {{vars.x}} node backend with the run's vars (#1606) (#1921) (4805334)

    whyApplyRunFallback — the launch-time admission for an operator's --fallback / Studio Launch row — screened each route against the node's backend, but its signature carried no vars, so a `backend: "{{vars.x}}"` node read as undecided and the four C176 predicates (the tools: inversion, session continuity, the permission gate, the unresolvable-tools check) silently did not apply to it. The compile side is silent on that shape by design — a launch may override the var — which left it with no screen…
  • studio: derive the wire mode unions from the DSL registry (#1935) (#1939) (3e09df4), references #1930s

    whySessionMode, AwaitMode and InteractionMode in studio/src/api/types.ts were a third hand copy of the registry enums, drifted: AwaitMode carried a "none" the parser refuses and InteractionMode missed review/async/human_or_host.

3.212.0 (2026-09-29) ​

Features ​

  • models: reasoning_effort none — DSL, adapters and studio parity (#1837) (#1900) (4486221)

    whyGPT-6 Sol/Luna accept reasoning_effort "none" and the model backend already transmitted it on Responses when handed the value directly, but the authoring path and the adapters each dropped it at a different seam:

3.211.0 (2026-09-29) ​

Features ​

  • dsl: five compile diagnostics — tree-noise shell traps, duplicate preset keys, sync interaction on CLI backends, review-gate values (#1555 #1661 #1644 #1094) (#1925) (9dfab04)

    whyAn unknown posture/merge_strategy word used to read as the DEFAULT (human_required / squash): fail-safe for the tree, but a silent replacement of the author's explicit choice — the shape C142 refuses for worktree:. Refused at the choke point where the default is applied; the IR keeps the fail-safe value so a launch surface that ignores compile errors still gates on a human and still squashes.

3.210.0 (2026-09-29) ​

Features ​

  • assessment: a Java stack extractor — versions, deployables, HTTP entrypoints (#1920) (63175c6)

    whyThe bundle shipped Go and Node extractors; a Java repository measured DEGRADED — the HTTP surface hand-counted, the toolchain versions undeclared, the stack named as unmeasured. The assessment's own report names the debt: the primary stack sits where no extractor measures.

3.209.0 (2026-09-29) ​

Features ​

  • product-docs: a map step — the diagrams drawn once, before the campaign (#1914) (117ac07)

    whyPrody documents a product page by page but never draws the whole: a reader (and the campaign itself) had no single picture of what the product offers and how its screens belong together. The new `diagram` step draws it ONCE before the first pass — Mermaid pages under `<product_dir>/diagrams/`, screens and journeys a reader recognises, every box grounded on a source. The continuation loop re-enters at scan_hints, so later passes never redraw the map; from pass 2 on it is just pages the campaign…

3.208.0 (2026-09-29) ​

Features ​

  • dsl: list and object literals in expr: (#1525) (#1915) (9b0c1ce), references #1497

    whyThe typed-collection remedy (C152, PR #1497) had exactly one expressible form because the expression language had no list or object literal. The literal lands in both syntax profiles — it is additive, and a profile gates changes of MEANING, not forms that were parse errors:
  • product-docs: the gate demands every declared route be cited (#1912) (256a12f)

    whyThe POSS assessment's deployable declared a pattern that matched nothing in the Dockerfile it named — the agent wrote the pattern without reading the file. The survey skill now teaches: read the file, pick the line that identifies the service, write the pattern that matches it verbatim.
  • server: the per-file editor carries import and dsl: profile changes (#1680) (#1917) (569c536), references #1665

    whySince #1665 the studio's per-file Source view refused both edits by name: the save rebuilt every file's header (its import lines, its dsl: profile) from the STORED files, so an applied change was silently dropped — a removed import emptied the orphan fragment with a 200, an added one made every later save refuse, a profile change was never written while the declarations had been read under the new one.

3.207.0 (2026-09-28) ​

Features ​

  • backends: claw read_file extracts PDFs instead of returning mangled bytes (#1913) (fa9bc6d), references claw-code-go#11 claw#11

    whyA .pdf in the workspace reached the text-lines windowing as binary and came back mangled — on the one backend whose read tool has no native PDF path. When the file carries the %PDF magic, extract its text (claw-code-go's BT/ET scraper, newly exported as ExtractPDFText — pin bumped in the parent commit) and window THE TEXT with the same chunk semantics as a regular file: line retention, byte cap, continuation marker. A .pdf-named file without the magic reads as text (misnamed export); the 64 MiB…

3.206.0 (2026-09-28) ​

Features ​

  • bots: Doki reads product context — reference repos, tracker snapshot, per-run backend (3.6.0) (#1911) (9516682), references #1612

    whyUntil now docs-refresh grounded only in the cloned repo + scope_notes; enrichment for a PRODUCT (functional specs, online-help PDFs, the tracker board) had to be paraphrased by hand into the notes.

3.205.3 (2026-09-28) ​

Bug Fixes ​

  • assessment: declare the forge_token secret for the workspace clone (#1893) (db45b94)

    whyThe runner's repo workspace clone ran credential-less on any private repo: the launch --connection-id fills the run's forge_token only when the workflow declares the secret, and the assessment bot never did — the clone died before the survey could start. Same shape as the product-docs bot's forge block.

3.205.2 (2026-09-27) ​

Bug Fixes ​

  • author: the flow comment counts — one physical comment, one entry (#1892) (7819d88), references #1814

    whyThe last two items of #1814. author.Comments read a flow collection's comments off its source AND listed the copies yaml.v3 hung on the node's head, line and foot: `[a: # c` + a line break counted "# c" twice. And the old text-based dedupe swallowed a same-text comment written on ANOTHER line — the head's lines are now derived from the source (the contiguous block ending above the opening bracket, blanks skipped), and only a sourced read ON those lines is the head's copy.

3.205.1 (2026-09-27) ​

Bug Fixes ​

  • author: the author twin's remaining lows — raw folds, remedies that exist, one mistake one message (#1884) (ac2a192)

    whyEight of #1814's ten items, each with its witness:
  • runtime: the engine writes only what is its own — the wip bank proves its worktree, the finish hook carries the run's context, no test mirror lands in the checkout (#1889) (3e2ede4), references #1805

    whyThree tickets, one seam: nothing the engine writes may land in a repository or a directory that is not the run's.

3.205.0 (2026-09-27) ​

Features ​

  • assessment: the bot that writes the contract a campaign executes (#1776) (8c7a130)

    whyA modernisation campaign starts from a contract an operator writes by hand. This bundle is the step that writes it: survey the repository, measure it against a versioned profile, propose the programme, and emit `.modernize/plan.yaml` validated lot by lot.

3.204.2 (2026-09-27) ​

Bug Fixes ​

  • delegate: the claude_code model id reaches the wire bare — the facade trio's prefixes are stripped (#1885) (8b1f51e)

    why`teams switch <slug>` minted a PAT with the slug stored verbatim as the team pin. A PAT identity resolves its team on every use — GetMembership(user, "pic-graal") finds nothing — so the token answered 401 "token team unavailable" on every call, and the CLI (sharing one credential file with the MCP server) carried the break into every surface. Paid in production: two studio re-logins and a blocked phase.

3.204.1 (2026-09-27) ​

Bug Fixes ​

  • author: the document reads as it is written — stale floats, block scalars under words, a comment that ends a value (#1881) (c157dc3), references #1821 #1781 #1781 #1780

    whyAdversarial-Rounds: 2 (opus-5.5: 6 + 4 verified findings, all fixed) Adversarial-Model: claude-opus-5-5

3.204.0 (2026-09-27) ​

Features ​

  • product-docs: prove exhaustiveness in a gate, not on the agent's word (#1774) (c5f4dcb)

    whyPrody's convergence rested on `scope_ok ∧ lint_ok ∧ docs_aligned`, and `docs_aligned` is the agent's own answer to the one question it cannot honestly answer about itself: did I document EVERYTHING, and does everything I documented EXIST? Surface coverage was an advisory hint. The catalog's doctrine is that the truth lives in the gates.

3.203.2 (2026-09-27) ​

Bug Fixes ​

  • server,cli: a team-pinned token cannot be minted for a team that does not exist (#1880) (8efbe48)

    why`teams switch <slug>` minted a PAT with the slug stored verbatim as the team pin. A PAT identity resolves its team on every use — GetMembership(user, "pic-graal") finds nothing — so the token answered 401 "token team unavailable" on every call, and the CLI (sharing one credential file with the MCP server) carried the break into every surface. Paid in production: two studio re-logins and a blocked phase.

3.203.1 (2026-09-26) ​

Bug Fixes ​

  • launch: admission gives back what it takes; unknown inputs refuse, not drop (#1726 #1757) (#1872) (74f4035)

    whygateLaunchRate consumed a token from the org's per-minute launch bucket, but launchAdmission carried only the monthly usage key — so a client looping a malformed body emptied the bucket while every 400 handed the monthly unit back, and the FIRST well-formed launch was 429'd with nothing running (measured, #1726).

3.203.0 (2026-09-26) ​

Features ​

3.202.3 (2026-09-26) ​

Bug Fixes ​

  • server: the metering surfaces read the slot's fact off the error, not off err != nil (#1875) (6414f2a), closes #1725, references #1725

    whyThree sibling surfaces (the trigger spine, the board dispatcher, the retry sweeper) refunded the metered slot on ANY error out of Launch/Resume, on the strength of a comment saying "every error means no run started" — and the repo already knew that sentence was false (RunPersistedError, #1638's own aftermath). The ticket measured it: a publish that reports failure AFTER the message landed leaves a run the runner may claim, and the refund under-counts it.

3.202.2 (2026-09-26) ​

Bug Fixes ​

  • the .bot file is read and validated by one rule (#1728 #1808 #1762) (#1871) (2336861)

    whyA file with no workflow got valid:false and nothing else on the JSON path: the reason was the human renderer's alone. It now rides the result as a parse diagnostic naming the declared remedy — and a file that failed to PARSE carries only its parse error, never a second "no workflow found" counting one cause twice.

3.202.1 (2026-09-26) ​

Bug Fixes ​

  • mcp: the project-scoped engine server no longer squats the reserved iterion namespace (#1870) (c8e0d32)

    whyThe runner refuses any project-declared MCP server whose normalized tool namespace lands on iterion's own (a project server named `iterion*` could forge infrastructure tools — ask_user, board, control — past the permission gate), so the refusal is load-bearing and stays. The repo's own `.mcp.json` predates the check and has parked every PR review on the runner's DLQ since it deployed (10 entries since 2026-09-25 21:31 UTC, this repository's reviews included). The local engine server is renamed…

3.202.0 (2026-09-25) ​

Features ​

  • cloud: a pinned provider funds its own slot at the org and platform tiers (#1818) (b0ccd66), references #659 #736 #1859

    whyThe shared tiers fill one credential per WIRE FAMILY, so a tenant whose org or platform holds both an Anthropic and a Moonshot key was served anthropic alone — and a node pinned `provider: moonshot` was then refused, correctly, with its funding one row away. Measured on the platform tier before this:

3.201.0 (2026-09-25) ​

Features ​

  • server,cli,mcp,studio: a run is addressed by its id, across the teams you may see (8f509ad), closes #1847 #1848 #1407 #1849, references #1847 #1848

    whyfeat(server,cli,mcp,studio): a run is addressed by its id, across the teams you may see

3.200.3 (2026-09-25) ​

Bug Fixes ​

  • auth: refuse admin password reset for disabled accounts (#1863) (4c245ce), closes #1561, references #1862

    whyReturn an actionable 422 before generating a credential or changing the account. Preserve active/pending recovery and explicit re-enable semantics. Cover the HTTP journey and document the refusal; concurrent stale writes remain separately tracked in #1862.
  • claude-code: align task credential precedence across CLI passes (#1859) (d484906), closes #1836

    whyCompose ambient forwarding, task env additions and selected credential routes once for host/sandbox main and format spawns. Bind direct credentials to their route, protect resolver markers, and preserve inherited gateway fingerprints.

3.200.2 (2026-09-25) ​

Bug Fixes ​

  • mcp: reserve internal server names across catalog and CLI adapters (#1861) (d94706b), closes #1742

    whyReject user/project/plugin MCP names that normalize into the permission-exempt infrastructure namespace. Keep internal registrations and custom servers separate, with a shared identity check and actionable rename diagnostics.

3.200.1 (2026-09-25) ​

Bug Fixes ​

  • credpool: release only the lease owned by a launch grant (#1856) (b13f7ef), closes #1845

    whyBind cleanup to the lease inserted by Acquire so a losing launch or resume cannot close a concurrent acquisition for the same run. Keep the existing close CAS and consumed-run accounting semantics.

3.200.0 (2026-09-25) ​

Features ​

  • dsl: the author YAML twin — surfaces, part 2 (lot 5, PR C2) (10a8071), closes #1584 #1010

    whyfeat(dsl): the author YAML twin — surfaces, part 2 (lot 5, PR C2)

3.199.3 (2026-09-25) ​

Bug Fixes ​

  • cloud: include Node runtime library and verify bundled CLIs (#1853) (933b695), closes #1850

    whyNode 26 is copied into Debian slim without libatomic.so.1, causing exit 127 in the server image. Install libatomic1 and smoke-test Node, Claude and Codex under the final non-root user before publication.

3.199.2 (2026-09-25) ​

Bug Fixes ​

  • server,credpool,runview: a launch refuses a run id another run already uses, in any team (e827696), closes #1845 #1833, references #1833

    whyPOST /api/runs accepted a caller-supplied run_id and used it as-is. A launch naming an existing run's id — any team's — reached the credential pool, whose Acquire first supersedes every open lease of that run id through a team-blind query: the other team's leases were closed before the duplicate-id save failed, the donor's slot was freed and the charge erased while the run still used the credential. The launch's answer also differed on whether the id existed. (#1833)
  • server: the artifact version route gates on the run's team; ?store= is refused on a cloud instance (308def3), closes #1832

    whyGET /api/runs/{id}/artifacts/{node}/{version} read the artifact body with no team gate. In cloud mode the body comes from the blob store (artifacts/<run>/<node>/<version>.json), which knows no team, so the route served another team's artifact to any authenticated caller who knew the run id. The handler now loads the run under the caller's context first, as its siblings do.

3.199.1 (2026-09-25) ​

Bug Fixes ​

  • studio: reuse tabs within the active project (#1830) (1a0f846), references #1811

    whyOpening the same file or draft in another project now selects a tab that the current editor can display. Reuse follows the existing visibility scope, including the unscoped cloud mode.
  • studio: select a visible neighbour when closing tabs (#1840) (b357d97), closes #1829

    whyKeep the replacement active editor or run tab in the current project. Compute both the candidate list and the closed-tab index in that scope; retain global cloud selection when project scoping is disabled.

3.199.0 (2026-09-25) ​

Features ​

  • models: default bots to Opus 5.5 and GPT-6 (#1835) (62309a8), references #1831 #1837

    whyMigrate catalog and implicit defaults, preserve explicit overrides, and pin compatible CLI versions. Adapt structured/grounded generation to mandatory adaptive thinking and consume the published GPT-6 SDK support.

Bug Fixes ​

  • studio: expose typed schedule failures and recovery advice (#1825) (6b9d278), references #1565

    whyShow the last run outcome and separate launch refusals in schedule rows. Use the persisted failure code for run hints before parsing legacy text. Keep the configured cadence and the existing manual pause control.

3.198.1 (2026-09-25) ​

Bug Fixes ​

  • dsl: preserve literals and quoted reference lists when writing (#1822) (a276854), references #1810 #1736 #1624 #1809

    whyKeep decimal floats parseable and typed, quote non-bare reference list values through the active string profile, and refuse JSON integers that the author YAML reader cannot represent as int64.

3.198.0 (2026-09-25) ​

Features ​

  • moonshot: Kimi as a first-class provider on the Anthropic wire (#1744) (#1753) (76aeef2)

    whyMoonshot was reachable only through `backend: "kimi"` — the CLI-agent route (ADR-065), which resolves its own credentials from the host env and sits outside the credential store entirely: no BYOK record, no funding walk, no usage windows, no rotation. A campaign whose only Anthropic-wire providers are anthropic and z.ai freezes the day both are walled at once, and that is not hypothetical: it cost hours, with one key on a dated weekly window, one forfait revoked and one team slot…

Bug Fixes ​

  • runtime,bots: ${BUNDLE_SKILLS_DIR} is absolute or the run is refused, and a child never inherits its parent's copy (#1806) (96a6b16), references #1797 #1797

    whyA reader joins a skill NAME onto the directory, so an empty or relative value resolved against the node's own working directory — the checkout — and the data block came back from whatever the repository put at that path. The previous spelling carried a literal `/`, which could not collapse that way.

3.197.3 (2026-09-24) ​

Bug Fixes ​

  • studio: every async answer settles only what it was asked about (#1770) (#1793) (1f29d1c), closes #1650 #1662, references #1733 #1791 #1790 #1788 #1789

    whyEvery answer the editor waits for — an Open, an example, an import, a save, a validation, a reload, a Source-view Apply, the assistant's apply and reload, a draft following its conversation — now carries the identity it was asked about, and settles only that. A superseded answer (a newer request speaks for the author) is dropped quietly; an answer refused because the author worked meanwhile says so.

3.197.2 (2026-09-24) ​

Bug Fixes ​

  • runtime,bots: read iterion: skill data blocks from an engine-owned copy (#1797) (84b62b2)

    whyA tool node that parses a machine-readable `iterion:` block out of a skill read it from `<workspace>/.claude/skills/`. That directory applies the workspace-wins collision policy, and the workspace is a checkout of the repository under audit: the checkout could both replace a shipped skill and supply a name the bundle never ships, and either was read as the bundle's. Five bots parse such blocks (`scanners`, `heuristics`, `lockfiles`, `reattack`, `pkgmgr`) across eleven read sites, and one of…

3.197.1 (2026-09-23) ​

Bug Fixes ​

  • runtime: the backend seam's zero value is the refusal too — read at the worst case, named in the refusal, answered by the dry run (#1767) (#1777) (914afa3), references #1748 #1758 #1748

    why## What

3.197.0 (2026-09-23) ​

Features ​

3.196.2 (2026-09-23) ​

Bug Fixes ​

  • runtime: the zero value of the tool-surface seam is the refusal — pinned at compile time, read at the worst case, named in the refusal (#1748) (#1766) (8094583), references #1652 #1694 #1652

    why## What

3.196.1 (2026-09-23) ​

Bug Fixes ​

  • bots: a missing verify.sh is a refusal, at all eleven gate carriers (#1711) (fff7be0), closes #1707, references #1585 #1598 #1707 #1707

    whyThe deterministic half of every campaign gate re-runs an agent-written <scratch_dir>/verify.sh and reports the REAL exit code — the whole point being that the verdict is not an LLM judgment. When no script was produced, ten carriers answered `passed: true, skipped: true`; the comment they shipped said it aloud, "counted as pass, but surfaced". Nothing read `skipped`: e2e-coverage declares it in verify_result and its convergence expression never mentions it, so "the gate was skipped" reached the…

3.196.0 (2026-09-23) ​

Features ​

  • dsl: the author YAML twin — surfaces, part 1: admission, transport, validate, fmt (lot 5, PR C1) (#1720) (9db9ee3), references #1010 #1584 #1010 #1282 #1741 #1739

    whyThe author document — a `.bot.yaml`, the YAML twin of a `.bot` read by pkg/dsl/author — is a draft of a workflow, never one (lot 5 of #1010, the arbitrated surface: a way of writing the .bot, not a second truth). Before any surface reads one, every surface that would launch, upload, schedule, store or resolve one refuses it by name, with one typed sentinel (`bundle.ErrAuthorDocument`, errors.Is) and the remedy in the message (write the .bot it stands for — the fmt command that writes it names…

3.195.0 (2026-09-23) ​

Features ​

  • auto-maintenance: the recipe, and a preflight that reddens on a broken agreement (#1752) (906e477), references #1589 #1595 #1597 #1596 #1585 #1591 #1589 #1595

    whyAn unattended dependency loop rests on settings in three systems — the repository ruleset, the iterion integration, the App installations — and nothing type-checks the agreement between them. That is how the headline friction of this epic was born: a gate context renamed on one of two sites, with no repository able to see the disagreement.

3.194.1 (2026-09-23) ​

Bug Fixes ​

  • bots: every acting prompt in the catalogue says what it reads is data, per-pass scan directories, the deepsec coverage sum, and 14 typed literals bound (#1494 #1475 #1495 #1524) (#1739) (7ea7be1), references #1473 #1323 #1652 #1737 #1646 #1349 #1722 #1722 #1737

    whyFour tickets on one surface: the shipped bot catalogue, its prompts, its tests and its regenerated catalogue.
  • docs,repomap,repograph: the link checker judges what the site serves and asks git what github serves, code is not prose for the link scanners, one spelling for docs/public links, one skip set (#1618 #1619 #1620 #1621) (#1741) (728f172), references #1508 #1508 #1723 #1724

    whyFour tickets on one surface: the documentation link checker, the scanners that read a page's links, and the sets the generated artifacts walk.
  • runtime,server,backends: quota slots released only when nothing started, fork inputs gated, the gated formatting spawn bounded, and a declaration is not a bound (#1638 #1608 #1682 #1652) (#1729) (9ccc818), closes #1670, references #1672 #1615 #1651

    whyFour small hardenings, one per ticket, each on a surface where a bound the operator reads as enforced was not.

3.194.0 (2026-09-23) ​

Features ​

  • backends: a workspace .claude/commands/ command reaches a claw node (#1418 lot 1) (#1712) (aac4480)

    why## What

3.193.0 (2026-09-23) ​

Features ​

  • modernize: a lot's gate can reach a deployed application (#1693) (c31be5c)

    whyThe modernize bot declared no platform credential, so a contract could not declare an exit gate that judges a DEPLOYED application — only a locally started one. Such a gate refuses for want of an identity it has no way to be given, whatever the lot did, and that refusal says nothing about the work.

3.192.2 (2026-09-23) ​

Bug Fixes ​

  • dsl: a prompt written in place is read from a block scalar as the scanner read it (#1691) (bd8b60e), references #1584 #1664

    whyThe author document's three text readers — a string value, a prompt declaration, a prompt written in place of its reference (`system:`, `user:`, `instructions:`) — must read a block scalar as yaml.v3's scanner read it: a line separator (U+2028/U+2029) it left in a literal block is the line break it meant, said by a warning at its line; a folded block holding one is refused. Two of the three did; the third took the scalar raw, so two authored lines of a `user: |` reached the model joined by an…

3.192.1 (2026-09-23) ​

Bug Fixes ​

  • model: a failing tool node leaves its output on the event, not only in the log (#1692) (8b67cd3)

    whyA tool node's completion event carried its error and nothing else. On a failure that meant one string — `exit status 1` — while the command's own stdout and stderr went to the run log, a different sink, under a hook an operator auditing the failure does not read.

3.192.0 (2026-09-23) ​

Features ​

  • dsl: the author YAML twin — converter, proof and measure (lot 5, PR B) (#1664) (83bc0f8), references #1584 #1584 #1010 #1010 #1584 #1094 #1584 #1094 #1584 #1624 #1627 #1628 #1628 #1110 #1110 #1631 #1656 #1671 #1671

    why`servers: ["forge"]`, `on: ["auth"]`, `watches: ["a"]`, `needs: ["gpu"]` read as EMPTY lists, `tools: [1, bash]` as `[bash]`: parseIdentList and parseToolRef consumed a token that could not open a name and reported nothing, so the server was never wired and no diagnostic said so. Both chokepoints now refuse the element where it stands — one E002 naming it, a hint that says "without quotes" for a quoted element — and the rest of the list is read. No shipped bot writes such an element (grep over…

Bug Fixes ​

  • runview: a refused run-level fallback reaches the run's timeline (#1583) (7d88d51)

    whyThe launch-time fallback chain is screened before it is materialised onto a node, and the screen is right to refuse: an empty tools: list crossing to a CLI backend would silently hand that node the full unrestricted toolset, a codex route cannot run inside an active sandbox, a stage naming a backend with no model resolves to nothing. Each refusal already carries a precise, actionable sentence.
  • server,docs: revoking a team credential takes the same right as creating one (#1689) (01119d2), references #1687

    whyCreating a team BYOK key or generic secret gates on `canManageTeam`, which carries the org arm; revoking one gated on `canMutateScopedRecord`, which did not. An org admin could install a credential in any team of their org and never take it back — strictly worse than not being able to install it, since the key funds runs and its plaintext is write-only from that point on.

3.191.2 (2026-09-23) ​

Bug Fixes ​

  • delegate: a facade's dated wall is a window, not a throttle (#1602) (8fbe427), references #1583

    whyThe ZAI-shaped facade words its multi-day wall differently from its 5h one: "Usage limit reached for 5 hour. Your limit will reset at …" against "[1310][Weekly/Monthly Limit Exhausted. Your limit will reset at …]". Only the first was in usageWindowSignals, so the second fell through to RateLimitKindTransient — a dated wall filed as a retryable throttle.
  • gate: a verdict certifies the revision it read, and the auto-merge arming is pinned to it (#1657) (92ba384), references #1586 #1586 #1585 #1587 #1587 #1587 #1633

    whyThe publish endpoint resolved the pull request's head at the moment it wrote the commit status, and the payload carried no revision at all (`pkg/server/forge_publish.go:720-762`). So a bot could audit A, a push land B, and A's verdict certify B. With a required check, `required_approving_review_count: 0` and auto-merge armed — the exact shape `SocialGouv/buildkit-operator` carries — that is a path for an unaudited commit to reach the default branch.

3.191.1 (2026-09-23) ​

Bug Fixes ​

  • parser: a list that is not written as one never reads the next property as its elements; an empty string is not a rule or a mount (#1681) (37e520e), references #1631 #1584 #1631 #1671

    whyTwo findings of the gate's third verdict on PR A (#1631), merged before they could be treated there; both reproduced first.

3.191.0 (2026-09-23) ​

Features ​

  • studio: a per-file Source view for a bot in several files, and the fold refusal at the chokepoint (#1665) (5b35906), references #1612 #1659 #1227 #1612 #1649 #1612 #1674

    whyThe Source view showed a bot in several files as its merged program, read-only, and told the author to "open each file from the files drawer" — a control that renders only for a cloud `botsource://` path, so every local author was sent to something that was not on their screen. It is now a picker over `unit.files`: one file's text at a time, rendered from the open document by provenance, with the merged program as a read-only entry; an edit is re-parsed with that one file replaced, and the save…

3.190.0 (2026-09-23) ​

Features ​

  • dsl: an empty tools: list declares no tools, and one table settles what a tool name means (#1671) (34daaec), closes #1676, references #1581 #1640 #1656 #1615 #1579 #1 #2 #1673 #1673 #1631

    why"this node has no tools" was unsayable. Four independent erasures: the parser returned nil for `[]`, the AST's JSON seam dropped it through `omitempty`, `iterion fmt` wrote the line only for a non-empty list, and the studio replaced an emptied tag list with `undefined`. `bots/evolve`'s gpt reviewer is what that cost: a formatting pass deleted its `tools: []` and left ten comment lines describing a rule nothing carried.

3.189.0 (2026-09-23) ​

Features ​

  • dsl: lot 5 PR A — structured registry, forms held to the parser, and the author JSON Schema (#1584) (#1631) (8969915), references #1010 #1010 #1094 #1094 #1624 #1627 #1628 #1628 #1656

    why`servers: ["forge"]`, `on: ["auth"]`, `watches: ["a"]`, `needs: ["gpu"]` read as EMPTY lists, `tools: [1, bash]` as `[bash]`: parseIdentList and parseToolRef consumed a token that could not open a name and reported nothing, so the server was never wired and no diagnostic said so. Both chokepoints now refuse the element where it stands — one E002 naming it, a hint that says "without quotes" for a quoted element — and the rest of the list is read. No shipped bot writes such an element (grep over…

3.188.0 (2026-09-22) ​

Features ​

  • security: a run carries who wrote its workspace, and an untrusted one can neither publish, read a forge token, nor pass a pin it cannot enforce (#1670) (6898e6c), references #874 #874 #874 #1639 #874

    whyGroundwork for the opt-in fork review lane (#874): the five constraints the ticket sets are only enforceable if "who wrote this code" is a property of the RUN, not of the surface that launched it. A resume, a usage-window retry and a forked child all rebuild their credentials from the stored run document, long after the admitting lane is out of scope.

3.187.0 (2026-09-22) ​

Features ​

  • server,studio,cli: administering teams and members from the cloud console (#1559) (a686907), references #1540 #1539 #1541 #1542 #1543 #1446 #1544

    whyAn operator could not answer the first question an account that "sees nothing" raises: where does it come from, and what was it actually granted? Every fact needed already lived in the store and none of it was reachable through the API, so the answer was a Mongo read or a guess.

3.186.0 (2026-09-22) ​

Features ​

  • backends: opencode is a protocol value of the CLI-agent seam, honoured at every site that decides per backend (#1656) (24d53ff), references #1416 #1 #1655

    why`backend: "opencode"` used to validate clean and then die at dispatch on `delegate: unknown backend "opencode"` — after paying for a sandbox image and a container. It is now an instance of the existing `CLIAgentProtocol` seam (ADR-065), invoked as its own CLI is:
  • dsl: a var declares its own constraint, so an operator typo is refused at launch (#1628) (5f49b45), closes #1609, references #1608 #1601 #1601 #1350

    whyA `vars:` declaration could carry a type and an `[enum: "a","b"]` set, but nothing narrower: a value that is a legal string and a nonsense one — the ticket's ` codeX --some-flag` — reached the run untouched and degraded inside whatever node consumed it, at the same coverage value a MISSING binary produces. Measured before the change: `iterion run --var "agent= codeX --some-flag"` finished green.

Bug Fixes ​

  • dsl: a preset value is checked against its var's enum and matching constraints at compile time (#1660) (2beec3b), references #1611 #1350

    whyA `presets:` entry could set a var to a value its `[enum: ...]` or its `[matching: "<re>"]` constraint refuses, and nothing looked: `iterion validate` said `result: OK` and only `iterion run --preset <name>` died at the launch gate. A bot could ship a preset no launch can accept.

3.185.1 (2026-09-22) ​

Bug Fixes ​

  • repomap: the docs map links resolve from docs/references on the site, not from the repository root (#1625) (774fc6b), references #1505 #1508 #1508

    whyThe Page column of docs/references/map-docs.md wrote every target as `../../docs/<page>.md`. github.com resolves that against the map's own directory and serves the page; the documentation site's root IS docs/, so the same target climbs out of the site and names nothing there. 318 dead links on one page: the `docs` job has failed on every push to main since 09bd1d0b1 (#1505) and the site has not published for three days.

3.185.0 (2026-09-22) ​

Features ​

  • dsl: a comment keeps the place it was written at, through a save and through fmt (#1616) (15de46b), references #1282

    whyThe parser kept only the file-head `##` comments. A comment written inside a declaration, between two, or at the end of a line never reached the AST, so a studio save and `iterion fmt` dropped it — and `unparse.Verify` could not see the loss, because the compiled program does not carry comments and the mirror comparison left them out. `iterion fmt` refused 59 of the 81 shipped `.bot` files for that reason alone.

3.184.1 (2026-09-22) ​

Bug Fixes ​

  • dsl: a backend written as a dial is screened by what it declares, not by its spelling (#1614) (19e3a83), references #1389 #1389

    why`effectiveNodeBackend` returned `""` — "no opinion" — for any backend string containing `${`, and `""` short-circuits every COMPARISON screen: the `tools:` inversion, the session-continuity refusal, the C177 effort drift, the primary-route permission-gate check, and the same set inside `ApplyRunFallback`, the launch-time `--fallback` admission. A node whose backend is an env dial therefore ran unscreened, and an author could add a route crossing the claw⇄CLI boundary to one and have it compile…

3.184.0 (2026-09-22) ​

Features ​

  • runtime: a list value renders by its declaration, and a var's text has one reading (#1601) (b38cba0), references #1320 #1285 #1320 #1320 #1285

    whyA `json` value and a `string[]` value reach a tool body as the same Go value — []any — so the renderer alone could never tell a document from an argv list. It guessed by shape: a scalar list space-joined, which is right for `string[]` and runs the second element as a command for `json`, and an EMPTY list rendered as nothing at all, so `cmd {{vars.x}} --flag` lost its argument and `--flag` shifted one place left, in silence, with the node reporting SUCCESS (#1320).

3.183.0 (2026-09-22) ​

Features ​

  • dsl: allow/ask/deny are declarable per agent and judge node, and a node list REPLACES the workflow's (#1581) (8a7bb2b), references #1222

    why`allow:` / `ask:` / `deny:` existed on the `workflow` block only; just the scalar `permission:` mode descended to a node. So a workflow whose nodes need different bounds had no way to say so, and the author's only lever was `permission: off` on the node that needed more — which removes every rule, not the one in the way.

3.182.1 (2026-09-22) ​

Bug Fixes ​

  • runtime: staging gestures spell no exclusion git would refuse (#1558) (#1572) (f19e5b7), references #1464 pre-#1507 #1569 #1571 #1577

    whyOn a repository whose .gitignore already ignores the mirror (this one: `**/.claude/`), `git add -A -- ':/' ':(exclude,top).claude'` stages the work but exits 1 ("The following paths are ignored by one of your .gitignore files: .claude"). Both staging gestures read that as failure: the wip bank left the run's work unbanked in a preserved worktree, the operator's commit-and-finalize failed. Measured on the #1464 dogfood.

3.182.0 (2026-09-22) ​

Features ​

  • release: the cut realigns the syntax floor pins it claims (#1287) (#1570) (28eba1e), references #1566 #1154 #1566

    whyA syntax floor (`parser.ProfileSince[2]`, `ImportSince`, `ContractSince`, `bundle.ToolAliasesSince`) is pinned by hand to the next minor above the release main carries — a guess written before the number is known, realigned by hand when a release overtakes it. The cut is the one place that knows the number actually released.

3.181.2 (2026-09-22) ​

Bug Fixes ​

  • dispatcher/native: the index watcher's default constructor goes through fswatch, and the class is inventoried (#1568) (6a682fe), references #1200 #1051 #1198

    whyThe native board store opens one inotify instance per store, unconditionally, and logs a refused one with %v and nothing else. Its constructor was the raw fsnotify.NewWatcher, so that refusal reached the log as "too many open files" and nothing more — which on an ARC runner does not say whether the process ran out of descriptors or the real UID ran out of inotify instances, a budget shared with every other container under that UID. That distinction is what #1198's diagnosis rests on.

3.181.1 (2026-09-22) ​

Bug Fixes ​

  • sandbox: auto degrades where the host cannot isolate, an explicit container refuses — one chokepoint, one typed code (#1425) (#1567) (57a200a), closes #1565, references #1426 #1564 #1426

    whyThe docs promised a graceful degrade the factory did not perform, and it was not theoretical: **107 scheduled ticks** died on the operator's own host between 2026-07-20 and 2026-09-17 with `sandbox: mode "auto" requested but no container runtime is available`. Arbitrated split, by MODE and never by the tier that named the mode:

3.181.0 (2026-09-21) ​

Features ​

  • bots: the tree-noise list reaches the bots (#1530) (9a9cd77)

    whyThe catalogue migrates from the literal `':(exclude,top).claude'` exclusion to the canonical channels the engine provisions (the engine half landed in 13db0dd47):

3.180.2 (2026-09-21) ​

Bug Fixes ​

3.180.1 (2026-09-21) ​

Bug Fixes ​

3.180.0 (2026-09-21) ​

Features ​

  • dsl: the contract gallery template, the feature-dev public contract, and a subbot's output projected from the child's contract (#1537) (c47df3a), references #1276 #1263 #1280 #1276 #1280

    whyThe public-contracts lot (PR #1276) deferred two surfaces, both landed here:

3.179.0 (2026-09-21) ​

Features ​

  • backend: the schema re-ask and the tool-alias floor (#1546) (2cb6c6d), references #1385 #1155 #1276 #1155 #1155

    whyWhen an LLM node's structured output fails its output: schema on a shape one more ask can fix (a parse fallback, or a missing required field), the executor re-asks the model ONCE with the validation error, continuing the work instead of repeating the turn: claw replays the conversation it just completed (a new delegate.Task.ContinueConversation, tools off, in-process, refused over the sandbox runner so an old runner can never receive it); claude_code/codex/pi resume the answer's session by id;…

3.178.4 (2026-09-21) ​

Bug Fixes ​

  • server,botsource: a mission rewind's apply resolves the bot identity the preview pinned (#1515) (9f38fd2), references #1381

    whyThe assistant mission rewinds in two coordinator passes: the preview resolves the stored bot at its current botsource row and computes the pivot; the apply re-resolved independently and computed the blast radius in whatever the slug carried by then. A republish — or a delete plus re-author under the same slug — between the two passes moved the destruction onto a graph the preview never saw, on a receipt that reported success; ExpectedPivot guards the pivot's NAME, and any of those graphs name…

3.178.3 (2026-09-21) ​

Bug Fixes ​

3.178.2 (2026-09-21) ​

Bug Fixes ​

  • studio: the Run gate and the tab binding stop lying about an unbound buffer (#1531) (ce784c8), references #1334 #1334 #1326 #1326 #1519 #1519

    whyA tab remembered its file in tab.params.file, written by TabBindingSync only ever non-null (`if (!path) return`): a tab that stopped following anything kept the PREVIOUS name, and a remount — /editor is a wouter route, the per-tab stores survive — re-fetched that file over the author's unsaved work (#1334). The same staleness held a draft param, whose re-apply would overwrite the canvas with the draft.

3.178.1 (2026-09-21) ​

Bug Fixes ​

  • dryrun: shape json outputs by their consumers, and name a best_effort fan-out's dead branches (#1491) (1b93cde), references #1318 #1456 #1325 #1318 #1456 #1325 #1325 #1434

    whyA `json`-typed output field a downstream position reads AS AN ARRAY takes the array shape now — empty on the false pass, one-element on the true pass. The rule fires at one seam (dryrun.Value), read from a workflow scan (arrayConsumers / arrayConsumedVars) that enumerates the two iteration surfaces of the DSL (fan_out_each `over:` refs and Foreach.CollectionRefs) at their exact leaf refs, the edge-expression array-context refs and the compute-expression array-context refs (through a new…

3.178.0 (2026-09-21) ​

Features ​

  • live: the last-green ledger answers "do the live e2e still pass?" for free, and the DRAFT stabilisation plan (#1511) (b59914d), references #1424 #1422 #1424 #1422

    whyThe plan #1424 asks for, as a DRAFT: per core surface, what is already free (measured from the coverage matrix's 387 rows), what must become free, what must stay paid and why, and the budget one stabilisation pass costs. Candidate order DSL → runtime → backends → bots, ranked by what breaks the most when it moves; no engine change, no new tests for tests' sake.

3.177.0 (2026-09-21) ​

Features ​

  • review-pr: the reviewer runs on z.ai glm-5.3 while the claude forfait is capped (#1519) (8b6b60f), references pre-#1390

    whyThe merge gate's reviewer fleet is parked: the deployment's Anthropic forfait is under a multi-day cap, and every PR in this repo crosses this bot. All four reviewer nodes (reviewer_claude, reviewer_gpt and both glance twins) are pinned to provider "zai" + model "glm-5.3".

3.176.0 (2026-09-21) ​

Features ​

  • runtime: a canonical tree-noise list — run.tree_noise, ITERION_TREE_NOISE, and gates that carry it (#1507) (13db0dd), closes #1506 #1506, references #1464 #1364 #1459 #1464 #1506

    whyThe gates that judge a run's worktree must set aside what the run's own setup and tooling wrote: the `.claude/` mirror the engine lays at run start (#1364) and the devbox.lock every devbox invocation rewrites (#1459). Until now that exclusion lived as literals in 26 bot files and 27 identical python `is_scaffold` copies, and the engine's own finalizer carried a third spelling (`scaffoldPrefix`). One list, every shape derived: pkg/treenoise holds the entries and emits the git pathspecs, the…

3.175.2 (2026-09-20) ​

Bug Fixes ​

  • resume: the launch's decisions travel with the run — sandbox mode, merge target and branch name survive a resume, and answers survive a failed one (#1490) (f3223b8), references #1435 #1366 #1435 #1366 #1366 #3 #1366 #1366 #1366

    whyThe class: every launch-time override iterion run accepts must either be persisted on the run and replayed on resume, or recomputed on purpose (documented). Two facets of one class were paying the ticket tax: --sandbox at launch was silently ignored by resume (docker on a host that saw one), and --merge-into / --branch-name / auto-merge choices were dropped between the CLI launch and the studio's resume. A third, smaller one on the way: answers of a resume that failed AFTER recording them (a…

3.175.1 (2026-09-19) ​

Bug Fixes ​

  • dsl: a with: mapping refuses what the runtime cannot resolve — {{input.*}} on a subbot or emit, secrets and attachments, and a literal that cannot be its target's type (#1497) (543f876), references #1308 #1310 #1420 #1505

    whyA data mapping — an edge `-> dst with { ... }`, a subbot / emit node's own `with:`, or a `fail message:` — is resolved through `pkg/runtime.engine.resolveMapping`, whose `resolveRef` has no arm for the `secrets` / `attachments` namespaces and reads `input.*` against the parent's run inputs for subbot / emit where the kind has no `input:` surface. A compute node's `expr:` runs through `pkg/dsl/expr`, whose `evalNamespaces` (snapshot.go) excludes `secrets` and `attachments` deliberately — so a…

3.175.0 (2026-09-19) ​

Features ​

  • server: /api/v1/runs/stats honours team_id with authorisation, and a schedule record carries the outcome of its last run (#1510) (e00bd4d), references #1419 pre-#1419 #1419 #1426 #1425 #1426 #1477 #1477 #1345 #1477 #1419 #1419 #1425 #1425 #1425 #1477

    whyThe ticket measured `GET /api/v1/runs/stats?team_id=<id>` as "accept-and-drop": called with three different tenant ids, the endpoint returned byte-identical numbers for the caller's active team. The adversarial round on the framing found something stricter: net/http.ServeMux silently discards every unknown query parameter, so `team_id` was never "accepted" and dropped — it was never SEEN. The user-facing result is the same (an operator comparing tenants sees the wrong answer with no error), so…

3.174.1 (2026-09-19) ​

Bug Fixes ​

  • four one-site defects — the zai hint suppresses every ambient Anthropic channel, the operator MCP refuses unknown arguments, a bot's shell finds the engine's own binary, and the dry-run report is ordered (#1487) (ee80560), references #1390 #1505 #1335 #1384 #1434 #1505

    whyanthropicCredEnvForCLI's providerHint=="zai" no-key branch used to clear ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN only, so on a host carrying an ambient ANTHROPIC_API_KEY the CLI silently routed the node to api.anthropic.com and 404'd on the GLM id, exactly the "silently falling back to a different provider" the code's own comment promised to prevent.

3.174.0 (2026-09-19) ​

Features ​

  • map: a repository map, a deterministic graph, and what discovery costs (#1505) (09bd1d0), references #1482 #1480 #1482 #1483 #1484 #1485 #1335 #1482 #1486 #1488 #1481

    why`iterion bench discovery` classifies a run's tool calls into orientation (read, search, list), change (write, commit) and neither, and reports the token spend of the nodes that never wrote a byte — the only split the event stream supports without imputing one.

3.173.0 (2026-09-19) ​

Features ​

  • webhooks: l'App retire la demande de review après publication, pour rendre le geste répétable (#1382) (ed8cbbe)

    whyGitHub lifts a review request only when the REQUESTED account submits the review. On a github_app connection the review is posted by <app_slug>[bot], and an App cannot be a reviewer at all — so the request armed through webhooks.Config.ReviewRequestLogins survives the review answering it: the "review requested" pastille stays pending forever and re-adding the reviewer is not a repeatable gesture.

3.172.3 (2026-09-19) ​

Bug Fixes ​

  • runtime: the repo devbox install at run start leaves the tracked devbox.lock what the run found it (#1459) (#1465) (ce459ec), references #1344 #1364 #1464 #1451 #1450 #1344 #828

    why`provisionHostDevbox` installs the TARGET REPO's devbox project in place, in the run's worktree. On a host whose devbox plugin registry is newer than the repository's pin, `devbox install` rewrites the tracked lock — one line, `nodejs_24@latest` `plugin_version` 0.0.4 → 0.0.5 on this machine — so the worktree differs on a tracked file before the first node runs, and every gate that reads the tree as the pass's own work (a campaign's scope gate, a clean-tree precheck, a whole-tree commit)…

3.172.2 (2026-09-19) ​

Bug Fixes ​

  • eventbus,server: a bus cancel waits for its in-flight callback, and the forge-board projection is joined at the same budget (#1477) (14b573a), references #1343 #1257 #1345 post-#1257 #1474 #1343 #1345 #1257

    why#1343 — the two eventbus.Bus implementations disagreed on what the unsubscribe returned by Subscribe guarantees. InProcBus cancelled the handler's context and waited unbounded; NATSBus neither cancelled nor waited (sub.Unsubscribe() alone, callback on context.Background()). Under SIGTERM in a pod, five server subscribers — userNotifyCancel, opsAlertsCancel, gateReconcileCancel, forgePublishExpiryCancel, gateAutofixCancel — could be cut mid-store-write, and pkg/server/assistant_run_watch.go:289…

3.172.1 (2026-09-19) ​

Bug Fixes ​

  • runtime: plugin skills land in both discovery shapes, and a same-name plugin collision is loud on the cloud mirror too (#1479) (407b719), closes #1372, references #1373 #1374 #1373 #1374

    why## #1373 — plugin skills were reachable only by claw

3.172.0 (2026-09-19) ​

Features ​

  • studio: credential spend admin screen (#1444) (#1463) (5905af0), references #1441 #641 #1441 #1087 #1441

    whyThe cloud super-admin console exposes six pages, but several requireSuperAdmin endpoints exist and are tested with no client to reach them. This adds the API/client layer the remaining admin screens (T2–T5) depend on, schema-first so the wrappers carry the generated OpenAPI types rather than hand-rolled shapes.

3.171.0 (2026-09-19) ​

Features ​

  • studio: org → teams drill-down in the admin org drawer (#1446) (#1468) (e690be8), references #1441 #1441

    whyThe cloud super-admin console exposes six pages, but several requireSuperAdmin endpoints exist and are tested with no client to reach them. This adds the API/client layer the remaining admin screens (T2–T5) depend on, schema-first so the wrappers carry the generated OpenAPI types rather than hand-rolled shapes.
  • studio: platform usage-caps admin screen (#1442) (#1460) (4f591d1), references #1441 #1441 #1441

    whyThe cloud super-admin console exposes six pages, but several requireSuperAdmin endpoints exist and are tested with no client to reach them. This adds the API/client layer the remaining admin screens (T2–T5) depend on, schema-first so the wrappers carry the generated OpenAPI types rather than hand-rolled shapes.

3.170.0 (2026-09-19) ​

Features ​

  • bots: docs-refresh and adr-cartograph read as dsl profile 2, and the ADR survey gets the inventory its prompt reads (#1344 wave 6) (#1462) (34bb532), references #1349 #1455 #1456 #1457 #1459

    whyThe last two catalogue bundles on profile 1. `iterion dsl migrate --to 2 --floor 3.141.0` wrote the `dsl: 2` header and nothing else (no quoted literal holds a backslash in either file); the paragraph breaks the authors wrote inside 10 prompts — 72 in all: docs-refresh 33 across 4 prompts, adr-cartograph 39 across 6 — now reach the models as blank lines instead of being folded away by the profile-1 lexer. Both manifests carry the `requires: iterion: ">= 3.141.0"` floor and a changelog line…

3.169.0 (2026-09-19) ​

Features ​

  • studio: admin API client layer for platform settings, credential spend & usage-readings (#1441) (#1454) (fc80e91)

    whyThe cloud super-admin console exposes six pages, but several requireSuperAdmin endpoints exist and are tested with no client to reach them. This adds the API/client layer the remaining admin screens (T2–T5) depend on, schema-first so the wrappers carry the generated OpenAPI types rather than hand-rolled shapes.

3.168.0 (2026-09-19) ​

Features ​

  • dsl: the last loose workflows read as profile 2, and the routing fields resolve {{vars.…}} (#1344 tail) (#1451) (07d7837), references #1206 #1367 #1450 #1450

    whyA census of every tracked .bot after wave 5 found two shipped workflows the waves never listed: pkg/cli/templates/dispatch_bots_default.bot, the `default` assignee `iterion dispatch` embeds in the binary (copied into templates/dispatch_bots/default/main.bot at build), and bots/smoke/board_smoke.bot, the hand-run board smoke; the local round then named the two runnable scripts under scripts/adhoc/. `iterion dsl migrate --to 2` on all four: the header and nothing else. Seven prompts keep the…

Bug Fixes ​

  • runtime: the simulation sweep reads the repo's source, not the operator's run store (4e8d925)

    whyTestNoProductionPackagePassesWithSimulation walked the tree skipping four dot-directories by name. `.iterion/` was not among them — and `.iterion/worktrees/<run-id>/` holds whole COPIES of the source tree, so the sweep found pkg/dryrun/run.go and pkg/runtime/simulation.go once per kept run and reported them as production launchers. Measured on a working checkout: 12 offences, every one of them a copy of a file the sweep deliberately excludes at its real path.

3.167.0 (2026-09-18) ​

Features ​

  • brand: the brand gate blocks, the short form is guarded, and a shared link gets its card (#1440) (aadec80), references #1437 #1437

    whyFour decisions, each arbitrated rather than assumed.

3.166.0 (2026-09-18) ​

Features ​

  • examples: the 25 examples read as dsl profile 2, with the three example bundles' floors (#1344 wave 5) (#1436) (d06cc04), references #1349

    why`iterion dsl migrate --to 2 --floor 3.141.0` on every workflow under examples/: the `dsl: 2` header and nothing else — no quoted literal in an example holds a backslash, so no value was re-spelled. Eight named prompts in six files now keep the 19 paragraph breaks profile 1 dropped before the text reached the model or the studio's form; each was read, each separates prose (a heading, a list, a templated value) from prose.

3.165.0 (2026-09-18) ​

Features ​

  • studio: / serves the product home, the studio moves under /studio, and iterion says what it is (#1433) (e6dfbda)

    whyTwo things nobody could read off a URL or a page.

3.164.0 (2026-09-18) ​

Features ​

  • bots: app-dev, copilot and secured-renovacy read as dsl profile 2, with their loops' exhaustion exits (#1344 wave 4, #1293) (#1383) (424c933), references #1349 #1420

    whyWave 4 of the catalogue's move to the `dsl: 2` profile: the three heaviest bundles, by the migrator and nothing else. 54 prompts / 290 blank lines the authors wrote now reach the models as paragraph breaks (app-dev 19/84, copilot 6/80, secured-renovacy 29/126), every one read; four quoted literals re-spelled with their value unchanged (the deploy fail_log, copilot's two expr trailers, the files_lines join — the expression lexer interprets \n itself, so both profiles hand the compute the same…

3.163.0 (2026-09-18) ​

Features ​

  • review-pr: the claude reviewer has somewhere to go (#1388) (0f694e6), references #1221 #1221

    whyreview-pr declared no `fallbacks:` route at all — one of the 28 bundles that declared none, and the only required merge gate among them. So when this instance's Anthropic credential could not serve, the claude reviewer simply failed, on the bot every PR in this repo crosses.

3.162.3 (2026-09-18) ​

Bug Fixes ​

  • modernize: name the path that spent the extension certificate (#1387) (dc4a9fc)

    whyThe exemption is correct and unchanged: a path outside the certified surface (additions under refs/ and corpus.json) spends the WHOLE certificate, because the exemption is safe only on "an addition cannot mask an existing divergence", and a change elsewhere under the net is not an addition.

3.162.2 (2026-09-17) ​

Bug Fixes ​

  • bots: the campaign gates and the whole-tree commits leave iterion's .claude/ scaffold out (#1364) (#1377) (b040a98), references #1344

    whyiterion mirrors a bundle's skills and plugin files into <workspace>/.claude/ before the first node. On a repository that does not ignore .claude/, the ten campaign gates read the untracked mirror as the pass's uncommitted work and refused every pass (net_dirty, four times in wave 3b of #1344), and the deterministic commits that stage the whole tree committed it (16 of bmady's 43 files in one feature commit).

3.162.1 (2026-09-17) ​

Bug Fixes ​

  • runtime: a driver with no bind mount has a workspace, and the reset must find it (#1365) (ab57dde), references #1195

    whyA shared sandbox whose WorkspaceFolder is empty is not a sandbox without a workspace. containerWorkspaceFolder already states the rule — "an explicit Spec.WorkspaceFolder wins, an empty one means the same absolute path as on the host" — and engine_resolve.go already applies it. A driver with no host filesystem cannot bind-mount anything, so it copies the workspace to that same absolute path and leaves the field empty. That is the shape every cloud run takes.

3.162.0 (2026-09-17) ​

Features ​

  • agents: publish the adversarial review loop as a skill, and require it here (#1372) (c6538b7)

    whyiterion mirrors a plugin's contributed markdown into a run workspace's .claude/<kind>/ at run start, naming each file by its base name. That is right for the flat shape every builtin ships (skills/graphify.md) and wrong for the two shapes where the base name is the constant "SKILL.md" and the real name lives elsewhere: the Agent Skills DIRECTORY form <name>/SKILL.md — what `npx skills add` publishes and what claude_code's Skill tool discovers — and a root-form pack whose whole tree is one…

Bug Fixes ​

3.161.1 (2026-09-17) ​

Bug Fixes ​

  • studio: Testing Library's 1 s async budget is a wall clock, not a work budget (#1378) (402e843)

    why`CredentialPreview.test.tsx` went red on CI with "Unable to find role=option and name 'Revi (review-pr)'" on a head whose only change was a markdown file, while the same tests pass locally in ~200 ms and passed on the previous head with byte-identical studio code (zero studio commits on main since that base). findBy* and waitFor give up after 1 s by default, and that second is wall clock: a loaded runner spends it (the job took 13m50s for 239 files) and the failure then reads exactly like a…

3.161.0 (2026-09-17) ​

Features ​

  • secrets: a team's own LLM key can name the workloads it funds (#1370) (1f73589), references #1363 #1368 #1368 #1368

    whyA key for provider P, once on a team, funded whatever that team ran. The only dials were scope (who may SEE it) and a concurrency ceiling; nothing said what a key was FOR. `is_default: false` reads like a reserve and is not one — keyRank is a preference order, and rank 3 is selected whenever it is the only visible key of its provider. Measured: a key added for one bot served two runs of a different bot within four hours.

3.160.0 (2026-09-17) ​

Features ​

  • bots: wave 3b of the catalogue on dsl: 2 — fourteen small bundles (#1344) (#1369) (4c00b5c), references #1293 #1367 #1364 #1366 #1367

    whybmady, evolve, golden-master (its four files), instrument, product-docs, adr-rechallenge, e2e-coverage, test-coverage, feature-gap-fill, supply-shield, supply-shield-cve, rgaa-audit, ultra11y and dep-update-guard declare profile 2: the paragraph breaks their authors wrote inside 127 prompts (572 blank lines) now reach the models. The migrator changed nothing else (no backslash literal to re-spell); every manifest gets a patch bump, a changelog line and the >= 3.141.0 engine floor.

3.159.1 (2026-09-17) ​

Bug Fixes ​

  • secrets: a credential field that omitempty drops can be set but never cleared (#1223) (6dcc5e4), references #1220

    whyMongoOAuthStore commits a record through $set, where an omitted key keeps the OLD value, while Upsert is a FULL replace — the connect/paste path builds the record from the blob it was handed and stores the whole thing. A bson `omitempty` therefore makes a field ONE-WAY: writable when non-zero, impossible to clear. The memory twin replaces its map entry outright and clears correctly, which is exactly why no in-memory test sees it.

3.159.0 (2026-09-17) ​

Features ​

  • bots: wave 3a of the catalogue on dsl: 2 — nine small bundles (#1344) (#1361) (7e67663), references #1349 #1282 #1010 #1282

    whyvuln-watch, issue-triage, arbitrate, feed-watch, devbox-setup, review-env, revi-converse, wiki-gen and modernize move to profile 2, by the migrator and nothing else: `dsl: 2` at the head of nine files, no literal re-spelled, the program proven unchanged by the migrator apart from the one change of meaning the profile carries — the blank lines the authors wrote inside 17 prompts (66 in all: wiki-gen 14, modernize 12, revi-converse 10, review-env 8, arbitrate 6, feed-watch 6, devbox-setup 6,…

3.158.0 (2026-09-17) ​

Features ​

  • cloud: record on the run what a cloud launch compiled (#1226, step 2/3) (#1357) (3371fdb), references #1351

    whyThe queue message carries the compiled IR and the identity hash, never the text, and a runner pod has no filesystem the launch touched — so a cloud run recorded no workflow_source and `rewind --auto` had nothing to diff against. The cloud branch of Launch already held the compile result and kept only its hash.

3.157.0 (2026-09-17) ​

Features ​

  • bots: wave 2 of the catalogue on dsl: 2 — whole-improve-loop, branch-improve-loop, sec-audit-source, sec-audit-deps, campaign (#1344) (#1358) (6426aa6), references #1349 #1293 #1282 #1010 #1293

    whyThe dispatcher-mirrored bots and the campaign supervisor move to profile 2, by the migrator and nothing else: `dsl: 2` at the head of five files, no literal re-spelled, the program proven unchanged by the migrator apart from the one change of meaning the profile carries — the blank lines the authors wrote inside 52 prompts (sec-audit-source 19 / 129, branch-improve-loop 15 / 65, whole-improve-loop 13 / 47, sec-audit-deps 3 / 20, campaign 2 / 3) now reach the models as paragraph breaks instead…

3.156.0 (2026-09-17) ​

Features ​

  • sec-audit-source: let the operator choose which agent the deep scan runs on (#1347) (ccc6490)

    whydeepsec has its own backend system, unrelated to iterion's: it drives `claude`, `codex` or `pi` from inside this tool node, each resolving its own credentials. The node hardcoded that choice -- gateway if a key happened to be in the environment, `--agent claude` otherwise -- with no override. That is a constant bounding operator work with no escape hatch.

Performance Improvements ​

  • runview: a run listing no longer carries the source its runs executed (#1355) (aa0ce3f), closes #1351

    whyListRunRecordsCtx lists ids then loads every run WHOLE, and its own Limit truncates only afterwards — so the heaviest field on the document decides what a request costs. That field is the recorded workflow source: the text of the unit the run executed, 100-520 KB for the real bots in this catalog. No consumer of a listing reads it; they all become RunHeaders, which carry neither field. One caller, the board projection, lists with no limit and retains every record. The finished-forks index is…

3.155.0 (2026-09-17) ​

Features ​

  • bots: wave 1 of the catalogue on dsl: 2 — review-pr, whats-next, feature-dev (#1344) (#1353) (c1fe2a0), references #1206 #1159 #1293 #1301 #1282 #1010 #1293 #1293 #1349 #1349

    whyThe dry run over the corpus required every file to change, which was true while no shipped bot declared `dsl: 2`. The catalogue now migrates wave by wave (#1344), so a file already on profile 2 must come back byte-identical, with no change and no prompt reported — the same lines the first prepared lot (#1206) wrote, so the two rebase cleanly.

3.154.2 (2026-09-17) ​

Bug Fixes ​

  • server: join the background loops the shutdown cancels (#1346) (819d6a5), closes #1257

    whyA loop stopped by a cancel alone returns after its cancel did, so the process could exit between a claim and its release, or mid-write into a store already being torn down — a write the grace period never covered.

3.154.1 (2026-09-17) ​

Bug Fixes ​

  • studio: a file that does not parse is not bound to its path (#1327) (21dd205), closes #1251, references #1251 #1251 #1326

    whyA `.bot` the parser could not read whole opened as the document it SALVAGED — the file minus the region it could not read — bound to its own path and marked saved. The first Save wrote that document back, and what the author wrote was gone. Silently, and totally, for the unreadable part.

3.154.0 (2026-09-16) ​

Features ​

  • cli,mcp,dsl: the dry run takes its launch values (validate --var/--preset, the MCP tool's vars); the skill names the loop namespace (lot 4 follow-up) (#1336) (d812f44), closes #1332, references #1289 #1292 #1110 #1010 pre-#1332 #1332

    whyFollow-up of lot 4 (#1289, PR #1292), from the F20 re-probe of 2026-09-16 with a mid-size model (#1110).

3.153.1 (2026-09-16) ​

Bug Fixes ​

  • sec-audit-source: a pass that refuses to run must destroy nothing (#1331) (85bff66)

    whyR24f9d8 from the gate, and it is my own fix from the round before. Removing the stale export on entry is what makes "nothing came out" mean "nothing came out of THIS pass" — but I put it at the top of the body, above the graceful-degrade probes. A pass that never gets as far as running deepsec (no binary, no usable run id, unreadable workspace) then destroyed a CONCURRENT pass's already-exported findings, and that neighbour kept claiming json_paths.deepsec while its coverage still read…

3.153.0 (2026-09-16) ​

Features ​

  • dsl: dry-run validation — validate --exec, fmt/fix, the loop warnings (lot 4, #1289) (#1292) (e652e78), references #1282 #1307 #1325

    whyA NodeExecutor that renders what each node would send through the production renderers and answers with the node's fixture or a schema-shaped output (an enum's first value then its last, a bool the pass's bias, 1, 1.0, "x", one element, an empty object), holds shell text to the interpreter's own parser (bash -n, sh -n; other interpreters are said unchecked), and reports every {{…}} kept as written — the prompt renderer now tells a listener what it keeps (model.TemplateResolver, the one…

3.152.4 (2026-09-16) ​

Bug Fixes ​

  • sec-audit-source: say when a deep scan cannot be shown to have covered the tree (#1321) (badbacb)

    whyA deepsec pass that was capped, or cut short, exports the findings it did produce, exits through the same path as a complete pass, and leaves finding_count looking healthy — on a busy tree a truncated pass outnumbers a complete pass on a clean one. Nothing said which one you were reading, so a partial audit could be transmitted to a product team as a full one. That is the defect this closes, and it is the same class that cost the pilot five reports built on 13% of a repository.

3.152.3 (2026-09-16) ​

Bug Fixes ​

  • golden-master: a mutant that lost its meta.json refuses, it no longer shrinks the set in silence (#1306) (a92cd3d)

    whyload_mutants skipped any directory without a meta.json and said nothing. The gate then compared `detected == total` over whatever the loader had been willing to return, so a held-out set of 7 that loses 3 meta.json reports a green 4/4. That is the vacuity trap of `holdout 0/0` with a different number, and the conjunction cannot see it because both sides shrink together.
  • subbotsource: a relative subbot source names the file the OS reaches (#1312) (596e032), closes #1283, references #1283

    whyTwo readers resolve one `subbot source:`. The bundle walk resolves the parent's directory before joining; the runtime resolver joined lexically. So through `link/parent -> real/parent`, a source `../sib/main.bot` folded to `link/sib/main.bot` — a path the kernel never produces, since it resolves the link first and only then walks `..`. A bundle that validated clean could run a different file, or none.

3.152.2 (2026-09-16) ​

Bug Fixes ​

  • dsl: a node's with: values are references like any other (#1311) (7ddbb79), references #1281 #1281

    why`collectAllRefs` walked prompts, edge with-mappings, tool commands, scripts and action params, fail messages and compute exprs — every template family except the one a NODE carries. A subbot handing its child `{{vars.depth}}` where no `depth` is declared compiled clean, and the child started with the literal text as its value.

3.152.1 (2026-09-16) ​

Bug Fixes ​

  • review-pr: the repo says whether it has a fixer; the reviewer stops assuming (#1313) (6a669dc), closes #1232, references #1232

    whyRevi published `Correction : /billy` on every review carrying a finding — at the moment and in the place a developer reads the findings, which makes it the highest-traffic instruction the gate emits. On this repo it says the opposite of the contract: the zero-touch lane is off for cost, findings are the developer's through the local loop.

3.152.0 (2026-09-16) ​

Features ​

  • connectors: a contract reference may declare itself nullable (#1302) (9a531c1), closes #1269

    whyA response contract generated from a Swagger 2 description refused the null that go-swagger services send for an absent relation. Forgejo sends one for assignee, milestone and merged_by on every unassigned issue, so --validate-responses on that description produced contracts refusing ordinary answers - and the cost is not a red diagnostic: a contract violation on a 2xx is marked ambiguous, has its Data cleared and is NOT retryable, so a mutation parks its run and a read fails its node, for an…

3.151.0 (2026-09-16) ​

Features ​

  • dsl: refuse two edges that disagree about what a foreach iterates (C269) (#1299) (38326be), closes #1290

    whycompileEdges registers a foreach by name and keeps the FIRST declaration, so a second edge declaring the same name over a different collection or element binding was discarded in silence: the edge carried the name, the name resolved to the other definition, and the run walked a collection its author never wrote. No error, no warning.

3.150.2 (2026-09-16) ​

Bug Fixes ​

  • runtime: a loop cap expression must produce a number, not a concatenation (#1294) (cdd871c), closes #1271

    why`+` concatenates as soon as one operand is a string, and loopCapInteger accepts a numeric string - deliberately, for the LEGACY single-reference template form. The two tolerances composed: a cap written `outputs.gate.remaining + 1` over an unschema'd field holding "3" evaluated to "31" and bounded the loop at 31 instead of 4, against the run budget, with no diagnostic. Measured end to end before the fix: 32 passes where 4 was written.

3.150.1 (2026-09-16) ​

Bug Fixes ​

  • dsl: bind group members and parameters per instance (#1191) (557e901)

    whyA prompt whose source cannot be resolved refuses its {{include}} for a reason that belongs to the declaration and cannot differ between the group instances binding it, so a group of N members landed N identical diagnostics on one span. The budget refusals beside it already stop at the first; this one now does too.

3.150.0 (2026-09-16) ​

Features ​

  • dsl: the public contract — declaration, binding, floor, readers (lot 4bis, #1263) (#1276) (c22cd5b), references #1216 #1010 #1280 #1281 #1282 #1285 #1154

    whyA bot's public contract as a top-level declaration named by the workflow's `contract:`: display name, responsibility, version, typed input/output ports with JSON defaults and file specs, deterministic criteria (`min_length`, `pattern`) with JSON parameters, visible effects. Harvested from #1216 (ADR-099; ADR-098 §16 amended: no `graph`, `port_policy` or `runtime_semantics` until a semantics runs under them).

3.149.4 (2026-09-15) ​

Bug Fixes ​

  • runner: retry final bank pushes and expose bank_failed (#1194) (6775622)

    whygitOpTimeout bounds one git subprocess, never the sequence. The push retry multiplies four network ops, and a run launched without --timeout carries no deadline for the live path to sit under, so the bank could pin a runner pod for attempts x ops x gitOpTimeout. bankContext now applies bankBudget on both arms, and still honours an operator who disabled per-op bounds.

3.149.3 (2026-09-15) ​

Bug Fixes ​

  • tools: interaction: must not strip an unrestricted node's native tools (#1278) (ccaf8e2)

    why`assembleEffectiveTools` promoted `ask_user` into a node's tool list whenever `interaction:` was set, unconditionally. Every sibling append in the same function is guarded by `len(effectiveTools) > 0` — board tools, runs tools, ultracode orchestration, claw's todo_write — each carrying the same comment: "Empty tools: means no restriction". The interaction branch was the only unguarded one of the class.

3.149.2 (2026-09-15) ​

Bug Fixes ​

  • two gaps the #1258 review surfaced — Vary replaced instead of appended, and a package no CI compiles (#1274) (53dd19a)

    whyBoth CORS sites wrote their own token with Header().Set("Vary", "Origin"), which discards whatever a middleware upstream recorded. The loss is invisible: the response still looks correct, it is merely cacheable across a dimension it genuinely varies on, so a shared cache can hand one client's representation to another. Found while reviewing #1258, where a middleware adds a Vary token and those two handlers silently dropped it.

3.149.1 (2026-09-15) ​

Bug Fixes ​

  • server: a missing build asset must 404, not masquerade as the SPA shell (#1258) (aef881d)

    whyThe SPA fallback answered every unrouted GET with index.html, /assets/* included. A content-hashed chunk is absent for exactly one reason — the document came from a different build than the replica answering — and 200 text/html turns that into "blocked due to a disallowed MIME type", which no client can act on and which caches as a broken script.

3.149.0 (2026-09-15) ​

Features ​

  • connectors: opt-in v2 response contracts, without replaying a confirmed mutation (#1266) (e1c0f05)

    whyResponse validation is a NEW authority, not strictness switched on over the existing descriptive schemas: those are a lossy projection (nullable, oneOf, writeOnly dropped, object inferred from properties, enums stringified), so enforcing them would refuse bodies the vendor legitimately sends. Contracts live in their own responses.json, are referenced explicitly per result case, and package format v2 is selected only when they are asked for.

3.148.2 (2026-09-15) ​

Bug Fixes ​

  • runtime: isolate subbot bundles across all four runners (#1195) (e8efd95)

    whyA resource scope is opened for ANY run carrying a bundle, contributions or a subbot node — `beginRunResources` says so — not only for a child that borrows the workspace's resources. Two places read the scope's existence as proof of borrowing, and both were wrong for the majority of runs that have one.

3.148.1 (2026-09-15) ​

Bug Fixes ​

  • runtime: replace stale fan-out outputs at convergence (#1187) (ee47e96)

3.148.0 (2026-09-15) ​

Features ​

  • cloud: verify subscription accounts and preview credential fallbacks (#1212) (694d5d6), references #945 #945

    whyThe oauth connections row is one space short of the column gofmt computes for the map literal, which is the whole of the test job's failure. No behaviour changes.
  • dsl: evaluate loop cap expressions at each crossing (#1192) (42b5048), references #1201

Bug Fixes ​

  • runtime: settle fan-out template evidence per item (#1193) (7ff4a66)

3.147.1 (2026-09-15) ​

Bug Fixes ​

  • billy: verify workflow delivery permission before analysis (#1199) (f7db671)

  • server: an assistant-mission sweep is joined after the drain and on a project switch, never inside its cancel (#1259) (60aabc8), closes #1254 #821 #848 #1250 #1257, references #1254 #821 #848

    whyrestartAssistantMissions started a sweep loop for the new coordinator and cancelled the previous one without anything ever waiting for it, and the shutdown did the same: a sweep mid-store-call returned after the cancel did, still writing into the store of a project that had been switched away from — and, in TestRestartAssistantMissionsIsRaceFree, into a temp dir the test was removing (#1254, the class of #821 and #848).

3.147.0 (2026-09-15) ​

Features ​

  • connector: preserve operation and auth identities across regeneration (#1211) (047a2cf)

3.146.10 (2026-09-15) ​

Bug Fixes ​

  • mcp: preserve safe stdio startup diagnostics (#1210) (17cdcc6)

3.146.9 (2026-09-15) ​

Bug Fixes ​

  • forge: preserve existing bot avatars on reconnect (#1196) (9cdcef8)

3.146.8 (2026-09-15) ​

Bug Fixes ​

  • review-pr: the scope guard must be a whitelist, not a list of failures (#1253) (84b9cd8), references #1246

    whyReview finding Ra43054 on #1246, and it is right.

3.146.7 (2026-09-15) ​

Bug Fixes ​

  • bots,server,studio: an example is bound to its path only when it is the file it names and parses clean; fragments are written before the mains (#1250) (243efe9), references #1248

    whyThird slice behind #1241: Revi's medium and question on #1248, and a local adversarial round on this diff before it reaches the gate.

3.146.6 (2026-09-15) ​

Bug Fixes ​

  • review-pr: zero findings out of zero files read is not an approval (#1246) (9406931)

    whyFound by running the merged bot against a real PR before pushing it to production — the validation the platform override was being held for.

3.146.5 (2026-09-15) ​

Bug Fixes ​

  • bots,server,studio: an example is served whole, published atomically, and the studio keeps the unit it binds (#1248) (2ddbfe5), references #1241

    whyFollow-up of the embed fix: Revi's second verdict on #1241 and a local adversarial re-attack of the same commit.

3.146.4 (2026-09-15) ​

Bug Fixes ​

  • golden-master: the ephemeral-set notice declines per set too (#1244) (dece1c0), references #1149 #1137 #1149

    why#1149 made `seal_holdout` decline PER SET: it keeps what `git ls-files` reports and relocates the rest. The predicate beside it did not follow.

3.146.3 (2026-09-15) ​

Bug Fixes ​

  • golden-master: the tree the gate judged must be the tree that existed (#1240) (f7178b5)

    whyA mutant's revert restores HEAD. So anything uncommitted when the gate runs is DESTROYED during the run, and every figure the gate then reports describes a tree that stopped existing partway through the measurement.

3.146.2 (2026-09-15) ​

Bug Fixes ​

  • studio,server: a companion workflow that imports opens as its unit, a fragment edit reloads the open document, a lost provenance is refused (#1235) (b790871), references #1225 #1164 #1010

    whyRevi's second verdict on PR #1225, after the queue had admitted it.

3.146.1 (2026-09-15) ​

Bug Fixes ​

  • review-pr: resolve the review base once, and refresh it first (#1230) (25794cd), references #1224 #1224

    whyA review took its scope from `git merge-base <base_ref> HEAD` on the local checkout. A workspace reused across runs carries whatever `base_ref` meant when it was made, and a merge-base against a stale base lands BELOW the branch point — so every file merged into the base since falls into the diff, and the review reports on code the branch never touched as if it had.

3.146.0 (2026-09-15) ​

Features ​

  • golden-master: the net HONOURS a declared lane exclusion instead of calling it noise (#1228) (a4e2e01)

    whyA multi-environment net can renounce ONE observation on ONE lane — a decision, written down and arbitrated, never a deletion. `corpus.json` already carried the declaration:

Bug Fixes ​

  • golden-master: a held-out set of zero is not a proof, and the gate now says so (#1152) (3fe5ab2)

    why`oracle_gate.converged` ends on `holdout_detected == holdout_total`, its strongest clause — and `0 == 0` satisfies it. A rite whose held-out set is empty converges on a term that measured nothing, which is the no-op confirmed as a success this bot exists to refuse in other people's deliveries.

3.145.1 (2026-09-15) ​

Bug Fixes ​

  • golden-master: the seal declines per SET, never per directory (#1149) (6ceb323)

    whyseal_holdout relocates the held-out set so the hardening loop cannot read it -- the seal was a sentence in a skill until it was made mechanical. A COMMITTED set is deliberately exempt: it awaits its own gate, and moving tracked files would leave uncommitted deletions a finalize refuses to merge while burning that set's single scoring on a gate that does not own it.

3.145.0 (2026-09-15) ​

Features ​

  • dsl: a bot in several files — import "lib/x.bot" read as one unit, one identity, saved by provenance (#1225) (309d6c1), references #1010 #1164 #1010

    whyThe first piece of the multi-file unit (ADR-098 §3, lot 3 of #1010): `import` is a keyword like `dsl`, read at the head of a file — after the header and the leading comments, before any declaration (E044 otherwise) — with a quoted, relative, slash-separated `.bot` path (E045 for the rest: absolute, a drive, `..`, `\`, NUL, junk after it); the same path twice is one import. The AST carries the imports as written, the JSON document as their paths, the writer puts them back where the parser reads…

3.144.1 (2026-09-15) ​

Bug Fixes ​

  • server: the merge-gate net must outlive the outage it exists for (#1224) (1f30ed3)

    whyA dead gating run was reachable by the reconciler's net for 60 minutes. The outage class that net exists for is a provider usage window, and a weekly one shuts for DAYS — so the net closed while the thing it guards against was still happening.

3.144.0 (2026-09-14) ​

Features ​

  • remote: a CLI launch can aim a run at a repository (#1161) (0c95277)

    why`POST /api/runs` has accepted `repo_url` / `repo_ref` / `connection_id` all along — the cloud runner clones the repo into the workspace before sandboxing, and the inbound-webhook path sets all three. `iterion remote runs launch` exposed none of them, so the only way to launch a repo-scoped bot with a checkout was a webhook or the studio.

3.143.0 (2026-09-13) ​

Features ​

  • ship the iterion assistant epic (#480) (6eea0ce), closes #482, references #14 #333 #334 #333 #333 #476 #476 #493 #481 #485 #486 #489 #485 #487 #488 #490 #491 #493 #566 #1135

    whyThe three dock presentations (closed / floating / docked-right), the lg breakpoint rule and the persistence helpers were private to FloatingChatPanel, so nothing outside /runs/:id could reuse them. They now live in @/lib/chatDock/dockState with openedDock() taking an injectable viewport width, which makes the breakpoint rule testable without a DOM. FloatingChatPanel keeps a ChatDock type alias so the run console's existing imports keep resolving.

3.142.6 (2026-09-13) ​

Bug Fixes ​

  • mongo: preserve future run state across older writers (#1185) (2ea41f0)

3.142.5 (2026-09-13) ​

Bug Fixes ​

  • studio: generate Monaco vocabulary from the DSL registry (#1183) (ba7e6a4)

3.142.4 (2026-09-13) ​

Bug Fixes ​

  • runtime: release pristine worktrees after early refusals (#1181) (f176718)

3.142.3 (2026-09-13) ​

Bug Fixes ​

  • engine: refuse unsupported async backend capabilities (#1180) (9f47bca)

3.142.2 (2026-09-13) ​

Bug Fixes ​

  • import: emit profile two drafts with DSL string quoting (#1179) (3b78eba)

3.142.1 (2026-09-13) ​

Bug Fixes ​

  • studio: restore run deep links after sign-in (#1176) (1b001e6)

3.142.0 (2026-09-13) ​

Features ​

  • bots: concise Revi reviews with linked run details (#1173) (7f695b3), references #1172 #1172

    whyKeep clean reviews to one visible sentence and retain actionable details in inline comments or explicit fallbacks when anchors are unavailable. Move review scope and method into the existing collapsed run details, and ask only questions that require a material maintainer decision.

3.141.0 (2026-09-13) ​

Features ​

  • bots: show Revi AI run details in a collapsed review footer (#1167) (6a25a6a), references #1166

    whyShow served models and harnesses, requested effort, and engine token counters without changing findings or gate semantics. Refs #1166.

3.140.3 (2026-09-12) ​

Bug Fixes ​

  • review-pr: the merge step no longer pins a gpt review to the Claude wire (#1160) (240d066), references #1150

    why`mono_family` picks which reviewer JUDGES; it never moved who MERGES. `agent converge` sits on every path and carried a hardcoded `backend: "claude_code"`, so selecting the gpt family bought a review that completed and then died on the Anthropic weekly cap at the aggregation step. A family switch that still requires the other family is the half-wired shape the parity doctrine calls a defect — measured today, when the deployment's seven-day window crossed its hard cap and every claude_code run…

3.140.2 (2026-09-12) ​

Bug Fixes ​

  • connector: the guarded client is carried by the VALUE, and a cleartext origin is named (#1150) (302b949)

    whyThe executor accepted any non-nil client. A default one and the guarded one have the same type, so nothing in the path could tell them apart, and the whole SSRF posture rested on one production site remembering to pass the right one — a convention held today only by there being exactly one such site, and due to break at the second (the cloud tier, which builds its own client).

3.140.1 (2026-09-11) ​

Bug Fixes ​

  • golden-master: a spent held-out set is refused before the boot, not after the replay (#1117) (51e8170)

    whyThe held-out REUSE check needs nothing the application provides: spent_fingerprints reads committed audit directories, mutant_fingerprint hashes a mutant directory, and held_meta is in hand a hundred lines earlier. It was nonetheless the last statement of the gate -- after app_up and the entire corpus replay, inside the try whose finally tears the application down.

3.140.0 (2026-09-11) ​

Features ​

  • botscaffold: a gallery of eight canonical shapes behind bots create --template and the studio builder, each held to its form (lot 1 of #1010) (#1114) (bb6b02b), closes #1110, references #1110 #1110

    whyThe five templates of the bot-creation gallery all rendered the same graph — one campaign agent, `campaign -> done`, from the one main.bot.tmpl — and differed by prompt and metadata only; the forms the catalog bots are made of (a campaign under a deterministic gate with a bounded loop and a typed fail, a reviewer fan-out with a compute convergence, a plan under a human gate, a verified action, …) existed nowhere at a size an author could read whole, and a Verified Action had no .bot instance in…

3.139.0 (2026-09-11) ​

Features ​

  • sec-audit: the capped findings can travel in the envelope, so triage is not pinned to one backend (#1141) (5551f72)

    whytriage reads the scanner findings through json_paths — it opens the files the tool nodes wrote. That works only where triage shares a filesystem with those nodes, and exactly one backend does: the sandbox-routed claude_code. `claw` is in-process and never enters the sandbox (delegate.go: "In-process backends (claw) refuse to start when this is set"), and `codex` refuses to run inside one at all (codex.go: "cannot run inside Iterion %s sandbox with the pinned SDK").

3.138.2 (2026-09-11) ​

Bug Fixes ​

  • sec-audit: the deep scanner's retry resumes instead of starting over (#1139) (97db319)

    whydeepsec exits 1 as soon as ONE batch errored, so the node's single retry is reached far more often than "the pass crashed" suggests — and it opened a FRESH run every time. Every batch the first attempt had already investigated was re-investigated and paid for again, under the same bound that had just expired, which on a large repository is the difference between finishing and timing out twice.

3.138.1 (2026-09-11) ​

Bug Fixes ​

  • dsl: an escaped quote in post_create reaches the shell as a literal quote (#1086) (9cb9ea2)

    whyA `"..."` DSL string is lexed in legacy escape mode unless the file opts into `## strict-escape: on` — and no bot in the catalogue does. Legacy mode keeps every \X VERBATIM, so a backslash-escaped quote survives into the shell, which reads \" as a literal quote CHARACTER. The command then runs with quotes inside its arguments instead of around them.

3.138.0 (2026-09-11) ​

Features ​

  • connector: the connector catalog — deterministic nodes over generated packages (P0) (#1119) (603d2a1), references #1072 #1073 #1072 #1092 #1092 #1067

    whyA connector's operations are data — a method, a path, flat typed params, a typed result, closed error classes — generated from the vendor's own API description and refined by an authored overlay. One model serves both offers: the deterministic `tool … action:` path and the MCP facade differ in who chooses the arguments, never in what the call is.

3.137.0 (2026-09-11) ​

Features ​

  • sec-audit: the deep scanner's findings leave the pod (#1104) (e68d353)

    whyThe deep scanner writes its findings to a file inside the sandbox, and the pod is destroyed with the run. The published envelope carries the PATH, so a pass that dies AFTER it — at triage, at the jury, on a provider usage cap — takes the whole contribution with it.

3.136.3 (2026-09-11) ​

Bug Fixes ​

  • sec-audit: the deep scanner reports what it did, and its timeout escalates (#1100) (8e82c61)

    whyTwo defects, both measured on real runs, both of the same family: a failure that leaves no readable trace.

3.136.2 (2026-09-11) ​

Bug Fixes ​

  • runtime: a convergence its whole fan-out failed lost every incoming mapping (#1120) (4745fd9), references #559 #1113 #484 #484 #559 #1113 #1118 #484

    whyWhen a fan-out stabilizes without a single branch producing output — every branch failed under `best_effort` (#559), or a `fan_out_each` fanned over an empty collection (#1113, the twin site) — the convergence node ran with NO incoming `with` mapping at all. Not just the ones reading the dead branches: also the ones reading a durable parent output or a var, which the failure never touched. A `tool` node was then handed the literal `{{input.x}}` in its command, since shell rendering deliberately…

3.136.1 (2026-09-10) ​

Bug Fixes ​

  • server: the origin gate refused in silence, so its own safety was unobservable (#1108) (5c0f3f8)

    whyWidening the CSRF boundary from 70 hand-picked handlers to every state-changing /api route left one question open: is it refusing anything it should not? The gate answered the caller with a 403 and recorded nothing, so "no legitimate client is being refused" and "we have no way to see one" produced identical evidence — an empty grep. That is how the board-MCP transport stayed an inference: sandboxed claude_code and pi POST to /api/v1/mcp/board, which the gate covers, and the claim that their…

3.136.0 (2026-09-10) ​

Features ​

  • dsl: the registry's value lists are held to the compiler, a block's remedy to its host, and worktree: is checked (C142) (#1103) (142755f), references #1092 #1084 #1094 #1010 #1010

    whyRevi's second verdict on #1092 named the class behind two guards that lot 1a added: the registry's value lists were proven to the PARSER only, and the "outdent it to the <host>'s level" remedy was right only because every multi-host block happened to call enterBlock.

3.135.1 (2026-09-10) ​

Bug Fixes ​

  • runs: a credential with no fingerprint still names the tier that paid (#1109) (290b798), references #1105 #1105

    why#1105 collected the tiers inside the FINGERPRINT harvest, which is keyed on an audit identity and skips a credential that has none — setOAuthFingerprint refuses an empty stamp outright, so an unstamped forfait never even enters the map that walk reads. A run funded only by one reported no tier at all: an empty answer where the GRANTED log line says `<unstamped>`, which is the confident silence the field exists to remove, on exactly the odd credential an operator is most likely to be chasing.

3.135.0 (2026-09-10) ​

Features ​

  • runs: a run says which tier paid for it, and says it again after a resume (#1105) (75e4413), closes #991, references #992

    why`run.bot_source_tier` answers "which bundle served this launch". Nothing answered the question an operator asks at least as often — "who paid for this run?" — although the publisher computes it: it resolves the credential through five tiers and names the winner in ONE INFO line. Answering for a run whose logs have rotated meant not answering at all.

3.134.0 (2026-09-10) ​

Features ​

  • home: add a playful open source invitation above the footer (68e9c80)
  • home: consolidate stack capabilities around Devbox (083b3e6)
  • home: highlight Devbox and language toolchains (31aaf3d)
  • home: showcase end-to-end bot missions (b3f8fa7)
  • web: invite visitors to star Iterion on GitHub (3a40098)

Bug Fixes ​

  • docs: render DSL with-map forms as inline code (2ef083b)

    whyVitePress interpreted the bare braces in the property table as HTML attributes, breaking the docs build. Format the syntax as inline code in the generator and introductory table, then regenerate the reference.

3.133.0 (2026-09-10) ​

Features ​

  • dsl: a property registry the parser is held to, and E012 names the remedy (lot 1a of #1010) (#1092) (57564e7), closes #1084, references #1084 #1094

    whyLot 1a of #1010 (#1084). The DSL's property surface was written by hand in five places (the parser's switch arms, the EBNF, the readable grammar, the root SKILL.md, the whats-next quickref) and drifted: the quickref's canonical examples did not parse until lot 0 rewrote them, and an unknown property was refused with E012 and a pointer to a table.

3.132.8 (2026-09-10) ​

Bug Fixes ​

  • credusage: a usage listing that hides its filter reads as a frozen meter (#1097) (32fa082), references #1087 #1087

    whyThe admin per-credential route answers for ONE tier and defaults to `platform` when the caller names none — correct, since no tenant view can show that tier, and nothing in the response said so. Both routes also labelled every answer with a `month` they derived from `time.Now()`, while reading no `?month=` at all: a caller asking for August was served September, byte for byte, under an August-shaped question.

3.132.7 (2026-09-10) ​

Bug Fixes ​

  • forge: a fixer rewriting a branch says so, instead of being discovered at push time (#1064) (40a0ba7)

    whyA FIXER run holds no required check. markGateInFlight claims `gate_context`, and a fixer has none — it answers a review rather than gating the merge — so for the tens of minutes it works, NOTHING on the pull request says it is there. The only signal that ever existed is a comment, and only in one case: a quota park, whose pause notice already tells the reader not to push. A fixer that is simply working is silent.

3.132.6 (2026-09-10) ​

Bug Fixes ​

  • runner: a per-credential spend never disappears without a line (#1090) (dda7468), closes #1052, references #1087

    whyFour declines dropped an attempt's per-credential metering in silence: no counter wired, no credentials on the context, and — the one that matters — a resolved slot carrying no fingerprint. Its sibling one line above (no slot at all) already warned; this one just `continue`d.

3.132.5 (2026-09-10) ​

Bug Fixes ​

  • sec-audit: a failed scanner stops leaving an output file behind (#1079) (dd24c6d)

    whyscan_health judges coverage from the filesystem: an output file that exists and parses counts as "that scanner ran". A tool that runs, FAILS, and still leaves a parseable artifact therefore reads as full coverage over a broken toolchain.

3.132.4 (2026-09-10) ​

Bug Fixes ​

  • auth: an org admin was offered teams the switch then refused (#1083) (8bb175b)

    why`buildOrgTree` lists every team of an org for its admins, synthesizing a `RoleAdmin` grant — deliberately, since `canManageTeam`/`orgAdminOfTeam` already let them write to each of those teams. `SwitchTeam` never learned the same rule: it had a step-in for super-admins only, so every other team came back `403 user is not a member of the team`. The studio builds its switcher from the first and calls the second, so the click did nothing at all, with no message. Measured on prod: 19 teams offered,…

3.132.3 (2026-09-10) ​

Bug Fixes ​

  • dsl: empty blocks have a written form, an include never resolves against a relative name, C141 on a use of an empty group (#1067) (140102a), references #1010 #1015

    whyThe block half of the empty-header class #1050 closed for declarations (Revi's R7f55fb on that PR): `budget:`, `memory:`, `compaction:`, `mcp:`, `auth:`, `cursors:`, `recovery:`, `sandbox:` (and its `build:`/`network:`) and the four top-level blocks were written as a bare header when empty — reachable from a plain file whose only property is zero-valued (`max_cost_usd: 0`, `args: {}`) and from the canvas document's `{}` — and a bare header did not parse (E002), so the save guard refused the…

3.132.2 (2026-09-10) ​

Bug Fixes ​

  • docs: the docs site has not built since the browser-security page landed (#1077) (a613c3e)

    why`check-links.mjs` resolves this site's github blob links against the real tree, and `studio/src/lib/monaco.ts` is not a path in it — the module is `monaco.tsx`. One character, and `pnpm -C docs build` exits 1 on it, so every push to main since d01f80707 (08:54Z, six commits) has failed to publish the documentation.

3.132.1 (2026-09-10) ​

Bug Fixes ​

  • runtime: a bot's devbox.json reaches the driver bots actually run on (#1061) (505c54c)

    why`devbox.json` next to a `main.bot` is the documented, durable way for a bot to declare the binaries its steps need — and until now it was declined on the kubernetes driver, which is where bots actually run in cloud. The event said so (`no host bind mount on this driver`), but only to whoever went looking: nothing failed except, later, the step that needed the tool.

3.132.0 (2026-09-10) ​

Features ​

  • credentials: a tenant may hold a CHAIN of forfaits, not one per kind (#1065) (ed8c522), references #945

    whyThe store held exactly one OAuthRecord per (owner, kind), enforced by a unique index. That made the credential chain no deeper than the tiers themselves: an operator holding four Claude subscriptions could wire two — their org's and the deployment's — and had no way to say "try these in this order". On 2026-09-08 that ceiling stopped every claude_code run on a production deployment for three hours: the org forfait's five-hour window closed, the single tier behind it was already spent on its…
  • credusage: the repository becomes an accounting dimension (#1069) (9f0247f), references #950

    why"A quota per repo" had no subject to attach to: credusage.Key was {fingerprint, provider, tier, tenant} × month, and nothing carried the repository a run targeted into accounting — so "one busy repository is eating the shared subscription" was unanswerable while it happened.

3.131.5 (2026-09-10) ​

Bug Fixes ​

  • model: a captured turn records the backend that produced it (#1062) (e8ec6eb), closes #1053

    whydelegateHooksFor already receives the node's RESOLVED backend and threw it away, stamping delegate.BackendClaudeCode on every captured turn. pi fires the same OnTurnFinished hook (pi_rpc.go), so every pi turn was persisted under another backend's name in store.TurnCheckpoint.Backend.

3.131.4 (2026-09-10) ​

Bug Fixes ​

  • security: close the same-site CSRF hole on the API, and the browser gaps beside it (#1058) (d01f807)

    whyThe Origin check was opt-in per handler, and opt-in drifted: 70 of 247 state-changing routes called requireSafeOrigin, leaving the BYOK keys, team and org secrets, OAuth forfaits, platform LLM credentials, forge connections, webhooks and org administration ungated.

3.131.3 (2026-09-10) ​

Bug Fixes ​

  • deps: close the five docs-chain advisories without shipping an alpha (#1060) (1aa36f5), closes #625

    whyAll five open Dependabot alerts live in ONE chain: vitepress 1.6.4 pinned vite 5.4.21, which pinned esbuild 0.21.5. Moving vite carries esbuild with it, so four of the five are one fix, and @babel/core is the fifth.

3.131.2 (2026-09-10) ​

Bug Fixes ​

  • dispatcher/native: a refused inotify watch no longer freezes the board index until restart (#1051) (7871eac), references #1047 #1020

    whyAdversarial re-attack of the previous two commits (2 high, 2 medium, 2 low), each with a red-then-green test:
  • forge: a store that could not answer is no longer read as "you have no App" (#1059) (bd01eeb), closes #969, references #969

    whyResolving a connection's GitHub App returned one `ok bool`, so "this tenant registered none" and "the store could not be read" were the same answer. Every caller then acted on the wrong one, and the two residuals #969 left behind turn out to be one user-visible defect.

3.131.1 (2026-09-10) ​

Bug Fixes ​

  • dsl,cloud: lot 0.5 transport — complete JSON codec, lossless unparser, includes travel with the AST (ADR-098) (#1050) (d57f911), closes #1012 #1010 #1015 #1013, references #1015 #1013 #1010 #1039 #1010 #1012 #1049 #1010 #1015 #1013 #1015

    whyThe AST JSON codec is what a cloud launch puts on the queue and what the studio saves through. It dropped four constructs: `group` / `use` (a workflow using them compiled from its .bot and failed with C008/C001 after the round-trip), the `as foreach` clause on an edge (silently gone) and a named resource pool (`slot: ["a", "b"]` came back as a bare capacity). So a bot with any of them behaved differently on a cloud launch than locally. The codec now mirrors every declaration — groups through…

3.131.0 (2026-09-10) ​

Features ​

  • persist artifact restart contracts (#1020) (5b81de3), closes #1047, references #1039 #1051 #1047 #1051

    why`Rewind` validated every persisted artifact contract before it knew its pivot — i.e. before `downstreamOf` computed what the rewind invalidates — so an artifact the operation was about to supersede refused the whole operation. Under the `enforce` context policy that made the loop the command exists for unusable: `--auto` targets the node whose declaration just changed, which is precisely the node whose contract no longer matches, and the rewind is what clears it (its tombstone carries no…

3.130.0 (2026-09-09) ​

Features ​

  • review-pr: read the issues a PR links, with no per-repo configuration (#1017) (7d95dc6), closes #1014, references #123 #1003 #997

    whyTicket conformance shipped in 0.6.0 behind `tracker_api_base`, a var an operator had to pin per repo. Nobody did: across the 16 connected GitHub repos it is set on ZERO of them, so a PR saying 'Fixes #123' was reviewed without anyone reading #123. The check existed and never ran.

3.129.2 (2026-09-09) ​

Bug Fixes ​

  • usage: an aggregated token count no longer claims to be input (#1052) (ba4f730), references #992

    whyA CLI delegate reports ONE token count and no split. Every usage surface booked it under `input_tokens`, which kept a sum correct and made the named field a lie: measured on ovh-prod, every credential filled exactly one of the two fields and zeroed the other, so each per-token ratio, cache-hit reading and input/output share taken from the public endpoint was wrong, with nothing on the row saying so.

3.129.1 (2026-09-09) ​

Bug Fixes ​

  • forge: a re-provision erased three fields it does not own (#1046) (22b660b)

    whyProvision rebuilds RepoIntegration from the REQUEST and Updates it, and the update replaces the whole document. Any field the literal omits is therefore erased — silently, on a live repo, with a 200 in reply.

3.129.0 (2026-09-09) ​

Features ​

  • runner: let a read-only bot decline the workspace checkpoint (#1042) (a7db190)

    whyThe mid-run workspace checkpoint force-pushes the pod's tree as `iterion/run-<id>-checkpoint` on the run's OWN remote — the repository the bot was pointed at — and reads that tree with `git add -A`, so a bot's scratch directory rides along: untracked, and nothing ignores it.

Bug Fixes ​

  • forge,server: a key iterion cannot read is not the forge being down (#1030) (daf30e7), references #969

    whyThe App-token mint signs its JWT from a stored private key BEFORE it opens a socket, so a key that is not parseable PEM fails inside what reads like a pure remote call. Three handlers default to 502 for anything the classifier does not recognise — a sound default, since an unclassified failure on a forge round trip really is the forge's — and they were reporting GitHub as broken for iterion's own stored key.
  • golden-master: a duplicate reference group is proved by a mutant, not by a note (#1009) (28af2c2)

    whyThe gate refused on ANY two byte-identical references, unconditionally. Its own message said the refusal is not always right — "on a refusal lane two entries legitimately capture the same 302, and the second is a control proving a mutant moved only the first" — and then offered no way to say so. The corpus answered in `note`, which is prose, and the gate reads data. So a net whose duplicates were every one of them justified could never converge.

3.128.0 (2026-09-09) ​

Features ​

  • dsl: authoring lot 0 — # comments, positioned diagnostics with fix lines, every doc fence compiled (#1010) (#1039) (b2067fd), references #936 #1012

    whyOutside a string, a prompt body or a `|` block scalar a `#` never meant anything in the language; it was a lexer error the parser then reported as an unknown property named '#'. Measured on the repository's own documentation on 2026-09-09: of the 48 ```iter fences that failed to parse, 25 failed on exactly that — the maintainers reach for `# note` as naturally as any model does, and the DSL quickref skill taught it in its canonical examples.

3.127.2 (2026-09-09) ​

Bug Fixes ​

  • forge: a deployment that moved could never repair its own hooks (#1032) (206a010)

    whyProvisioning short-circuits when the bot set and the event set already match, and that test never looked at the address the forge is actually calling. But the hook URL is not a property of the request: it moves when the deployment's public URL moves, and when a connection starts or stops pinning a base of its own.
  • runtime: a node that failed still spent, and the run never booked it (#916) (de5bf14)

    why`recordBudget` runs on the success path only. A node that failed returned its result beside the error — with the pass's cost stamped on it, which is what the delegate has always done and what two fixes today made reliable — and nothing read it. So the run's totals, the daily spend cap and a lending donor's ledger all missed whatever the failing node burned. On a long agent node that is a whole session, and the runs that fail are exactly the ones that burned the most.

3.127.1 (2026-09-09) ​

Bug Fixes ​

  • pipelines: the control center served a team the origin of its own fork (#1031) (a99fec7), references #871

    whyThe pipelines board is selected per team in cloud (cloudBoardResolve), but its bot was resolved tenant-free and launched by filesystem path — the fifth surface of the #871 class, and the last one still outside the tiered resolver. Two silent consequences: a team that forked a catalog bot got the CATALOG bundle on its own cards, and a bot only that team authored could not be carded at all (a stored row's Path is blanked, so MainFile() had nothing to launch).

3.127.0 (2026-09-09) ​

Features ​

  • admit runs before execution with context contract (#1019) (4c360b1)

3.126.0 (2026-09-09) ​

Features ​

  • persist versioned workflow execution context (#1018) (a0b4945)

3.125.1 (2026-09-09) ​

Bug Fixes ​

  • cost: an OpenAI turn was priced as if it had cost nothing to send (#1034) (6bb10e3), references #992 #992

    whyEvery OpenAI-family call through claw reported ZERO input tokens. Both endpoints reported the count and both translations dropped it: the chat-completions path parsed `prompt_tokens` and never read it, and /v1/responses sends `message_start` bare and built its usage from the output half alone. Sweeping claw for any assignment of input tokens returned two hits — the Anthropic SSE client and bedrock.

3.125.0 (2026-09-09) ​

Features ​

  • runview: expose shared workflow diagnostics (#1008) (d9d136f)

3.124.0 (2026-09-09) ​

Features ​

  • studio: redesign the cloud home around orchestration (#1028) (e5a711d)

    whyCloudLanding is one of App.tsx's few eager view imports — PublicTopBar lives in the same module and renders on /marketplace, outside the lazy route tree. The redesign's static `import CloudHome` therefore pulled the whole product page into the entry chunk: CloudHome + PlatformFeatures + StackCompatibility, ~40 lucide icon modules, 11 @lobehub brand icons and the 348-line cloud-home.css, downloaded and parsed on first paint by every authenticated operator — an audience AuthGate never shows it to.

3.123.3 (2026-09-09) ​

Bug Fixes ​

  • server: a tenant the store says is GONE is not a blip to launch past (#1027) (998baac), references #969

    whygateLaunch is the choke point every cloud launch surface crosses — the REST launch and resume, the inbound webhooks, the retry sweeper, the board dispatcher. It read the caller's team and, on ANY error, admitted the launch: quotas are operator policy, and a transient Mongo blip must not wedge a whole deployment.

3.123.2 (2026-09-09) ​

Bug Fixes ​

  • golden-master,modernize: a repairable certificate refusal no longer ends the run (#1007) (e8154c8)

    whyRf213cf. `lot_gate -> extension_provenance when forged` lands on a `resumable: false` fail declared ahead of the repair loop, so every shape that set `forged` ended the campaign outright. The justification written above that edge — "dropping the act block breaks ledger_append_only" — holds for two of the five sites that set it, and `ledger_append_only` is `head_txt.startswith(base_txt)`: it pins only the text BELOW the run's base, so a block appended during the segment can be narrowed or…

3.123.1 (2026-09-09) ​

Bug Fixes ​

  • bots: a campaign verify node refuses a dirty tree instead of judging it (#995) (9b0e19b), references #807 #807 #799

    whyA tool node whose whole contract is "judge HEAD" was judging whatever the previous attempt left on disk. Measured once: a golden-master gate ran 7,676 s until the pod's exec stream broke, the engine classified the failure NETWORK_TRANSIENT and re-executed the node on the same tree — where a mutant the harness had applied was still there. The second attempt judged a mutated program and called it the lot's; the run finished not-converged with hours of budget left.
  • server: an avatar recorded after a failed store write is iterion's fault, not the forge's (#993) (cf69634), references #969 #969

    whyforgeUpstreamStatus returns 0 to mean "NOT an answer from the forge", and its own doc says the caller then answers with its fault status — "Only that arm may be a 500." The avatar route rendered that arm 502 Bad Gateway, so a persist failure AFTER an upload that had already landed on the forge was reported as a forge outage: the exact inversion the classifier was written to end, running the other way. Sentry, alerts and any client retrying on 502 were told a third party broke when iterion's own…

3.123.0 (2026-09-09) ​

Features ​

  • credentials,teams: an org can lend its own LLM keys, and a team has a lifecycle (#1000) (ebbae7e)

    whySharing a key across an org's product teams had no home. The API-key walk only sees the team's and the user's rows, and secrets.OrgOwnerKey — despite its name — keys a TEAM forfait. The only way to share was to copy the credential into every team: N writes per rotation, N places to forget one, and no way to tell whose spend was whose. Measured on the prod instance, where one Claude forfait is already duplicated across two teams.
  • forge: a connection can pin the base its hook URLs are built from (#1011) (df80e5b)

    whyHook URLs are derived from the deployment's public URL, which is right for every connection until one of them cannot reach that host. GitLab refuses any webhook URL outside its instance-wide outbound allowlist with "Invalid url given" (HTTP 422), and listing a host is an administrative act on the forge's side, not ours. One such forge therefore pinned the public URL of the WHOLE deployment: moving to a new domain meant either leaving that forge behind or not moving.

3.122.3 (2026-09-08) ​

Bug Fixes ​

  • secrets,runner: a codex forfait refreshes itself, and an unrefreshable one stops being silent (#977) (bf05b07)

    whyNothing refreshed the ChatGPT (codex) forfait. The server's worker skipped the kind outright when no client id was configured, and the runner's per-run refresher handled only Anthropic, "left to the CLI / store worker" — which was in turn skipping it. Measured on a real deployment: a forfait last refreshed on 2026-08-29 was still being served on 2026-09-08, and the only symptom was a run failing its first LLM call with `401 Provided authentication token is expired`, ten days and one layer away…

3.122.2 (2026-09-08) ​

Bug Fixes ​

  • server: a team-scoped write must land in the PATH team, not the caller's tenant (#997) (#1003) (d6d1fe7)

    whyThe auth middleware stamps ONE tenant — the caller's JWT — while authorization is checked against the team in the path, and canManageTeam deliberately admits a super-admin (or an org admin) on a team that is not their active one. When a handler forgot to re-scope, the row landed as (scope_team = path team, tenant_id = caller's team): invisible from both list endpoints, invisible to the target team's runs, and answered 201. The bot then ran without the credential it had been given.

3.122.1 (2026-09-08) ​

Bug Fixes ​

  • runner: a new generation erased the checkpoint it should have read (#990) (6f3926e), references #988

    whyThe workspace checkpoint is force-pushed to ONE ref per run, so the first push of a new runner generation destroys what the previous one left. That is harmless when the resume continued the same tree, and irreversible when it did not.

3.122.0 (2026-09-08) ​

Features ​

  • runs: expose persisted workspace checkpoint recovery (#988) (e4a7f60), closes #972

    whySurface the latest successful checkpoint event in inspection and unavailable commit listings, with provenance and a quoted fetch hint. Preserve no_baseline, final-bank fields and merge eligibility; report read failures and require validation of recovered work. Closes #972.

3.121.5 (2026-09-08) ​

Bug Fixes ​

  • test: persist Git fixture maintenance opt-outs (#987) (1b79f49), closes #974

    whyProduction Git commands invoked by tests do not inherit gittest.Cmd flags. Persist both opt-outs in the fixture common config and verify real Git resolution from source and linked worktrees, keeping a separate command-level control. Fixes #974.

3.121.4 (2026-09-08) ​

Bug Fixes ​

  • sandbox: a custom workdir over an oversized recipe streams the script, not a wrapper that re-embeds it (#967) (ac7b8d2)

    whyAddresses R6a7f93. The custom-workdir path streamed `cd '<dir>' && exec bash -c '<script>'` through `sh -s`. That keeps the script off the HOST argv, but the in-pod shell then re-issues execve("bash", ["bash", "-c", "<script>"]) — and MAX_ARG_STRLEN applies to that exec too. E2BIG was relocated into the pod, not removed, for precisely the shape this streaming exists to serve: an oversized `<shell> -c <script>` combined with a non-default WorkDir.

3.121.3 (2026-09-08) ​

Bug Fixes ​

  • golden-master,modernize: an extension is acted by the net's subbot only — the gate knows it by the subbot's commits and certified blobs (#882) (e46e357)

    whyMeasured on a live campaign: a lot filed an extension request in one commit and acted it in the next — added the reference, appended the act block — and the harness's provenance rule, which refuses a request and an act introduced by the SAME commit, saw nothing; the lot's own file became a reference of the net that judges it, exempted as a pure addition, and the lot landed with a caveat.

3.121.2 (2026-09-08) ​

Bug Fixes ​

  • server: the reads that DESCRIBE a bot resolve the tier that runs it (#946) (#971) (8608ae5), references #871

    why#871 made every launch surface resolve `team -> platform -> baked`, so a team's fork now runs on its board cards, triggers, schedules and webhooks. The reads that describe those launches stayed on platform-over-baked, so the same delivery ran the fork and was described by the origin — with no diagnostic, because both answers are well-formed.

3.121.1 (2026-09-08) ​

Bug Fixes ​

  • distinguish human waits from stalled runs (#965) (2bea34b)

  • model: an in-process retry resumes the session the dead attempt opened (#958) (f713312), references #952 #912

    whyWhen the LAST in-place attempt is the cheap one, the node reported the cost of nothing: attempt 1 spends an agentic session and hits a transient wall, attempt 2 cannot even spawn and returns an empty Result, and `result, err = fn()` overwrote the figure with zero. The caps, the org monthly cap and a lending donor's ledger all read that figure.

3.121.0 (2026-09-08) ​

Features ​

  • observability: a node served through a facade says so on the run record (#926) (b3f7efb), references #474

    whyWith a tenant z.ai key, the claude_code delegate's default precedence routes every node through the Anthropic-shaped facade, which answers the requested claude id with the model it aliases it to. Declared and effective ids agree, model_drift stays silent, and the only trace was the _session_fingerprint buried in the node output. Measured 2026-09-07: three claude_code probe nodes declared claude-fable-5 / claude-opus-5 / claude-opus-4-8 all carried `facade:https://api.z.ai/api/anthropic` and…

Bug Fixes ​

  • claw: a stale codex-cli never downgrades the ChatGPT identity, and a JSON Schema type array parses (claw b6e34a39) (#917) (34ea228)

    whyThe ChatGPT-Codex backend gates model availability on the `version:` header; iterion resolved it from a host `codex --version` probe and let that value win over claw's baked release. A stale binary in an image then downgraded every OAuth call: measured 2026-09-07 on the cloud runner (codex-cli 0.139.0 shipped in the image), the backend answered "The 'gpt-6-astra' model requires a newer version of Codex" while the same model is served to the 0.144.6 release claw now presents.
  • runview: list a run's artifacts from its artifact_index when the directory is not on this host (#919) (7bf70d8)

    whyListAllArtifacts walked runs/<id>/artifacts on the local filesystem and returned an empty list when the directory was absent — which is every run on a cloud server pod, since the directory lives on the runner that wrote it. Measured 2026-09-07: a run whose events carried two artifact_written entries (one of them a `publish:`) answered `{"artifacts": []}` on GET /api/runs/{id}/artifacts while GET /api/runs/{id}/artifacts/{node}/0 served the body.
  • server: a launch field the request does not declare is refused, not dropped (#949) (5ff01b3)

    whyFrom the client, a parameter that was refused and one that was swallowed are the same answer: the request is accepted, the value does nothing, and the caller learns it from the behaviour of whatever it started rather than from what it was told.

3.120.2 (2026-09-08) ​

Bug Fixes ​

  • runtime: a queued raise_budget must reach the boundary that is about to kill the run (#938) (62829f3)

    why`POST /runs/{id}/raise-budget` on a run busy inside a long node answers "queued … the run is busy in a long node and will apply it at its next boundary — it is not lost". For `bump_loop` that is true. For the budget it was false in the one case the command exists for.
  • sandbox: a tool recipe too large for one argv element streams through stdin on kubernetes too (#937) (03370e7)

    whyThe kubernetes driver passed a `sh -c <script>` recipe to `kubectl exec` as a single argv element. Linux caps ONE argument at MAX_ARG_STRLEN (32 pages = 128 KiB) — a limit no ulimit raises — so a large interpolated recipe fails the fork with E2BIG before the pod is ever contacted:

3.120.1 (2026-09-08) ​

Bug Fixes ​

  • delegate: a delegation that died still names the session it opened (#952) (50d6574)

    whyThe claude CLI announces its session id on `system/init`, the first thing it emits. It was logged there and dropped. A session that then dies mid-stream never produces a ResultMessage, and the failure path builds its result from that message alone — so the delegate returned a failure that could not name the session it had just spent minutes or hours filling.

3.120.0 (2026-09-08) ​

Features ​

  • credentials: accept a bare Claude setup token, and fingerprint the token (#948) (78b1957)

    whyThree things a session paid for this morning, none of which the existing runbook answered.

Bug Fixes ​

  • platform-bots: four defects inside the shadow guard, found after #851 merged (#944) (7ad9695)

    whywarnIfOverrideShadowsNewerBake fires for both origins — storedLaunchBot calls it for `team` rows as well as `platform` ones, and versionsBelow has a dedicated team branch — but the remedy baked into the message was unconditionally the platform one: `iterion remote admin bots push bots/<slug>` and `DELETE /api/admin/bots/<slug>`.

3.119.0 (2026-09-08) ​

Features ​

  • pipelines: Retry from zero — a board action that forces a FRESH run (#954) (27efc84), closes #496 #494, references #495

    whyOn a needs-attention card, Retry only restages the ticket and lets whoever claims it decide what "retry" meant: the studio's admission loop mints a fresh run, a live `iterion dispatch` resolves `last_run_id` and RESUMES the dead one from its checkpoint (resolveRunID -> LastRunForIssue -> resumableRunID). So on a dispatcher-owned board Retry is effectively *Resume* — beside a menu that already offers "Resume from checkpoint" as a separate, deliberate action. For a run that died in a way resuming…

Bug Fixes ​

  • runtime: cancel a node's whole process group, not just its shell (#935) (#955) (cd3a01b)

    whyA tool node runs its recipe through `exec.CommandContext(ctx, "bash", "-c", …)` without Setpgid, so cancellation killed the shell and nothing else. A job the recipe backgrounded survived, kept the inherited stdout pipe open, and `cmd.Output()` never returned: the run reported cancelled while the work it paid for ran to completion — burning wall-clock, a cloud pod, and writing the workspace that finalization was about to capture.
  • server: a team's forked bot serves every launch surface, not only the manual one (#940) (6f8bcf1), closes #871, references #871 #946

    why`resolveBotSource` — the chokepoint the board dispatcher, the trigger spine, the cloud scheduler and the inbound webhooks all cross — hardcoded an empty team id, so the team tier of `team -> platform -> baked` applied on the studio button alone. A team that forked a catalog bot in the studio editor ran its fork by hand and the baked/platform bundle on every board card, trigger, schedule and webhook review, with no diagnostic: documented as functional while inert.

3.118.1 (2026-09-08) ​

Bug Fixes ​

  • runner: reserve the last usage-window retry for the authoritative reset (#922) (#953) (4e74590), references #684 #684

    whyA usage-window retry arms on the EARLIER of the failed credential's own reset and the reopening of a credential the launch's walk passed over (#684). That earlier wake is speculative — the skipped credential may be refused too — yet it spends an attempt of the same budget: every arming `$inc`s `retry_state.attempts` and `ScheduleRunRetry` refuses past `max_attempts`.

3.118.0 (2026-09-08) ​

Features ​

  • golden-master: an upload is a multipart request, and its boundary does not move (#902) (1921402)

    whyA corpus that declares a file field had it flattened by `urlencode`, which serialises a structured value through its repr: the application received a form field whose value was the TEXT of a Python object, refused the request for the wrong reason, and the reference recorded THAT refusal as the behaviour. An observation point that cannot express its own request observes nothing — and the lot that needed one stopped, correctly, rather than record two rejections as if they were the product.

Bug Fixes ​

  • sandbox: export the seeded forfait config dirs on the container env (#915) (167c40b)

    whyThe run's Claude Code / Codex forfait is delivered into the sandbox and seeded into CLAUDE_CONFIG_DIR / CODEX_HOME after start, but only the claude_code and claw delegates pointed their own spawns at those dirs. Every other process in the container — a tool node, a devbox script, a scanner driving the claude-agent-sdk — inherited the bare container env and ran unauthenticated while the credentials sat next to it.

3.117.0 (2026-09-08) ​

Features ​

  • dsl: warn (C249) when a branch-spawning router names one target twice (#934) (c122095)

    why`fork -> a` declared twice on a `fan_out_all` router validated clean and still does: the compiler keeps both edges, the run finishes, nothing says anything. But `fan_out_all` spawns one goroutine per outgoing edge and derives every branch id from the TARGET (`branch_<router>_<target>`), so the two executions wear one id — they collapse onto one output slot at convergence and, since durable branch checkpoints, onto one `BranchCheckpoint` whose cursor each goroutine overwrites, which lets a…

Bug Fixes ​

  • bundle,runner,server: a bundle may declare the engine it needs, and three surfaces refuse what they cannot run (#942) (4afe964), references #858 #881 #881 #858 #881

    why#858 layer 1. Layer 2 (C138, builtin arity at compile) shipped in #881; this is the complementary half — the arity check catches a builtin the evaluator does not KNOW, this catches everything else a newer engine brought.

3.116.5 (2026-09-08) ​

Bug Fixes ​

  • dispatcher: spare a run parked on a paused subbot descendant from the stall watchdog (#932) (4a953c0), references #558

    whyA dispatcher-owned parent that reaches a `subbot` node whose child parks on a human gate blocks in runview.AwaitSubbotTerminal, polling the child's run record once a second. That poll appends no event, so the entry's watermark — fed only by DispatchSpec.OnEvent — ages past the stall timeout while nothing is wrong. reconcileStalled then interrupts the run, the retry re-enters the same engine, and a review left open over a weekend eventually burns max_attempts. A production deployment was working…

3.116.4 (2026-09-08) ​

Bug Fixes ​

  • platform-bots: a stored override that shadows a newer baked bundle is no longer silent (#851) (8ce6262), references #742 780/#785

    whyA platform or team bot override outranks the baked catalog at every launch surface — that is the tier's purpose. The cost, unmeasured until now, is that a bundle pushed once keeps serving after a later release bakes a newer one for the same slug: the image moves, the bot does not, and nothing says so.

3.116.3 (2026-09-08) ​

Bug Fixes ​

  • board,forge,server: a failure must not answer like a success — three seams that could not report one (#928) (a650a51), references #891 #904 #893 #884

    why`AggregateLabels() []LabelUsage` had no error to report a failure with, so boardmongo's only option after a failed `listAll` was `return nil` — and the studio label picker, `iterion remote labels` and every vocabulary consumer read a transient Mongo failure as "this board uses no labels", with an operator re-creating labels that already exist.

3.116.2 (2026-09-08) ​

Bug Fixes ​

  • cloud: snapshot complete bot collections before dispatch (#918) (e348289)

3.116.1 (2026-09-08) ​

Bug Fixes ​

  • golden-master: report broken selftest fixtures before indexing verdicts (#910) (6af47f6)

  • recovery,retrypolicy: two structural verdicts EXECUTION_FAILED promised could be outlasted (#921) (9a27477)

    whyA provider that refuses the MODEL — an id no backend claims, a model the account may not use, one whose minimum client release the image is behind — answered about the caller, not about the request. No sample differs and no wait helps, so the automatic resume above it can only spend pods to be told the same thing.

3.116.0 (2026-09-08) ​

Features ​

  • golden-master: the two debts behind a 0/0 held-out figure are fields, not sentences (#901) (0d7ffe3)

    whyThe gate line checks `holdout_detected == holdout_total`, which is vacuously true at 0/0. The report already said so — in a NOTICE, and this file's own comment names why that is not enough: "a notice string is where debts go to hide". One of the two debts got a field when it was measured (`holdout_awaiting_gate`); the other stayed prose.

Bug Fixes ​

  • runner,alert: a checkpoint compares the WORK, and a timer tick is not the run working (#906) (bddfcd0)

    whyTwo halves of one defect in the workspace checkpoint, found by the piloting session before the runner was even bumped — reproduced on a throwaway repository, three ticks a second apart.

3.115.3 (2026-09-07) ​

Bug Fixes ​

  • delegate: the two terminal returns that walked past the spend stamp (#908) (4e55cf0)

    whyA delegation that ends badly still SPENT. The caps, the fallback chain's carried spend and a donor's ledger all read the cost from the output map, so a terminal return that skips the stamp records nothing — the money is gone either way, only the accounting disappears. `typedFailure` exists as that choke point, and its own docstring says so; two returns walked past it.

3.115.2 (2026-09-07) ​

Bug Fixes ​

  • forge,server: a head repository the credential may not read is declared, and a forge refusal answers as the forge, not as an iterion fault (#903) (f47ff6e), closes #887 #888 #893

    whypkg/forge/gitlab's source-project lookup mapped BOTH 403 and 404 onto an empty headProject with a nil error. A permission ANSWER — the project exists, this credential may not read it — therefore arrived at every caller as the same value a merge request that names no source project produces, and the only thing standing between that and "therefore the base project" was that SameRepoAs happens to fail closed on an empty name. Nothing in the function said the result must not be trusted, and the…

3.115.1 (2026-09-07) ​

Bug Fixes ​

  • server: retry PR lookup failures before board launches (#897) (b9d6e2c)

3.115.0 (2026-09-07) ​

Features ​

  • golden-master: the judge reads the declaration it was asked about, and the verdict says which one (#894) (7bc419d)

    whyA net can declare a second ENVIRONMENT for the same corpus — a second database engine, a second runtime — and judging it means booting the app the other way and replaying the SAME references. Until now the harness opened `config.json` in two places, hard-coded, so a gate command passing `GM_CONFIG=<other>.json` ran a judge that never read the variable.

Bug Fixes ​

  • sandbox: never pass kubectl --request-timeout — it discards the in-cluster config and every apply dials localhost (#899) (d9a3a7c), references #823

    whyDeployed to production at 18:27Z and rolled back at 18:50Z: with the flag set, every kubernetes sandbox creation fails at "apply file secrets secret".

3.114.0 (2026-09-07) ​

Features ​

  • runner: a copy-based sandbox's work leaves the pod while the pod still answers (#898) (f111dd4)

    whyOn a driver whose workspace is a tar COPY inside a pod, nothing a run produces leaves that pod before teardown: the export runs once, at the end, and every push the runner performs reads the exported clone. A pod that dies hard therefore takes the whole run with it, however well the run committed.

Bug Fixes ​

  • board-sync: a human's move out of Blocked on the roadmap board is the reopen (#895) (169be9d), closes #839

    whyThe terminal sink (ADR-096 §5) protects a card from a MACHINE resurrecting it. A drag on the bound GitHub board is not a machine: it is the operator's hand, arriving through the only channel they have. The project pass refused it all the same, so a card moved Blocked -> Inbox in production stayed `blocked` for ever while the roadmap showed Inbox, with one server log line to say so.

3.113.3 (2026-09-07) ​

Bug Fixes ​

  • bots: lot_verify reads the oracle report with one decoder pass per opening line — the enclosing object wins, a named report never loses to an unnamed one; no guessed block start, no budget (#865) (0c48563), references pre-#815

    whyRevi (R46d257, medium) on the bounded scan: the block's start was guessed among the two nearest opening lines and the window's FIRST one, so an indent=0 report (a list's objects at column 0) preceded by any brace-first line was lost and a green oracle typed ORACLE_NOT_RUN. The guess is gone: json.JSONDecoder().raw_decode(window, idx) parses in place from each "{" line of the window (lstripped: a logger's indentation leaves a block without a start otherwise), from the last upward; a noise line…
  • review-pr: the run budget follows the measured workload — $48, sized on two deaths at $36 and $30 and the engine's 90% wall (#868) (a1e76e4)

    whyMeasured: the merge gate died twice on one revision of a 7-file PR, budget exceeded at cost_usd 36/12 then 30/12 on the automatic relaunch, and posted no verdict; a second PR of the same size died the same way. The cap was sized on a 2026-09-03 sample (median $3.6, p95 $10.5); the review that reproduces its findings and answers the fixer's rounds costs three times that now. A required check that dies is worse than one that costs: the cap follows the workload, the per-run --max-cost-usd override…
  • runner,runtime,dsl: a deterministic failure is never auto-resumed, every failed run says why on its timeline, a run records the build that launched and ran it, and a stacked PR clones its base (#881) (02b928a), closes #859 #857 #697 #880, references #858 #850 #774 #697 #697

    whyFunction calls parse generically, so a `compute` expression calling a builtin with an argument count that builtin does not accept compiled clean and only failed at run time. On a cloud launch that costs a sandbox, a clone and a plan phase before the failure is even visible — and the failure is deterministic, so nothing about retrying it helps.

3.113.2 (2026-09-07) ​

Bug Fixes ​

  • ultracode: the model gate admits the Claude 5 family, shared by the compiler and the studio endpoint (#780) (9d39bc5)

    why`reasoning_effort: ultracode` warned C089 on every model but Opus 4.8, and the studio's effort-capabilities endpoint offered the mode on Opus 4.8 alone — two copies of the same predicate, written when Opus 4.8 was the only model carrying the orchestration half. The Claude 5 family (Opus 5, Fable 5.1) carries it too, and the campaign bots now default to claude-opus-5: on them ultracode compiled with a stale warning and the studio never offered the mode. The prerogative itself was never gated on…

3.113.1 (2026-09-07) ​

Bug Fixes ​

  • dsl,boardmongo: a group param expands in one pass, and a cascade budgets one round-trip per write instead of the whole sweep (#889) (565ae40), closes #879 #883

    whyThe bind loop called strings.ReplaceAll once per key, so a bound value containing `{{params.<other>}}` text was re-expanded on a later iteration, and Go's random map order decided which value won: two compiles of the same .bot could produce different node commands, edge conditions and loop caps.

3.113.0 (2026-09-07) ​

Features ​

  • modernize: a lot QUALIFIES a moved reference before asking to re-record it (#890) (21f4839)

    whyThe net stays off-limits and a moved reference stays a regression until proven otherwise; what the doctrine did not say is that naming WHICH of three things moved is the lot's job, and that the gesture differs:

3.112.25 (2026-09-07) ​

Bug Fixes ​

  • forge,server,credentials,runtime: a 404 is typed by resource, a publish grant refuses the launch it cannot serve and dies with its run, and a squash message stays in the run range (#886) (1178fa6), closes #812 #826 #820 #747

    whyEvery provider's 404 collapsed onto ErrHookNotFound at the one shared mapping point, so a pull request that does not exist reported "forge: hook not found" — and GitHub answers 404, not 403, for a resource a credential may not see, so a token short of pull_requests got the same webhook message. StatusErr now types a 404 by OPERATION: the hook operations keep their sentinel (deprovision reads it as "already gone"), everything else becomes a *NotFoundError naming its own call, and every 404…
  • model: ChainHooks forwards every EventHooks field, and run.* resolves in the single template pass (#878) (2b4510f), closes #836 #817

    whyChainHooks composed 18 of the 22 func fields and dropped OnAssistantText, OnUsageCap, OnUsageProgress and OnOrchestrationStall. runview chains every ExtraHooks entry through it, so any run launched with one — the Prometheus exporter (ITERION_PROMETHEUS_ADDR) is the shipped case — lost those four event families silently: no live usage_progress for a supervisor's cost_gt monitor, no usage_cap, no delegate_stall, no assistant text in the timeline. Nothing failed; the callbacks simply never fired.
  • sandbox: every setup phase is bounded on both drivers, a tool_result crossing the IPC is clamped, and a promise on a dropped bind is withdrawn (#877) (186d434), closes #823 #837 #834, references #815

    whyTwo setup phases still ran on the bare run context, so a stall in either held the run with no `sandbox_started` event, no typed failure and no redelivery until the outer max_duration fired.
  • webhooks,gate,branch-improve-loop: a fixer stops when its PR closes, never pushes onto a merged one, and a gate that cannot decide fails closed (#875) (f334053), closes #847 #831 #863 #773, references #863 #855 #783 #788

    whyFour defects in the inbound lanes, all measured on production deliveries.

3.112.24 (2026-09-07) ​

Bug Fixes ​

  • golden-master: a stat-recorded path is stat-compared whatever today's size bound (#873) (6164e1a)

    whyThird review round of the residue follow-up. The legacy migration of a "st:" record raised the size bound in the same change: a record written under 8 MiB for a file that hashes under 256 MiB today could never match — an untouched operator file in that band was permanent residue, the very stop the migration was written to prevent. The comparison is scheme-aware now: a stat record is answered by the stat form of the path whatever the current bound; the legacy branch migrates digits only.
  • server,trigger,cloudsched: the trigger spine and cron schedules launch through the org gate, and an emit meters what it fans out to (#872) (a6cec38), closes #844

    whyThree cloud launch surfaces sat outside gateLaunch, so an org launched past its suspend, its concurrency cap, its launch rate and its monthly run/cost caps, unmetered.

3.112.23 (2026-09-07) ​

Bug Fixes ​

  • delegate: the Workflow tool is withheld from every non-ultracode claude_code node — the reviewed content can no longer arm a multi-agent orchestration (#869) (9fbb5f7), closes #867, references #780 #785 #788 #780 #785

    whyClaude Code arms its multi-agent Workflow tool on the word "ultracode" anywhere in the prompt, and a node's prompt carries the content it works on. Four revi/review runs on 2026-09-05 (PRs #780 and #785, both ABOUT ultracode) each launched one or two background Workflow orchestrations from a plain `high`-effort judge node, reached 1.2–3.2 M session tokens and died at $14–63 against a $12 cap — the data switched the node into a mode the operator's effort never granted.

3.112.22 (2026-09-07) ​

Bug Fixes ​

  • golden-master: a type change is a dirty path, a big file's fingerprint round-trips, an unreadable subtree is undecidable (#866) (17681ca)

    whySixth review round. The porcelain status set lacked T, so a type change (a tracked file swapped for a symlink) was neither recorded before the mutant nor seen after a revert that left it: the one disposition that lets the gate through, on the fail-open this PR closes. T is a status.

3.112.21 (2026-09-07) ​

Bug Fixes ​

  • golden-master: a revert that says 0 without restoring leaves a residue the sweep names and stops on (#856) (3ececd4)

    whyRevi's open question on the fourth round: "reverted" let the gate through whenever revert.sh exited 0, even when git status still showed changes; the note said so, nothing stopped, and record mode has no dirty check — an under-specified revert.sh would have references sealed over the residue, the harm the sweep runs in record mode to prevent.
  • pluginsource: the rename is the test-and-set, so an immutable tree is never retired on a stale read (#862) (a05060a), references #854 #822

    whyThe early-accept added for #854 is a READ, and the retire it was meant to prevent is decided two syscalls later — nothing serialises them across publishers. Replay with pinned A/B/C over one cache dir: C reads `dest` absent; A renames its tree in; B's rename loses ENOTEMPTY and enters the fallback; C's `os.Stat` now sees A's tree and retires it, so `dest` is ABSENT; B's read finds nothing and returns the ENOTEMPTY cause. The exact message that ejected #822 from the merge queue, still reachable.

3.112.20 (2026-09-07) ​

Bug Fixes ​

  • bots: lot_verify's report search is bounded — a chatty stdout with no report is typed in seconds, not minutes (#852) (e6a1f12)

    whyMeasured by the reviewer on the shipped function: for every stdout line ending in `}` the search retried a parse against every earlier line whose lstrip started with `{`, re-joining the slice each time — 30 s of CPU for 2000 lines of 33 characters, 86 s at 500, linear in line length, on the very no-report path the reader exists to type.

3.112.19 (2026-09-06) ​

Bug Fixes ​

  • server,cloudpublisher: the board dispatcher launches through the org gate, a run no credential tier can fund is refused at publish, and a launch give-up reaches the needs-attention lane (#850) (0841f18), closes #841

    whyA card the dispatcher gave up on before any run existed (the launch attempt cap) carried a give-up stamp with no run id, and GiveUp.Current was bound to a run id, so the pipeline board filed the card among the done tickets: a card nobody could launch was invisible. The stamp now carries a `launch` marker — current for the card in its state whatever run it points at, part of the stamp's identity, round-tripped by both store twins — and the ticket card (or the run card of an older attempt) takes…

3.112.18 (2026-09-06) ​

Bug Fixes ​

  • runner,runtime,sandbox: a stale launch redelivery never restarts a requeued run, the resumed budget is re-stamped post-clamp, and the kubernetes post_create phase is bounded (#822) (193585f), closes #714 #718 #719 #723 #720

    whyA `queued` doc is the one status runResolveDoc restarts from the entry node, so a redelivery that reaches it from an earlier life of the run re-spends everything the checkpoint exists to save. The admission gauntlet now reads the queued arm explicitly: a launch message published before the doc's current QueuedAt belongs to a finished attempt and is dropped (the attempt now queued carries its own delivery), and a queued doc holding a checkpoint resumes instead of restarting.

3.112.17 (2026-09-06) ​

Bug Fixes ​

  • pluginsource: a peer that published the same pinned checkout first has published this fetch's tree (#855) (9144328), closes #854, references #822

    whyTwo fetchers sharing a cache dir both stage the same key; the first rename(staging → final) wins and the loser's fails ENOTEMPTY. The loser already re-read the final path and accepted it — but that read could find NOTHING, because publish renames an existing tree ASIDE before its own rename. With N publishers the sequence is: A publishes, B's rename loses, C retires the path to make room for its own copy, and B — reading between C's retire and C's rename — sees no final path and reports…

3.112.16 (2026-09-06) ​

Bug Fixes ​

  • delegate: a terminal verdict on the formatting pass is returned, not retried; a structured answer is an answer; a 403 is a refusal (#845) (8202bcf)

    whyThird review round of the facade-render fix. The formatting loop assigned every rendered verdict to fmtErr and retried the attempt once, so a dead credential, an unavailable model or an exhausted window bought a second CLI spawn against a provider that had just refused, and re-filed the same usage evidence. Only the transient class is retried now (renderRetryable); the rest returns typed at once, as the recovery pass already did.

3.112.15 (2026-09-06) ​

Bug Fixes ​

  • server: a refused launch gives the board card back and retries with a backoff; the attempt cap files it blocked with the reason (#840) (558325b), closes #814, references #839

    whyIssue.LaunchRefusal (attempts, last reason, last instant, not_before) is the cloud dispatcher's retry bound for a claimed card whose launch the run service refused before any run started. Written fenced (SetLaunchRefusalOwned, a BoardStore contract method on both twins), kept across the give-back transition, cleared by a stamped run (SetLastRun / SetLastRunOwned - a launch happened) and by an operator Reopen. Coordinator.ListDispatchable skips a card until its not_before, in the query, for the…

3.112.14 (2026-09-06) ​

Bug Fixes ​

  • billy: a delivery reserve out of the duration cap, a typed decline the platform honours, a structural drift gate, and no masked exit status in verify.sh (#830) (5166823), closes #789 #779 #705 #706, references #770 #705 #706 #779 #789

    whyhas_drift_gate read the verify script one line at a time and counted a quiet diff only when the failing exit appeared on the SAME line, so the commonest real shape -- a multiline "if ! git diff --quiet ...; then ... exit 1; fi", which is what this repo's own openapi:check target writes -- was rejected. Run 01a072b5 delivered eight commits, logged VERIFY OK, and still returned exit 3 / DRIFT GATE MISSING, leaving the merge gate red on a green tree.
  • forge,webhooks: one App client per connection with its real slug, tokens minted from the recorded grant, a 403 that arms the preflight, a connection-first reply gate, and a GitLab fork MR that names its source (#846) (e2b5fe7), closes #711 #710 #717 #708 #728, references #711

    whyTwo AppClient gaps that share a fix site.
  • golden-master: the harness reverts a mutant an interrupted gate left applied, at the start of the next (#807) (7a70bbc), references #799

    whyA stream cut, a SIGTERM or a pod kill between apply.sh and revert.sh leaves a mutant in the tree. The next gate on that tree judged a mutated program and called it the lot's: measured on a verify node retried on the same tree after its exec stream broke at 7 676 s — the oracle reported the mutant's file as "not committed", the build gate went red on a package that exists on the bank, and the run finished not converged with hours of budget left.
  • server: a publish grant must belong to the run that carries it (#849) (0aab3d2), closes #825

    whyThe publish token is a launch VAR, and injectForgePublishVars honours a caller-pinned one. Every reader then proved the grant SELF-consistent — its connection belongs to its team, its repo matches the pull request, its host matches the connection — and a grant minted for another tenant passed all of them. None asked whether that tenant was the run's. So an authenticated operator of team B holding a team-A token could have iterion comment on team A's pull request, write team A's REQUIRED commit…

3.112.13 (2026-09-06) ​

Bug Fixes ​

  • server,store: the pr_url launches pass the fork guard, a typed PR-closed end reason, a claimed launch_error retry, and gate notices that name a remedy that can reach the run (#824) (463394b), closes #683 #702 #722 #721 #713 #715 #712, references #713

    whyapplyPRLaunchContext — the door the studio launch, the remote CLI/MCP launch and the cloud board coordinator use to stamp a PR onto a run — carried no fork guard, while the five webhook lanes were made fail-closed by #683. Same launch pair (<base>.CloneURL + the PR's head branch), so a head branch that does not live in the base repo could be checked out against a same-named branch of the base repo and a code-pushing bot would commit onto it.

3.112.12 (2026-09-06) ​

Bug Fixes ​

  • runner: the per-run clone's git commands leave no maintenance process behind (#829) (da8a2f0), closes #821, references #813 #821 #828

    whyThe merge-queue build of #813 ejected the PR on a cleanup, not an assertion: `TempDir RemoveAll cleanup: unlinkat …/deploy-onyxia-…/.git/ objects: directory not empty` — something was still writing under the plugin-source cache checkout after the test had passed.
  • runtime,bots: an oracle that never ran is not a RED — the run-files env var goes with its bind, the report goes to a temp file of the run (#815) (f517637), references #795 pre-#795

    whyMeasured on the pod backend, four modernize lots in one night: gate green, references intact, and "oracle RED" on one line — the gate wrapper's `cannot create /iterion/artifact-files/gm-last-report.json: Directory nonexistent`. The harness never ran: the redirect died first. One lot diagnosed it and blocked, two were stopped by the loop budget guard, one spent 173 minutes repairing an environment. Some 20 run-hours, no verdict.
  • sandbox: a sandboxed claw node relays its tools, retries and turns (#835) (e6c4b40), closes #811, references #805

    whyThe `__claw-runner` relay carried the LLM steps (#805) and nothing else, so everything the in-container loop observed died at the container boundary: the studio timeline of a sandboxed claw node showed LLM steps and no tools, an in-container permission denial left no audit, a supervisor's `tool_*` monitor never armed, the plan checklist stayed empty, a retry or a compaction round read as a silent gap, and `iterion fork --turn` had no anchor at all.
  • server: a live 401 marks the connection revoked; only a forge that answered "no" earns the vouch (#833) (5cd4877)

    whyA failed WhoAmI now takes three shapes instead of one. A credential the forge rejects outright is the reconnect problem the revoked-status rung already names — a 422, forced or not, because no force can carry an upload the forge would refuse the same way. A forge that answered but would not describe the account (insufficient scope) is the one case the operator can vouch for: a 409 unforced, tolerated under force. Anything else — a spent budget, an unreachable forge, a 5xx — is not an answer: a…

3.112.11 (2026-09-06) ​

Bug Fixes ​

  • git,pluginsource,runview: a git command iterion runs into a directory it disposes of leaves nothing running (#827) (cdfdc65), closes #821, references #813

    whyThe merge-queue build of #813 ejected the PR on a cleanup, not an assertion: `TempDir RemoveAll cleanup: unlinkat …/deploy-onyxia-…/.git/ objects: directory not empty` — something was still writing under the plugin-source cache checkout after the test had passed.

3.112.10 (2026-09-06) ​

Bug Fixes ​

  • forge: the GitHub-App client serves the pull/CI API on scoped tokens — the card PR panel works on App connections (#809) (89fafb5), closes #777

    whyforgeAdminFor yields a forgegithub.AppClient for a github_app connection, and that type did not implement forge.PullClient, so pullClientForConn failed its assertion: GET|POST /api/v1/native/issues/{id}/pulls, …/pulls/{n}/ci and …/pulls/{n}/merge answered 501 on the connection kind the connect wizard creates by default, while the same card worked on a PAT connection.

3.112.9 (2026-09-06) ​

Bug Fixes ​

  • delegate: a facade's bracketed API-error render is an API error, not a node's answer (#818) (6930fd8)

    whyMeasured on a golden-master run behind an Anthropic-shaped facade: the campaign node finished in 2.5 minutes with its structured output built from "API Error: [500][Operation failed][<request id>]" — the CLI's render of an upstream 500 — and the graph continued: 283 minutes and the next node's budget spent on a request that was never acted, the run ending unconverged.

3.112.8 (2026-09-06) ​

Bug Fixes ​

  • secrets,server,cli: the OAuth token endpoints are env-overridable, a refused personal OAuth credential is audited, and iterion secret set shape-checks what it stores (#819) (ca46d33), closes #725 #726 #727

    whyThe Anthropic forfait's authorize URL, redirect URI and scopes each read envOr("ITERION_OAUTH_FORFAIT_ANTHROPIC_...", default). The token endpoint the auth-code exchange and the refresh worker both POST to was a bare const, under a comment promising the same per-deployment override — so an OEM-repackaged CLI or a proxying deployment could move three endpoints of four and keep refreshing against the vendor's host, silently.

3.112.7 (2026-09-06) ​

Bug Fixes ​

  • server: the board dispatcher never claims a card it cannot launch, and a column iterion wrote on its own authority is not reflected onto the bound board (#813) (9a4a6a0), references #798 #798 #798 #798

    whyTwo store-level seams the #798 fix rests on. Coordinator.ListDispatchable is the cloud dispatch tick's candidate query: unclaimed cards in a launch column THAT CARRY A BOT, filtered in the query because the batch is capped and bot-less cards (never written, so the oldest) would fill every batch. ListEligible stays the sweeps' listing. Issue.StateReason persists the provenance of the card's last transition - the same value its state event carries, derived once by native.StateProvenance - so a…

3.112.6 (2026-09-06) ​

Bug Fixes ​

  • dsl,runtime: three silent constants — run.* in every data mapping, a typed compute int, outputs.* in tool commands (#816) (0b2138c), closes #791 #792 #797

    whyresolveRef's RefRun arm served `id` and nothing else, so a fail node's `message:` (and an edge `with`, an `emit` payload, a subbot `with:`) rendered `{{run.max_cost_usd}}` / `{{run.elapsed_seconds}}` as an empty string with no diagnostic — C029/C036 accept the reference, the resolver dropped it. The very message a budget guard wants ("planning used X of Y, raise --max-duration and resume") could not be written from `run.*`.
  • server: a forge that will not describe the account asks for the operator's word, not a 502 dead end (#808) (bd94b5a)

    whyAn avatar apply on a connection of unknown kind asks the forge who the token is. When the forge refuses to say (a token without the scope, a forge without the field), the unforced apply answered a bare 502 that recorded nothing — so the studio card, which now only offers the vouch on a 409, repeated the identical 502 on every click and the forced apply the server still supports was unreachable from it.

3.112.5 (2026-09-06) ​

Bug Fixes ​

  • runner: a sandboxed claw node meters from its relayed steps, on the credential that served it (#810) (dc76093), closes #805, references #805

    whyA sandboxed claw node prices its call inside the container, from a cold process where neither the live registry nor the aggregator has a cache yet, so the committed table is what it falls back to — and gpt-5.6-sol, the model the campaign bots' cross-family plan review runs on, had no entry: 24 of the 33 plan reviews served on ovh-prod in the last week carried no cost_usd at all (#805).

3.112.4 (2026-09-06) ​

Bug Fixes ​

  • runner: an IR this runner cannot load is a verdict on the run, acked — not eight silent redeliveries to the DLQ (#806) (b693fd0)

    whyMeasured on a five-release server/runner skew: six runs (five of one team, one of another) went failed_resumable within 75 s with zero events and an empty final_error; the runner logs read "compile IR: N diagnostic(s)" on every delivery, 1 … 8, then "parking on DLQ". The compiled IR of a server ahead of the fleet does not load on the runner, the error came back generic, and the delivery loop did what it does with generic errors — while the run said nothing.

3.112.3 (2026-09-06) ​

Bug Fixes ​

  • server: a forced apply bails when /user never answers; the card vouches only on a 409 (#804) (66e63b5), references #803

    whyRevi's gate on #803 (R77fadf, R4a0ce8) and its assumptions: - Under force, a WhoAmI failure was swallowed even when it was the apply's own deadline expiring — the upload then ran on a dead context and stamped "context deadline exceeded" on the connection, blaming the avatar for a forge that does not answer. A genuine refusal is still tolerated; a spent budget bails with the accurate reason and records nothing. - The card sent force:true for every account not flagged as a bot, so the 409 branch…

3.112.2 (2026-09-06) ​

Bug Fixes ​

  • server: the avatar record rides its own budget, not the round-trips' deadline (#803) (75e8d0e), references #800

    whyRevi's gate on #800 (R8b0bd7): giving the apply one bounded context put the outcome record under the same 20 s deadline as the upload — so the slow forge, the failure worth recording, expired the context first and the record never ran (and an upload landing near the deadline answered 502 for an avatar that was live). The record now takes a 10 s budget of its own, detached from the round-trips; a forge that hangs is proved to leave its reason on the connection.

3.112.1 (2026-09-06) ​

Bug Fixes ​

  • runtime: resolve the template snapshot on every dispatch path — a fan-out branch, the llm router and llm_or_human rendered {{run.}} and {{outputs.}} as literals (#796) (72275c0), closes #763, references #763 #763

    why`model.WithRunID` / `WithTemplateData` were attached on the trunk dispatch path only, so the SAME node rendered two ways depending on how it was reached. Inside a `fan_out_all` / `fan_out_each` body a prompt kept its literal `{{outputs.x.y}}` braces and a tool `command:` substituted an EMPTY string for `{{run.id}}` — a silent constant, not a visible failure. The llm router and the `llm_or_human` LLM half never had the snapshot either.

3.112.0 (2026-09-06) ​

Features ​

  • chart: a priorityClassName for the server and runner Deployments (#802) (8a22cc7)

    whyThe platform pods (server, runner) and the sandboxed run pods carried no PriorityClass, so a run pod bursting on a node committed at 99 % of its requests was a peer of the runner that owns its run for the scheduler and for eviction. `server.priorityClassName` / `runner.priorityClassName` (empty = the cluster default) let a deployment rank the platform above the run pods it starts (SocialGouv/iterion#732: prod sets both to the cluster's `resource-burstable`; the run pods stay unclassed).

Bug Fixes ​

  • board: derive the degraded readout from the binding, not from one pass (#787) (cfaaa62), closes #775, references #793

    whyReconcileStatusOptions reported a LostColumn only on the pass that observed the loss: both append sites were guarded by `id != ""` and that same pass dropped the cached id. From the next pass on the state had no id, so `rep.Lost` was empty and `rep.Reason()` was "" — and the caller, reading "nothing lost", cleared `degraded` on the first unrelated adoption. The lost column then read healthy forever while every reflect onto it still refused, counted in `reflect_no_column` and invisible…
  • runtime: the loop budget guard's warning says its rule (#801) (1dfe7ec)

    whyA declined back-edge emitted budget_warning {remaining, needed, used, limit}; read on a run with more remaining than needed, it did not say why the loop stopped — the rule is that the next iteration would land at or past the 90 % threshold where the engine refuses to start any node, and remaining > needed does not contradict it. Measured on a run whose repair loop was declined with remaining 15 644 s and needed 13 154 s: used 13 156 + needed 13 154 = 26 310 ≥ 25 920 (90 % of 28 800). The event…

3.111.1 (2026-09-06) ​

Bug Fixes ​

  • server: a connection older than account_kind learns it before the avatar gate (#800) (d7e4138)

    whyRevi's gate on #794: the runbook handed the PIC operator the non-forced avatar command for connections that predate AccountKind — every one of them answered 409 needs_force, because the field is written at connect time only. The apply now asks the forge who the token is when the field is empty and records what it learns, so the bot gate judges the account and a group-token bot user needs no --force; a person's PAT still does.

3.111.0 (2026-09-05) ​

Features ​

  • brand: the iterion-bot mascot everywhere — forge bot avatars, favicons, app icon, docs logo (#794) (1d0661d)

    whyThe official iterion-bot GitHub account wears the mascot; the product still wore the hexagon, and every favicon/app-icon/logo copy was a hand-dropped file with no source of truth.

3.110.0 (2026-09-05) ​

Features ​

  • trigger: an effect KIND on the outbox — a native card move reaches the bound GitHub board through the durable outbox, the pass stays the net (#793) (7d01723)

    whyThe outbox row gains a discriminator so an effect that is NOT owed to a subscription can ride it: `launch` (ADR-094's original) and `projection` (ADR-097 §10's named follow-up).

Bug Fixes ​

  • runtime: a loop is priced on any entry into its body; a stale run-start mark is re-based on resume (#783) (d69189a)

    whyThe loop budget guard prices the next iteration by the last one, from a mark set when the loop is entered from outside. That mark was only set when the body was entered at the loop's HEAD (loop.Entries). A campaign bot's extension loop is entered elsewhere: its body shares the verify and gate nodes with the lot's own repair loop, so the run reaches it at verify, off its head, and the loop kept the mark the session baseline had set at run start. Its first crossing was then priced at everything…

3.109.0 (2026-09-05) ​

Features ​

  • bots: the plan-budget guard reads the run, and every coded refusal reaches the run as a typed fail (#790) (044904c)

    whybranch-improve-loop's plan-phase guard measured its own wall clock in `plan_scope_probe` (a `started_epoch` stamp) and compared spend against two vars mirroring the `budget:` block by hand. Both were workarounds for primitives that did not exist; both are now defects. A mirror var is not reached by `iterion run --max-cost-usd 200`, so a re-budgeted run kept refusing against the literal 75 nobody had updated — the guard's arithmetic silently disagreeing with the budget the run was actually under.

Bug Fixes ​

  • engine,bots: the engine's script and the judge's report stay out of the judged tree (#795) (7a792ac)

    whyAn extension act by pure addition, correct in content, was refused by the net's verify for three untracked paths the acting agent never wrote: `.iterion-script-*.py` — the tool node's own script, created by the executor at the workspace root so a bind-mount sandbox can see it — and `.golden-master/.last-report.json` — the gate wrapper's report, defaulting inside the net — beside the skills the engine mirrors. The gate that judges the tree's cleanliness read the engine's instrument and the…

3.108.3 (2026-09-05) ​

Bug Fixes ​

  • forge: the GitHub-App client serves the issue API on scoped tokens — the forge→board issue sync works on App connections (#776) (0025229), closes #781

    whyforgeAdminFor returns a *github.AppClient for a github_app connection and that type carried only CommentIssue, so `admin.(forge.IssueClient)` failed on the connection shape the studio's connect wizard creates by default. The forge->board sync answered 502 "provider github has no issue client" on demand and warned it every 5 minutes in the worker, so a bound team's cards were never hydrated and the ADR-097 project pass read skipped_no_card for every item, pass after pass. The autofix lane's…

3.108.2 (2026-09-05) ​

Bug Fixes ​

  • runner: retain exhausted lock deliveries in the DLQ (#770) (c804064), closes #703

    whyDelay lock contention by the configured lease interval and retain the final delivery in the DLQ with a durable event. Closes #703.

3.108.1 (2026-09-05) ​

Bug Fixes ​

  • cli: a subbot child on the CLI host executes in the parent's sandbox and in the parent's workdir (#778) (1a781bb), references #766

    why#766 made a child execute in its parent's sandbox on the cloud runner and in the studio's in-process service — and left the third host out: the CLI's subbot runner built the child engine without the parent's sandbox facts and without a workdir, so the child defaulted to the process cwd (engine.go: workDir defaults to os.Getwd() at Run time). A parent that swapped to a per-run worktree therefore handed its child the BASE tree: whatever the child committed landed in a tree the parent's gate never…

3.108.0 (2026-09-05) ​

Features ​

  • dsl: a node reads the run's budget through run.*, and a fail node carries a typed code, message and resumability (#764) (34a6850), closes #738 #739, references #737 #695 #670 #760

    whyA node that wants to reason about the run's budget -- "planning has used a third of max_duration, stop planning" -- had nothing to read: the `run` namespace resolved only `run.id`, and the `budget:` block's caps were compile-time literals. PR #737 had to self-measure wall-clock in a tool node and mirror the budget through two hand-maintained vars that drift from the block in silence; every phase-budget guard would have repeated it.

3.107.0 (2026-09-05) ​

Features ​

  • golden-master: sync-harness.bot — the judge's code reaches a target tree without a rite (#750) (12e35c6), references #765 #765

    whyThe harness is the net's decision procedure. Its content belongs to the net's owner and changes through a rite; its code is this bundle's and is repaired upstream — and until now the only path for a repaired judge into a tree that judges with it was a full rite: hours of agent work and a fresh held-out cycle for a one-line fix in a file no agent may edit. Measured on a live campaign: a rite re-materialised the harness with the bug still in it, and every lot on that tree was refused on the same…

3.106.1 (2026-09-05) ​

Bug Fixes ​

  • board: re-land #745's round-4 fixes the merge queue dropped, plus the round-5 findings and #759 (#772) (ef7b8c1)

    whySetStateFrom answers a card that drifted between the read and the write with (issue, changed=false, nil) — the operator got there first, which is not an error. The project import discarded that flag and read the nil error as success: it counted a transition the store never made, skipped the reflect, and recorded the board's status as synchronized, which makes decideProjectStatus a no-op from then on. The periodic worker repairs that on a later pass; the one-shot `iterion issue import --project`…
  • runtime,runner: a subbot child executes in its parent's sandbox — a pod of its own lost its commits (#766) (b272045)

    whyMeasured on the first subbot to run on a cloud pod: under the kubernetes driver the child engine started a sandbox of its own, the driver copies the workspace into a pod, so the child's commits lived in its copy and died with it (final_branch null, the commit unreachable from anywhere) while the parent, parked on the subbot node, resumed and re-judged an unchanged tree. A net's extension loop cannot converge that way. On the docker driver the same code happened to work: a second container…
  • store: protect run saves with document version checks (#771) (09fe07f), closes #701

    whyAdvance versions on partial writes in both stores; protect rename and rewind, and reload queued transitions before saving metadata. Preserve legacy documents and destination versions during migration. Closes #701.
  • webhooks: acknowledge authorization outages without launching work (#768) (d2d0def), closes #704

    whyRetain the forge failure in the delivery audit and return HTTP 200 across the command, conversation and review request lanes. Closes #704.

3.106.0 (2026-09-05) ​

Features ​

  • bots: the campaign fleet plans by default, refuses a missing repo typed, and carries Persy (#761) (7aa77ff), references #751 #752 #619 #751 #752 #619

    whyThe seven campaign bots (feature-dev, feature-gap-fill, branch-improve-loop, whole-improve-loop, test-coverage, e2e-coverage, app-dev) keyed their plan phase on plan_review, which ResolvePlanReview answers off on every single-provider deployment - so the commonest setup never planned, silently, under a var named after a review (#751).

Bug Fixes ​

  • golden-master: the certifier reads a request in the re-baseline ledger's spelling too; an act already at the base is not an extension (#765) (21ee24f)

    whyMeasured on the first extension request to reach the additions-only verdict in cloud: the request had been written as the ledger's own header taught — `expected_paths` and `entries` (the re-baseline idiom a worker had copied into the header) — while `extension_verdict` read only `paths` and `corpus_entries`. Every conforming request was therefore "smuggled": "1 added entry no acted request claims", "refs/<id>.txt is neither declared in the request's paths nor derived from a claimed corpus…

3.105.0 (2026-09-05) ​

Features ​

  • board: sync a team's GitHub Projects v2 board with the native board — ADR-097 (#745) (3e991ce), references #2 #1 SocialGouv/iterion#613

    whyAGENTS.md makes the Projects v2 board the roadmap truth and the native board the bots' operational surface, but nothing joins them: the board's Status/Area/Mode/Priority live in the GraphQL API, and no seam in iterion speaks GraphQL to a forge. So a human's "In progress" never reaches the dispatcher and a bot's "done" never reaches the roadmap.

3.104.2 (2026-09-05) ​

Bug Fixes ​

  • modernize: every refusal is a verdict the graph fails on; the gate's commit is HEAD plus one line; a timeout fails before any subbot (#757) (7503e2f)

    whyThird review round (two medium, three open questions), each reproduced and pinned.
  • runtime: elect a fan-out collector from predecessors inside the fan-out only (#758) (2f69832), closes #741, references SocialGouv/iterion#741 #741 #741 #741 #741

    whyA fan-out target that is ALSO reachable from outside the fan-out — the mono/dual topology review-pr and evolve ship, where a `condition` router reaches the same reviewer directly or through the fan_out_all — has two distinct predecessors, and the collector election counted both. The target itself was elected: its branch stopped before executing anything, the sibling ran the whole post-fan-out chain inside its branch, and the trunk then ran the target plus the same chain a second time. Observed…
  • sandbox: the claw bind-mount follows the effective backend; a pod that never got placed parks the run failed_resumable (#760) (cdcd63b), closes #724 #699

    why`containsClawNode` read each node's declared `backend:` and `fallbacks:` only. Launch-time model/backend overrides (`--backend '*=claw'`, `--model`, the studio override object, `RunMessage.model_overrides`) are applied at dispatch and never folded into the IR, so a workflow of `claude_code` nodes run with `--backend '*=claw'` got no in-container iterion binary: every node died on `exec: /usr/local/bin/iterion: no such file or directory`, and `sandbox_claw_routed_via_runner` stayed silent.
  • store/mongo: New retries a late server selection; the CI replica-set init fails without a PRIMARY (#754) (d6e7985), closes #729, references #698

    whyA fresh client's first server selection can outlast a single 5 s ping on a loaded host while the replica set is healthy — the handshake is late, not the primary absent. New pinged once and turned that lag into a boot failure, and into a conformance harness that ejected a green PR from the merge queue (#729: "rs0 primary elected" printed, then the 5th fresh client saw ReplicaSetNoPrimary and PR #698 was thrown out).

3.104.1 (2026-09-05) ​

Bug Fixes ​

  • webhooks: the GitLab note handler routes every slash command generically — /revi approve and the fork guard reach GitLab (#753) (6f336f4), closes #683

    whyGitLab addresses merge requests as a REST resource separate from issues (an MR and an issue can share the same iid in one project), so CommentIssue's /issues/:iid/notes endpoint would land on, or 404 against, the wrong resource for a caller replying on an MR. Adds the MR-scoped counterpart posting to /merge_requests/:iid/notes, needed by the upcoming GitLab /revi approve reply lane.

3.104.0 (2026-09-05) ​

Features ​

  • credentials: a refresh writes only the tokens, refusals earn a rest and stay visible, and every credential's spend is metered by nature (#748) (1906ab1), references #656 #629 #610 #624 #629 #690 #629 #629 #624 #629 #641

    whyThe three refresh paths did Get -> RefreshRecord -> whole-record Upsert, so a rename committed between the read and the persist was reverted to the label the refresh had read a provider round trip earlier. The rename side was already a store-level $set (SetAccountLabel); this is its mirror.

Bug Fixes ​

  • modernize: the contract is not the worker's to rewrite — typed only_lot refusal, done written by the gate, rewrites refused before the gate (#734) (a430ddc), references #737

    whyThree defects of one class, measured on a live campaign: a run's terminal state lied about what had been proven, and an operator relaunched on the lie.

3.103.0 (2026-09-05) ​

Features ​

  • review-pr: per-repo review tiers — glance / guard / audit; the pure re-request click is pinned; Revi/Billy/Vetty's shared gate documented (#742) (b2a31a1), references #685 pre-#685 #621 #650 #650 #646 #683 #683

    whyLets a repo's criticality or budget policy pick ONE preset instead of tuning severity_threshold/max_findings/post_to_board/review_mode separately. guard (the default) reproduces the bot's pre-#685 posture byte-for-byte; glance trades depth for a lower floor (cheaper same-family reviewer model via two new topology-routed judge nodes, since model:/ reasoning_effort: only ever resolve ${ENV_VAR}, never a runtime var, on any backend); audit forces the cross-family dual fan-out regardless of…
  • runner: wire a SubbotRunner into the cloud runner — subbot nodes ran locally only (#743) (1716b9e)

    whyThe pod's engine was built without WithSubbotRunner (pkg/runner/loop.go), while the CLI and the studio paths carried one. Every `subbot` node on a cloud run therefore died at dispatch with `subbot "x": no SubbotRunner is wired` — a net's extension subbot, its re-anchor subbot, and a programme supervisor's per-lot child all existed locally only. "No feature ships local-only", violated by the one surface that runs unattended.

Bug Fixes ​

  • claw: carry the run's forfait across the sandbox boundary (it currently cannot authenticate there at all) (#744) (edd5b9d), closes #736 #698, references #698 #687 #698 #736 #687

    whyin-process generation — supervisor evals, GenerateObjectDirect — which is what fixed #687's pacer. It did not hold for `backend: claw` AGENT nodes on a cloud pod, which are the DEFAULT shape: production runs ITERION_SANDBOX_DEFAULT=auto with an empty override, so those execute sandboxed and the in-container __claw-runner rebuilds its registry from env alone.

3.102.6 (2026-09-05) ​

Bug Fixes ​

  • bots: the plan phase can no longer eat the delivery budget; only_lot on a non-actionable lot fails typed (#737) (b2b3616), references iterion#683

    whyTwo production runs on iterion#683 spent the whole planning chain (plan -> plan_review -> plan_revise) for 150 min / $8.59 combined and never reached campaign, the node that writes code (native:695). The chain had no ceiling of its own, so it could freely spend the entire run's max_duration / max_cost_usd budget on an optional enrichment (ADR-091) before the actual delivery work ever started.
  • golden-master: an extension act already present at base is not re-judged (#735) (292e3f6)

    whyextension_verdict judged EVERY act in the ledger at HEAD against the run's base. An act introduced before that base has, by construction, its added references in the base tree — so every one of them read as "existed at base — a rewrite wearing an addition's name", and the certifier refused a net that had done nothing wrong.
  • quota: a reading is trusted for a bounded time, the retry waits for the nearest key, the ceiling counts spenders, the stamp is visible (#730) (f25d224), references #690 #684 #661 #659 #659 #690 #690 #684 #661 #659 #690

    whyA reading carrying a reset instant was trusted until that instant, however old. The provider resets windows early: on 2026-09-04 the ledger held 93-99% seven_day readings taken before such a reset, every credential walk skipped both forfaits on them and every claude_code run was refused at admission — the revi/review gate of two PRs with them — for a reset four days out. The lock was self-sustaining: the only writer of a fresh reading is a live session's rate_limit_event, and the refusal is…

3.102.5 (2026-09-05) ​

Bug Fixes ​

3.102.4 (2026-09-05) ​

Bug Fixes ​

  • claw: an Anthropic forfait authenticates claw, on the pod and on disk (#698) (7d8d81c), references #687 #687 #687 #687 #687

    whyA run whose only anthropic credential is a Claude Code OAuth forfait resolved an UNAUTHENTICATED claw client: non-nil, so callers proceeded, and every call answered 401 "x-api-key header is required". Revi's pacer supervisor died this way in prod for a full day (#687) while its unit tests stayed green — a clean failure is a feature you believe you shipped.

3.102.3 (2026-09-05) ​

Bug Fixes ​

  • board: the cloud twin claims launches atomically, the reaper gives up on a pruned run, labels adjust relatively, GitHub claims bootstrap their label (#731) (53f5635), references #665 #660 #660 #667 #666

    whyThe studio's pipeline admission loop launches a Ready card through native.LaunchClaimer — a CAS Ready → in_progress that also reads the claim family, because the dispatcher wins a card with the CLAIM and moves it to in_progress afterwards, off the actor. The Mongo twin never implemented it, so on a cloud board the admission loop degraded to a best-effort SetState: replica A's board dispatcher claims a Ready card (lease live, in_progress move in flight) while replica B's admission tick launches…
  • engine: a recovery pause retries its node, an orchestration stall recovers in place, and a run read never writes run.json back (#716) (9c3ab3d), references #688 #692 #692 #691 #691

    whyThe recovery dispatcher parks a FAILED node for a human (AUTH_FAILED, budget, any RecoveryPauseForHuman policy) through the plain human-pause path, with an empty pauseInfo. The checkpoint therefore carried no BackendName, and resumeFromPause has exactly one branch that re-executes the paused node — the delegate pause, keyed on BackendName. A recovery pause fell through to the human path: the acknowledgement became the node's OUTPUT, node_finished was emitted, and the DAG moved on to the gate,…
  • sandbox/kubernetes: the pod-ready wait is configurable and defaults to 10 min — a 180 s cap killed runs the autoscaler had just made room for (#707) (70da3d9), closes #696, references #694

    whyMeasured on a 12-node cluster once run pods carry requests (#694): with ten run pods the scheduler answered "0/12 nodes are available: 11 Insufficient cpu" for two minutes, the cluster autoscaler added a worker, the fresh node's CNI took a few seconds, the 736 MB sandbox image pulled in 1m37 — and the driver, which had given up at 180 s, killed the container one second after it started. Cold pulls alone measured 2m25 to 3m05 on other nodes; one run died on that too. Closes #696.

3.102.2 (2026-09-05) ​

Bug Fixes ​

  • launch: the launch path survives one team's broken plugin, strands no row, retries with a budget, commits any message (#709) (ad5405e), references #631 #537 #536 #538

    whyA converged run's squash merge failed with "fork/exec /usr/bin/git: argument list too long": the assembled message was passed as one `-m` argument, and Linux caps a single argv element at 128 KiB (MAX_ARG_STRLEN), so exactly the runs that produced the most could not be merged (#631).

3.102.1 (2026-09-05) ​

Bug Fixes ​

  • webhooks: a merge-queue heal stands down when the queue takes the PR back (#693) (1b1009f), references iterion#682 #692

    whyThe auto-heal lane launches the brancher bot when the queue ejects a PR, to rebase and push so the PR re-enters the queue. Nothing closed that loop: once the PR WAS back in the queue, the heal kept running, and its delivery tail force-pushes the branch — which cancels the queue build in flight and ejects the PR a second time. The repair becomes the next breakage.

3.102.0 (2026-09-04) ​

Features ​

  • sandbox/kubernetes: run pods carry resource requests and a soft node spread (#694) (8243001)

    whyA sibling pod that requests nothing scores every node the same, so the scheduler packs a campaign's runs onto whichever node already holds the sandbox image. Measured on a three-worker pool (8 cores each): five of six run pods on one node at 89% CPU while two workers idled, and a behavioural oracle's 300 s application boot budget blown at 459 s — red verdicts on untouched trees, one run burning its 8 h budget on that red.

3.101.3 (2026-09-04) ​

Bug Fixes ​

  • runner: a stalled resume is adopted, re-budgeted and told apart from a quota pause (#689) (9f7bc53), references #669 #669 #669 #669 #669 #652 #669 652/#669 #652 #669 #652 #652 #652 #652 #652 #669 #669 #669 #669 #669 #669 #669 #669 #669 resume#1 resume#2

    whyA DLQ-parked run reached noticeGatePausedForRetry with the same RetryState the pre-park usage-window carried, so the PR got a second "the LLM provider's quota is exhausted … resume automatically at HH:MM" — but nothing wakes a DLQ-parked run automatically; only `iterion remote admin dlq` replays it. Observed live 2026-09-03: the comment sent the operator down the wrong path.

3.101.2 (2026-09-04) ​

Bug Fixes ​

  • credentials: the wants a run actually needs, and a refusal that says why (#682) (d2f7cee), references #627 #668 #654 #668 #668 #659 #659 #668 #668 #659 #627 #654 #659 #668 #654 #627

    whyTwo paid failures in prod, hours apart, were accepted silently by the server: a claude_code accessToken pasted as a terminal transcript (embedded newlines/ANSI escapes) and a claude_code record missing expiresAt/scopes — the shape the CLI reads as "Not logged in". Both lived on disk after ingestion, so every downstream call from a full fleet of runs would die on 401 for hours before the cause was found.

3.101.1 (2026-09-04) ​

Bug Fixes ​

  • docs-refresh: 3.5.7 — scan_hints stops manufacturing the same false positives every pass (#680) (e1786cc)

    whyA link written /dsl is SITE-absolute: every static-site generator routes it from the site root. os.path.join drops everything left of an absolute path, so the scanner asked the filesystem for /dsl and reported it dead. On prod run 01a055f9 that was 20 of the 22 hints, every one false, and the remaining 2 were explicit HTML anchors the heading-only collector could not see.

3.101.0 (2026-09-04) ​

Features ​

  • product-docs: 1.1.0 — platform-agnostic publish tail (image + deploy-target skill) (#675) (ad5d29b)

    whyThe publish tail spoke one platform: the SSP Cloud datalab (three S3 STS secrets with a 7-day life and a standing serve service), which let the demo URL die by itself and put platform literals in the DSL. It now mirrors app-dev's deploy phase: the bundle's own publish-static-site skill builds the site and packages it with crane as one layer on a non-root nginx base pushed to publish_image:<docs commit> (registry_token on stdin, never argv), and the operator-attached deploy-target skill puts…

3.100.3 (2026-09-04) ​

Bug Fixes ​

  • cloudpublisher: the walk consults the operator's hard caps — a capped forfait is passed over like a refused one (#678) (aa91cb8), closes #677

    whyMeasured 2026-09-04 on a production tenant: a weekly forfait at 97% utilization (provider still ALLOWING, hard cap 95, reset four days out) was granted by the resolution on four consecutive retry attempts; the runner's pre-flight parked each one in ~1s, and because a park writes no refusal reading, no signal ever engaged the credential-tier fallback — a fresh org-tier forfait at ~0% sat unreached the whole time. Two closures:

3.100.2 (2026-09-04) ​

Bug Fixes ​

  • runner: a re-execution restores the chain its earlier attempt banked (#674) (ea2b274), closes #652, references #652

    whyA resume or redelivery of a repo-targeted run re-cloned the target branch and started the remaining nodes on a bare tree: the commits the earlier attempt had banked (final_branch/final_commit) or parked (an attempt ref) stayed reachable on the forge but never reached the workspace, so a campaign that had committed three passes planned its fourth on nothing and the PR tail had nothing to deliver — docs-refresh 01a055f9 (33 commits) and Billy 01a06728 (#652).

3.100.1 (2026-09-04) ​

Bug Fixes ​

  • forge: a re-provision keeps a schedule row's id, vars and last fire; Doki 3.5.6 declares its schedule vars (#673) (7cbf526)

    whysyncSchedules rebuilds an integration's schedule rows from the manifests on every re-provision and carried cron, pause and guard tuning over — but not the vars the operator set, nor last_fire_at, and it minted a new id each time. The docs-refresh weekly lost its open_mr/mode twice that way (07-27, 09-03: the second time from a re-provision that enabled another bot), ran a multi-hour full sweep and shipped nothing, while runs and audit entries kept pointing at a schedule id that no longer…

3.100.0 (2026-09-04) ​

Features ​

  • review: Revi cost-reduction pass — severity floor at the source, pacer supervisor, live cost signal, push debounce (#651) (2bd3bbc), references acme/b#7 acme/a#7 acme/b#7 acme/a#7

    whyMeasured in prod (2026-09-03, 9-run sample): reviewer_claude (opus/high) is 85-93% of a $1.4-$10.5 run (median $3.6), ~28 runs in 3h. The reviewer's judging quality is untouched (stays opus/high); the waste is cut around it:

3.99.1 (2026-09-04) ​

Bug Fixes ​

  • oauth: pre-fill the account name on reconnect, cap the label, and publish the team OAuth routes in the spec (#657) (ed85bf6), references #656

    whyFollow-ups from the second review round of #653:

3.99.0 (2026-09-03) ​

Features ​

  • board: claim lease + fenced watchdog for the dispatcher board (ADR-096) (#646) (e194aeb), references #597

    whyCommit 0 of the dispatcher-watchdog chantier (C1 slice 2/3) — the prerequisite the plan review made non-negotiable (F3/F25):

3.98.0 (2026-09-03) ​

Features ​

  • oauth: name the account behind a credential, and expose its fingerprint (#653) (5e27183)

    whyNothing on an OAuth record said WHOSE account it was. The payload is sealed, the API view exposed neither a label nor the fingerprint, and the only place the credential is ever identified is a server log line the publisher writes when it picks one (fp=700acc7b…). Answering "whose subscription paid for that run?" therefore meant grepping logs and correlating hex by hand — measured today, with three fingerprints across three owners in one window.

Bug Fixes ​

  • mcp: make the explicitly-named wildcard's fatal boot a decision, not an accident (#645) (fd3f89b), closes #638, references #633 #633

    whyTwo lines apart, expandWildcards hard-fails a wildcard whose MCP server cannot boot and merely warns when that server booted with no tools. The asymmetry is correct — #633 established that ambient servers (target repo .mcp.json, plugin catalog) degrade per-server upstream in buildTask, so a wildcard reaching this function is a DECLARED dependency and must never be dropped in silence — but nothing at the code site said so, and the error named neither the declaration nor a way out. A reader had…

3.97.0 (2026-09-03) ​

Features ​

  • ir: a codex fallback stage is refused at launch where the node will run sandboxed (#648) (c820b54)

    whyThe codex CLI hard-errors on any non-noop sandbox driver at dispatch — so a codex stage on a sandboxed node fails EXACTLY when the fallback chain is needed, which is worse than not having it. ApplyRunFallback now resolves each node's effective sandbox mode (node override → workflow spec → the deployment's ITERION_SANDBOX_DEFAULT snapshot, threaded through ExecutorSpec from both the cloud runner and the local service) and refuses the stage with the same launch-time voice as the other C176…

3.96.2 (2026-09-03) ​

Bug Fixes ​

  • forge: refresh lead must exceed the tick period, or a token phase-locks onto its own expiry (#649) (1417654)

    whyThe refresh worker only renews connections expiring within Lead (was 5m), swept every 10m: a 1h installation token could go from "not yet due" to "expired" between two ticks, the refresh then landing AT expiry — and since each mint inherits that phase, the connection locks onto an always-refreshed-at-death cycle. Any run whose launch minute sits just before the lock point is sealed a token with seconds of life: its clone barely makes it, its state push and NATS redeliveries die on it.

3.96.1 (2026-09-03) ​

Bug Fixes ​

  • quota: classify an account spend ceiling, tell the PR when a review parks, stop runs on a closed PR (#639) (cca3d20), references other/#7 widgets/#7

    why"You've hit your org's monthly spend limit · ask your admin to raise it at claude.ai/settings/usage" put THREE words and an apostrophe between "your" and "limit"; the qualifier pattern tolerated exactly one word without one, so the notice sailed through as the node's answer and died as "structured output invalid: missing required field …" — the precise masking bug the pattern's own comment was written for, re-opened by a new wording (three branch-improve-loop runs on 2026-09-03, runs 01a06694 /…

3.96.0 (2026-09-03) ​

Features ​

  • secrets: per-key concurrency ceiling — the operator's answer to fair-usage limits no provider will quantify (#640) (b7e8402)

    whyA provider that freezes an account for 'usage pattern' violations publishes NO numeric bound to adapt to: the refusal message names no frequency, the 200s carry no rate headers, and the threshold only reveals itself by tripping it — which is how a whole fleet ended up frozen behind one credential. The bound has to be operator-set.

3.95.0 (2026-09-03) ​

Features ​

  • Revi ticket conformance (Jira & co) + org governance (provision approval, delegated caps) (#630) (cf216f2)

    whyGiven a tracker_api_base (pinned per repo via the integration's launch_vars) and a bound read-only tracker_token file secret, the reviewers fetch the ticket(s) the PR references — explicit ticket_refs or extracted from the PR title/body + source branch — and verify the diff delivers the demand. Gaps surface as findings of the new "requirements" category (gating like any finding); a per-ticket verdict (covered / partial / not covered / unverifiable) is threaded converge -> pr_gate -> publish…

3.94.3 (2026-09-03) ​

Bug Fixes ​

  • chart: let image.digest pin the server, so one ReplicaSet is one build (#637) (5334b2d), closes #636

    why`iterion.image` could only build `repo:tag`, so "pin the server by digest" was not expressible at all — the operator's only lever was a tag, which is the thing that moves. `runner.image` is consumed verbatim and could already carry a digest; the shared server image had no equivalent seam.

3.94.2 (2026-09-03) ​

Bug Fixes ​

  • review-pr: raise max_duration 45m → 90m (#634) (3985c81)

    whyThe cost cap (max_cost_usd) is the money guard; the duration cap only needs to catch a genuinely hung run. At 45m it killed real reviews mid-publish — a large diff or a usage-window retry walks past it while spending nothing, and the verdict a gate is waiting on dies with the run (observed 2026-09-02, round 7 of the pilot: converge refused at 92% of the duration axis, the review completed everywhere but the publish).

3.94.1 (2026-09-03) ​

Bug Fixes ​

  • mcp: an ambient MCP server that cannot boot costs its tools, never the run (#633) (2eedd4d)

    whyThe claw splice added every active MCP server — including servers the node never named, inherited from the target repo's .mcp.json or the plugin catalog — as an mcp.<server>.* wildcard, and expandWildcards hard-failed the node when one of them could not boot. One token-less repo server (the repo-scoped sentry on runner pods, which have no SENTRY_ACCESS_TOKEN) therefore killed every claw node of a run at plan_review — observed 3× on 2026-09-02, neutralising the zero-touch fixer lane on this repo…

3.94.0 (2026-09-02) ​

Features ​

  • add generation-aware zero-interruption rollouts (#628) (53c4b61)

3.93.0 (2026-09-02) ​

Features ​

  • webhooks: GitHub review-thread conversations — reply to a suggestion, get an in-thread answer (#626) (d4611df)

    whyReplying inside one of the bot's review threads on GitHub now launches the converse bot (roleBots().ReviConverse), which answers in the same thread — the GitHub half of the GitLab conversational lane (forge-conversations.md).

3.92.0 (2026-09-02) ​

Features ​

  • runtime: support branch-local bounded loops (#557) (c121be6)

    whyThe answered branch closed its siblings' resume barrier only through checkpointResumedBranch (the happy path) and the panic recovers. Any other exit between consuming ResumeAnswers and landing the successor cursor — an artifact write failure, an answer satisfying no outgoing edge, a C245/unknown-node guard after an edited source — left the barrier open; under best_effort nothing cancels the siblings and collectBranches only arms its grace timer after a cancellation, so the fan-out hung until…

3.91.0 (2026-09-02) ​

Features ​

  • usagecap: an auth-rejected credential becomes skip evidence — the third refusal family (#624) (b7f2ef0)

    whyRe-resolution is already universal server-side (SubmitResume and the retry sweeper both re-resolve), yet a structurally-broken credential kept condemning run after run: it filled its slot on every resolution, gated the pool and platform tiers off, and its failure produced NO evidence for the credential-tier skip to act on. Quota refusals have a family, frequency refusals have a family — the provider rejecting the credential ITSELF had none.

3.90.0 (2026-09-02) ​

Features ​

  • secrets: several keys of one provider become an ordered fallback chain (#612) (7ff4007)

    whyCompletes the credential chain the fair-usage freeze exposed: the BYOK tier resolved ONE fixed key per provider, so a key whose account the provider froze was resealed into every fresh launch until an operator removed it by hand (measured 2026-09-02: two removals, five cancels, three relaunch waves).

3.89.0 (2026-09-02) ​

Features ​

  • webhooks: an explicitly named review identity lights the re-request lane on GitHub (#605) (9ff26bc), references #604 #608 #608

    whyRebuilt as one commit on main after #604 merged and #608 landed the GitHub/Forgejo replier gate there (convergent fix — both Revi loops demanded it); this is the remaining delta. Four Revi rounds on the PR (3→3→5→0 findings, all real) shaped it; every behaviour guard is mutation-verified.

3.88.0 (2026-09-02) ​

Features ​

  • cli: a command to stand a credential pool up, and a forfait blob that fails where the file name is known (#600) (d87ff60)

    whyTwo gaps found while actually setting a pool up on a live instance:

Bug Fixes ​

  • delegate: a fair-usage refusal parks the run and feeds the credential skip — not the node's answer (#610) (dade12e)

    whyMeasured 2026-09-02: a provider account under a fair-usage frequency restriction refused EVERY request with a ~330-char relayed 429. The one-liner length cap kept isRateLimitMessage from seeing it, so the refusal text became the agent's output (a campaign node 'finished' with the error as its work_remaining); two modernize lots burned ~7h each overnight and two rites spun full passes on it in minutes.

3.87.0 (2026-09-02) ​

Features ​

  • cloudpublisher: skip a forfait whose window is closed — the credential tiers become a fallback chain (#601) (e963626)

    whyThe tiers were a fixed first choice, not a chain: a run whose tenant (or the platform) holds an OAuth forfait was never eligible for any later tier, INCLUDING when that forfait's provider window was closed. The run got the exhausted credential, spent one LLM call to be refused, and parked until the window reset - up to a week on the weekly one - while a second forfait or the mutualised pool could have served it immediately.

3.86.1 (2026-09-01) ​

Bug Fixes ​

  • webhooks: the re-request replier gate reaches the prforge lane; an authz error never strands a co-riding resync (#608) (524dabf)

    whyTestBudgetGraceCoversDuration slept 350ms against a 570ms graced ceiling, leaving ~220ms for engine overhead — a loaded CI runner spends more than that and the run correctly dies past the ceiling, failing the test (seen ejecting merge-queue entries). Scaled to 2s/2.3s/3.8s: same contract, ~1.5s of slack.

3.86.0 (2026-09-01) ​

Features ​

  • server: the outcome router — a terminal run is decided by its contract, once per episode (#599) (5436277), references #1 #595 #597 #607

    whyThe measured class, one campaign, 48h: a converged run waited 8h51 for a human; a run marked 'handled' by its external observer was redelivered, worked 6h, converged, and was never looked at again; a third landed while carrying an explicit blocker and reddened every downstream launch. The decision lived outside the authority that knows the state — an external script guessing from a stale copy.

3.85.1 (2026-09-01) ​

Bug Fixes ​

  • routing: the single trusted reading enforces its own preconditions (#607) (d7716eb)

    whyRevi's pass on the contract, 5/5 adopted:

3.85.0 (2026-09-01) ​

Features ​

  • webhooks: on-demand re-review via the forge-native re-request button; per-repo merge-gate opt-out (#604) (c5eb318), references iterion#300

    whyAn operator who pins gate_enabled=false on a repo integration turns the review bot advisory-only — no commit status ever lands — so the statuses-scope derivation that forces re-review-on-sync (whose sole purpose is keeping a REQUIRED check alive across pushes) no longer applies. The pin now disables the forcing on fresh provisions AND releases an already-forced sync through the backfill, in both cases surviving re-provisions — unlike a bare review_on_sync PATCH, which the next provision…

3.84.1 (2026-09-01) ​

Bug Fixes ​

  • server: refuse team-less identities at the auth choke — no more empty-tenant panics on /api/runs* (Sentry ITERION-13/-1W/-1Z) (#606) (097de77)

    whyAn authenticated identity whose TeamID resolved empty (a PAT minted with no team whose owner has no default team, a GitHub-gated user before any grant) sailed through requireAuth and reached the Mongo store with an EMPTY tenant in ctx — the store's fail-closed guard then panicked on every request: a recovered 500 for the caller, steady-state Sentry noise burying real crashes (ITERION-13/-1W/-1Z, 1800+ events since 2026-08-24 on GET /api/runs, GET /api/runs/{id} and the run WebSocket).

3.84.0 (2026-09-01) ​

Features ​

  • routing: the launch-frozen outcome contract — a run carries what success means (#598) (1253dc4), references #597

    why'converged + nothing blocking' has no generic representation: outputs are an opaque per-bot bag, and a consumer reading only a convergence flag would have auto-landed exactly the blocked run a measured incident came from (a converged branch carrying an explicit blocker — 190 min of downstream work reddened). Only a contract can know the fields.

3.83.1 (2026-09-01) ​

Bug Fixes ​

  • server: a queued run waiting for a free runner is not an orphan (#602) (a6d6fb9)

    whyThe orphan sweeper reads "queued row, stale, no lease" as "the message is gone" and flips the run to failed_resumable. That is one of TWO causes with the same shape, and the other one is normal operation: a runner pod takes ONE run at a time, so a frozen pool is a hard parallelism ceiling — a campaign of multi-hour runs fills every pod and short runs simply wait their turn, unfetched, lease-less, and stale.

3.83.0 (2026-09-01) ​

Features ​

  • store: numbered episodes with continuation ownership — the run document stops saying nothing (#597) (146c7cc)

    whyA run document that says failed_resumable and nothing else forces every outcome consumer to guess: is a redelivery in flight? did it hit its budget wall? was it an operator stop? Measured on a live campaign: a budget wall hidden behind an empty final_error (the code existed — in the run_failed EVENT, never on the document), and an external router that marked a redelivered run 'handled' while it quietly converged.

3.82.1 (2026-09-01) ​

Bug Fixes ​

  • runview: merge is an owned, claimed state machine — no more double-squash between replicas (#595) (b9f8ec4)

    whyPerformMergeCtx checked merge_status==merged then went to work: on a multi-replica server (prod runs 3) two concurrent calls both passed the check, both built a squash (different commits under the default squash strategy), and the loser — non-FF push refused — then persisted merge_status=failed over the winner's merged via a full-document ReplaceOne. Found by adversarial plan review, verified in code; the merge path is live on every repo-targeted campaign landing.

3.82.0 (2026-09-01) ​

Features ​

  • store: canonical terminal-state contract + persisted failure taxonomy (ADR-095) (#603) (daa6c8a)

    whyOne place answers every lifecycle-classification question: policy-named predicates on RunStatus (IsFinalSuccess/IsFinalFailure/IsTerminalResumable/ CanOperatorResume/RequiresResumeAnswers/CanAutoResume/ CountsAgainstLaunchLimit) and the FailureCode vocabulary — runtime's ErrorCode values plus INTERRUPTED/FAIL_NODE/PROCESS_ORPHANED/ QUEUE_SCHEMA_MISMATCH — persisted as an open-world, zero-means-unknown field on Run. Truth-table + relation tests pin every set; the negative-space test forbids new…

3.81.0 (2026-09-01) ​

Features ​

  • reliability: silent-failure pack — effect outbox, terminal cancel, cloud operator alerts, sweep nets (#594) (940cb43)

    whyThe dispatcher resumed `cancelled` runs from their checkpoint (bilan issue-triage friction 7): an operator's cancel was undone on the next tick. The status was in the auto-resume set because the dispatcher's OWN stops (stall reap, external state change, daemon shutdown) used a bare context.CancelFunc, so the engine persisted `cancelled` for them too — removing the status alone would have turned stall recovery into a permanent park.

3.80.1 (2026-09-01) ​

Bug Fixes ​

  • queue: survive a queue-backend outage — bounded republish retry, typed 503, budget guard before the sandbox (#593) (e598ebc)

    whyA production outage of the queue backend lasted about ten minutes and exposed three measured failure modes:

3.80.0 (2026-09-01) ​

Features ​

  • golden-master: net extension by pure addition — request ledger + the net's own acting subbot (#588) (64eb486)

    whySelftest fixtures and run-note examples used a real target repo's directory name as a path literal; replace with neutral placeholders. No behavior change — harness selftest 70/70, sync test green.

3.79.0 (2026-09-01) ​

Features ​

  • runner: park an unbankable attempt's work on its own ref (#590) (14ea91e)

    whyAn interrupted delivery, a paused run, and a bankable death on a lease-lost ctx all leave their commits stranded in the git-meta snapshot: the storage branch must not be touched (another pod may own the lease; FinalBranch on a half-done run would be merge-eligible mid-flight), so until now nothing was pushed at all, and turning the snapshot back into a branch takes a manual replay every time — the same measured cost the death bank closed for budget/failure outcomes (nine manual recoveries in…

3.78.1 (2026-08-31) ​

Bug Fixes ​

  • queue: configurable JetStream stream replication (data-HA) (#592) (578f1c1)

    whyProduction on 2026-08-31 exposed a connection-HA/data-HA gap: a three-node JetStream cluster still held the engine streams and locks bucket at R1. During a broker blip, publishing returned 'nats: no response from stream', in-flight work was interrupted, and resume requests failed.

3.78.0 (2026-08-31) ​

Features ​

  • sandbox: permission policy crosses the claw IPC — gated claw fallbacks under sandbox + feed-watch usage_window fallback (#589) (93878cd)

    whyA sandboxed claw node with an enabled permission gate was refused outright: delegate.IOTask carried no policy, so the in-container __claw-runner would have executed bash/file_edit/write_file ungated. That blanket refusal also made a claw fallback route useless for any gated node under the shipped sandbox: auto default — e.g. feed-watch's synthesize, which cannot degrade to the OpenAI forfait when the Anthropic usage window closes.

3.77.2 (2026-08-30) ​

Bug Fixes ​

  • test: deflake the launch tests at their cost, not their timeout (#586) (003d155)

    whyTestLaunch_AppliesBudgetOverrides (30s) and TestEngineRunner_SubbotChildHoldsRunLock (60s) both failed on wall-clock this week. Neither timeout is raised here.

3.77.1 (2026-08-30) ​

Bug Fixes ​

  • changelog: backfill the three releases the cutover skipped (#584) (554db02), references #579

    whyv3.75.0, v3.75.1 and v3.76.0 shipped while #579 was open, so release-it still ran under `infile: false` and wrote nothing. CHANGELOG.md kept the snapshot taken at v3.74.1 and the v3.77.0 section landed straight on top of it — the only visible trace was a compare link reaching back to a version no section described.

3.77.0 (2026-08-30) ​

Features ​

  • release: commit a CHANGELOG.md fed by release-it, with commit-body context (#579) (501ef49)

    whyThe conventional-changelog plugin ran with `infile: false`, so 264 releases of notes existed only on the GitHub releases page. `infile` settles the drift objection that deferred the file: release-it writes the section in `beforeRelease` and stages it with `git add . --update`, so it lands in the release commit itself and cannot diverge from the tags.

3.76.0 (2026-08-30) ​

Features ​

  • fallback: run-level fallback becomes an ordered chain — v11 rides beside v10 (#583) (96bc9db)

    whyOne stage was not enough: a run whose primary AND first fallback share a failing provider still dies, and a three-stage route (subscription → facade → cross-family) was inexpressible. The launch field now accepts a single object (promoted to a one-stage chain, wire-compatible) or an ordered array; stages apply in order, a refused stage is logged and skipped, and the existing fallback event carries the zero-based fallback_index of the stage that took over. Budgets stay per-stage on the existing…

3.75.1 (2026-08-30) ​

Bug Fixes ​

  • bots,docs: Revi follow-up on the plan-phase extension (#578) (#580) (e38c3f8)

    whyFindings R0c5ecf R111a3c R62b46b R9c35e4 R80915d R15f1bc R86427d R255dc8 R2c671d R1c4b91.

3.75.0 (2026-08-30) ​

Features ​

  • bots: extend the cross-model plan phase to feature-gap-fill, test-coverage and e2e-coverage (#578) (22a29c0)

    whyReplicates the ADR-091 fragment (plan_topology → plan → plan_review → plan_gate → plan_revise; plan_review/plan_review_policy vars; the peer's action:skip fallback route) verbatim from feature-dev, with per-bot mission hand-offs (gap_spec+scope_notes / target+test kinds / matrix_path+target). All three had the exact feature-dev shape (entry: campaign + one continuation_loop back-edge), so the back-edge blanks and the map-every-field discipline carry over unchanged under selected-incoming-edges…

3.74.1 (2026-08-29) ​

Bug Fixes ​

  • gate: the sweep's last pass over a stuck run must be visible at info (#577) (f416cbc), references #564

    whyA run that owes a merge-gate verdict and abstains logs its reason at Debug on the sweep path, to keep ~60 identical lines an hour per replica out of the log. Deployments run at info, so those passes emitted nothing at all: the single Warn the event path fires dies with the pod, and a required check left unanswered has no trace anywhere naming why.

3.74.0 (2026-08-29) ​

Features ​

  • queue: the run-level fallback route rides the wire to the pod (#574) (d51fd5d), references #513

    whyThe launch API accepted `fallback` (the operator's single run-level rescue route, ADR-087) and the local executor honoured it — but the cloud publisher dropped it at publish: no RunMessage field, no run-doc stamp, no resume replay. The route meant to rescue a run from a provider's exhausted usage window never fired precisely where runs park unattended (measured on one campaign: 72-85% of two runs' wall-clock lost to usage-window parking a declared fallback would have bypassed).

3.73.0 (2026-08-29) ​

Features ​

  • model,cost,studio: surface model pricing and max-output in ModelCapabilities (ADR-042 follow-through) (#575) (c94ecce)

    whyThe aggregator has parsed and cached limit.output since ADR-042, but the field stopped at fetchedSpec: nothing downstream could read a model's completion cap. Carry it through merge onto ModelCapabilities, out via ResolvedCapabilities, and into the `iterion models` table as a MAX OUT column.

3.72.0 (2026-08-29) ​

Features ​

  • feed-watch: deliver a long digest in several messages, never cut (#570) (8bc4e5a)

    whyA digest over 14000 chars was truncated with a notice pointing at "the run artifacts" — somewhere its chat readers cannot go. The 28 août Veille Cyber digest (14803 chars) lost its last 803 characters that way.
  • runner: a dying run banks its branch (#556) (5d2007f)

    whyA repo-targeted cloud run that died (budget cap mid-delegate, failure path) left its commits only in the git-meta snapshot: the bank push was gated on runErr == nil, so the branch never reached the forge and the successor restarted from the base commit. Turning the snapshot back into a branch took a manual replay every time — measured: nine manual recoveries in three days of one campaign.

Bug Fixes ​

  • feed-watch: size the marker reserve from the marker, and refuse the queue on a truncated digest (#576) (73fdf0c)

    whyR79a75e. MARK_RESERVE was 32 while the truncation notice the last capped part carries is 64 chars, so a ceiling-truncated digest overshot the very budget the reserve exists to respect: at limit=1200 the last message measured 1232. An operator who sets a sink's max_chars to the platform's own limit gets that POST rejected — losing precisely the notice saying the digest was cut.

3.71.0 (2026-08-29) ​

Features ​

  • platformcfg: bot vars — ${ITERION_X} resolved from the DB before the pod env (#568) (06627f3)

    whyBots parameterize their model pins, reasoning effort and tunables as ${ITERION_X:-default} expansions, read from the runner pod's env — so re-tuning a bot meant a Helm values change and a rollout, while credentials, bundles, caps, roles and the sandbox image had all moved to the CLI→API→DB settings surface. Same doctrine, fourth family: bot_vars.

3.70.1 (2026-08-29) ​

Bug Fixes ​

  • model: forward host-probed codex version into the sandboxed claw runner (#571) (82f98df)

    whyThe sandbox image ships no codex binary, so with no ITERION_CODEX_VERSION override the in-container runner fell back to claw's baked-in version string and OpenAI 400'd newer models (gpt-5.6-sol) that the host's own codex install can serve. Forward the host-resolved version through the existing env choke point.

3.70.0 (2026-08-28) ​

Features ​

  • bots: Senti names who is actually vulnerable, not who uses the tech (#545) (59fb4c2), references #veille-vigie-secu

    whyThe flow that had never run for real now has: two watch-only GitHub Apps (one per org, since a private App only installs on its owner), both on All repositories, both carrying metadata+vulnerability_alerts read and nothing else. `poll_dependabot` answered `orgs_ok: 2, orgs_failed: 0` — which is the proof that the dependabot_tokens map is keyed by ORG and not by the App's bot handle, the critical the review caught and no test could see.

3.69.1 (2026-08-27) ​

Bug Fixes ​

  • cloud: a resume must keep the budget the launch declared (#554) (72ab85c)

    whySubmitLaunch published the operator's budget override on the wire, but SubmitResume rebuilt its RunMessage with a nil override — so the first usage-window auto-retry silently reverted the run to the workflow's own cap. Measured: a run launched with max_duration 8h, parked by a cap and resumed, died at 14407s/14400s while its doc still displayed 8h. Cloud resumes are unattended, so nothing could re-state the ask.

3.69.0 (2026-08-27) ​

Features ​

  • codex: expose native web search from DSL tools (#550) (c4e1528)

3.68.5 (2026-08-27) ​

Bug Fixes ​

  • model: forward ITERION_CODEX_VERSION into the sandboxed claw runner (#553) (a2ac17a)

    whyThe ChatGPT-forfait wire gates model availability on the codex-cli version header. The sandbox image ships no codex binary, so the in-container runner's `codex --version` probe finds nothing and falls back to claw's baked-in version — which the backend refuses for newer models. Lived on iterion#541's /billy run: plan_review 400'd with "gpt-5.6-sol requires a newer codex-cli" on every cloud attempt, with no operator-side cure since the override never crossed the boundary.

3.68.4 (2026-08-27) ​

Bug Fixes ​

  • bots: plan_review_policy defaults to skip fleet-wide (#548) (f803fe4)

    whyExtends branch-improve-loop's default to the three sibling plan-phase campaign bots (app-dev, feature-dev, whole-improve-loop). Two lived incidents the same day made the case: a dead second-family credential blocked a fixer through plan_review auto + wait, and a stale pod OpenAI key paused every cloud campaign the same way. The cross-model peer is an optional enrichment — the primary family alone must always suffice — so a peer failure completes plan_review with the _skipped stamp and the…

3.68.3 (2026-08-27) ​

Bug Fixes ​

  • runtime: build node input from selected incoming edges only (#539) (bd1ca52), closes #484

    whybuildNodeInputRS merged with-mappings from every incoming edge whose source had produced output, so a mutually exclusive when/else pair that later converged let the unselected mapping silently overwrite the selected one (Copi's validator verdict never reached the operator).

3.68.2 (2026-08-27) ​

Bug Fixes ​

  • usagecap: the meter follows the credential, not the slot (#541) (57b7062)

    whyA usage reading records which WINDOW is spent, keyed by backend and scope (tenant/platform). When the credential behind that slot is rotated — a fresh OAuth token posted over a team's exhausted one — the old account's seven-day reading stayed on the meter, legitimately fresh until its own reset instant, and parked every run of the new credential for days. Lived on a real deployment: a fresh token blocked at 95% by the reading of the account it replaced, resets five days out.

3.68.1 (2026-08-27) ​

Bug Fixes ​

  • supervise: unpinned evaluator prefers the supervised run's own provider family (#546) (1de6be2)

    whyAn unpinned supervisor resolved its model by host auto-detect, so whatever key sat first in the environment decided — on the prod runner pods a dead platform OPENAI key (429, no credits) failed every Persy eval while the supervised campaign ran fine on Anthropic (run 01a042c2). SpecsFromWorkflow now derives a provider hint from the watched nodes (provider: routing, model prefix, or backend family) and the resolver prefers it when that provider is detected available; pin and…

3.68.0 (2026-08-27) ​

Features ​

  • bots: product-docs publication tail — skill-driven Onyxia deploy behind deterministic gates (#533) (6e8e6ea)

    whypublish_gate (opt-in + secrets pre-flight) -> publish agent loading the org-private deploy-onyxia-sspcloud skill explicitly (ADR-059 skills: ref) -> verify_publish (external URL truth gate, fails the run when the site is not serving) -> surface_site_link. French admonition titles in the GitBook->MkDocs converter.

3.67.0 (2026-08-27) ​

Features ​

  • bots: product-docs (Prody) — functional documentation from a multi-repo product catalog (#524) (ff16bf4)

    whyNew catalog bot generating and maintaining BUSINESS-AUDIENCE product documentation in a dedicated docs repository, grounded in the source code of the N repositories a product catalog names.

3.66.0 (2026-08-27) ​

Features ​

  • platform: DB-backed bot overrides + runtime bot-roles/sandbox settings (#535) (bdafa72)

    whyIterating on a native bot on a cloud instance used to cost an image build + rollout. This makes it one CLI call, mirroring the platform LLM credentials pattern (env/baked = default, DB record = runtime override):

3.65.0 (2026-08-26) ​

Features ​

  • runtime: a bounded grace so a spent run still delivers what it paid for (#532) (96d89ef)

    whyA run whose cap runs out mid-way dies holding work it has already paid for, with no way to hand it over: a documentation campaign overran its budget and left a finished, committed corpus with no pull request. The money was spent either way; refusing the last few nodes only decided whether anything came of it.

3.64.2 (2026-08-26) ​

Bug Fixes ​

  • runtime: a budget overrun must not strand the node that earned it (#529) (722b760)

    whyA node whose usage takes the run past a hard cap has already SUCCEEDED: its output is validated, persisted and in the store. Failing inside the post-exec check anchored the checkpoint on that node, so a resume with a raised cap re-executed it — for an agent pass, paying its entire cost a second time to reach a result already on disk. Observed on a docs campaign that overran 442/400: the only way forward was to pay for the whole pass again.
  • studio: default inline subbot frame to live/latest child (#530) (415b981), closes #525

    whyThe Run canvas picked children[0] (created_at asc = oldest) when the operator had not chosen a tab, so a historical failed child painted the expanded graph red after a later child had already succeeded.

3.64.1 (2026-08-26) ​

Bug Fixes ​

  • ast: serialize supervisor declarations across the cloud queue + Persy dogfood bilan (#528) (129b2a1)

    whyThe AST JSON codec is the queue's wire format; MarshalFile dropped supervisor decls, so a cloud runner pod recompiled every workflow without its supervisors — no spawn, no skip log. Found live by the first repo-targeted dogfood run on the prod runner (01a03d70).
  • cli: local file secrets never reached the sandbox (#531) (d9c1c70)

    whyA bot declaring a file secret got it mounted nowhere on a local run. The credentials were resolved from the sealed store and stamped into the EXECUTOR's context — but the engine mounts declared file secrets into the sandbox at run start, from the context handed to Run, and that one never carried them. An optional secret was then skipped in silence ("the agent simply won't find the file"), and a required one failed the launch with "has no resolved value" on a host whose store held exactly that…
  • sandbox: fall back when the engine's own image tag was never published (#526) (ef95dad)

    whyA binary built between releases — or from a release whose sandbox image did not ship — pins ghcr.io/socialgouv/iterion-sandbox-slim:<version>, a tag nobody pushed. The pull then fails with a raw 'manifest unknown' and the run dies at startup, before any node: observed on v3.58.3, where neither 3.58.3 nor v3.58.3 exists at the registry, so every local sandboxed run needed a manual --sandbox-default-image.

3.64.0 (2026-08-26) ​

Features ​

  • forge: watch-only GitHub App for org-wide Dependabot alerts (#527) (0a77503)

    whyThe org-wide alerts endpoint returns only what an installation can see, so covering an org means installing on All repositories. Doing that with the ordinary forge App would grant contents:write — and, when opted in, administration:write — on every repository, as a side effect of wanting to READ alerts.

3.63.0 (2026-08-26) ​

Features ​

  • backends: cool down refused fallback routes (#511) (c123f89), closes #468

    whyKeep the typed usage-window cause reachable when a cooled primary is skipped and its fallback also fails. Surface proactive skips in the Studio timeline and clarify the reserved unavailable-reset path.

3.62.1 (2026-08-26) ​

Bug Fixes ​

  • queue: make schema bumps safe across mixed-version runner rollouts (#510) (b8adb7a), references #481

    whyA version mismatch on the runs queue was Naked immediately, so a stale runner fleet could burn the whole MaxDeliver budget in seconds during a rolling schema bump — and JetStream then dropped the message silently, leaving the run document queued forever with no recovery path (#481).

3.62.0 (2026-08-26) ​

Features ​

  • dsl,bots: cross-model peer-reviewed plan phase + fallbacks action:skip / when: (ADR-091) (#523) (4f3b7b3)

    whyTwo new opt-in injections beside review_mode, resolved at launch from the same family set: plan_review (auto -> on iff >=2 distinct credentialed families, family-agnostic) for bots with a peer-reviewed plan phase, and llm_families (the raw sorted family list) so any bot can build its own policy without a new engine role var. InjectAll folds the three into one call + one log summary at the CLI/runview/dispatcher launch surfaces; FamilySet decouples resolution from detect.Report so the cloud…

3.61.0 (2026-08-25) ​

Features ​

  • supervise,bots: Persy perseverance coach + declarative monitors + supervisors kill switch (#522) (7470085)

    whyfeature-dev v2.2.0 ships the first use of the DSL supervisor block: Persy, a perseverance coach watching the campaign node. Monitors-first policy (give-up markers, Bash failures, budget warnings) with four intervention classes — premature impossibility, expedient path, failure loop, bank under pressure — and an asymptote guard so it composes with the ADR-058 convergence contract. The campaign contract gains the static PERSISTENCE clause. A dedicated test keeps the coach non-vacuous (C190/C193…

3.60.1 (2026-08-25) ​

Bug Fixes ​

  • forge: Revi round 8 (final) — the last three, all consistent with each other (#521) (7efe01a)

    whyRevi's gate went GREEN on the previous head (max medium). These are the three it still raised, fixed as the agreed last round.

3.60.0 (2026-08-25) ​

Features ​

  • bots: Senti (vuln-watch) — inventory-scoped vulnerability sentinel, zero LLM (#515) (d6cc7d6)

    whyA github_app connection can now opt into SecurityReadEnabled: the refresh worker (and the new PATCH connections endpoint, which mints immediately so a missing grant answers 422 on the spot) mints an org-wide vulnerability_alerts:read installation token and merges it into the team-scoped dependabot_tokens generic secret ({org_login: token} JSON map, egress-pinned to the forge host). The profile is a separate opt-in like DeliveryInstallationPermissions — never folded into the runtime baseline —…

3.59.3 (2026-08-25) ​

Bug Fixes ​

  • modernize: read a scalar exit_gate as one whole command (#520) (7e9dad5)

    whyA YAML scalar and a sequence are both legitimate contract forms for a lot's exit_gate. plan_read joined the value with "\n" unconditionally, so a bare string was iterated character by character: the verifier's first command became the single letter of the declared gate, failed with exit 127, and the lot could never converge — a red verdict manufactured by the reader, not earned by the tree.

3.59.2 (2026-08-25) ​

Bug Fixes ​

  • bots: revert a guard built on a false positive, pin the real one (#518) (17df7af), references #508

    whyThe [high] that motivated `ValidateShellSafeRef` in #508 does not exist. Its premise — that a bot's `PUSH_BRANCH={{vars.push_branch}} python3 -c` reaches the shell unquoted — reads the .bot SOURCE as if it were the final command line. The engine shell-escapes every ref at substitution time (resolveCommandTemplate → shellEscapeValue), so the hostile value arrives as `PUSH_BRANCH='x;id;#'`. Proven by execution, with the shell itself as the oracle.

3.59.1 (2026-08-25) ​

Bug Fixes ​

  • deps: update npm (non-major) (#517) (3d908a7)

  • runner: bank push resolves the LIVE credential through origin (24092c4)

    whyThe bank pushed with the claim-time token injected into the URL. A GitHub App installation token lives one hour; a paused-and-resumed run banks far later, and the final push — the run's most valuable action — died on a dead credential (loudly, thanks to FinalBranchError) while refreshGitCredentialsLoop had a live token sitting in the clone's credential store the whole time.

3.59.0 (2026-08-24) ​

Features ​

  • cli: iterion remote admin caps — get/set the runtime usage caps (7f49737)
  • runner,runview: usage-cap enforcement reads the live effective policy (f324cd0)
  • server: super-admin runtime usage-cap settings API + effective healthz echo (9d4659c)
  • usagecap: platform runtime-settings record + TTL-cached live policy resolver (099373f)

3.58.5 (2026-08-24) ​

Bug Fixes ​

  • forge: unbreak the dep-update lane's dead-gate recovery (#508) (6bfe375), references buildkit-operator#21 iterion#504

    whyFour production defects found auditing the Renovate auto-upgrade pipeline (buildkit-operator#21 + iterion#504, 2026-08-17/24):

3.58.4 (2026-08-24) ​

Bug Fixes ​

  • cloud: launch model_overrides now reach the runner's executor (427a9f4)

    whyA cloud launch's model_overrides were persisted display-only: the studio showed the operator's per-node pins, but the RunMessage never carried them and the runner pod built its executor without them — every delegate ran on the workflow's own models while the Overview claimed otherwise. The local path applies them (service_launch → engine + executor); the cloud path silently didn't.

3.58.3 (2026-08-24) ​

Bug Fixes ​

  • auth: repair remote teams/orgs switch — decode the real /api/auth/me shape (775f9dc)

    whyThe CLI decoded /api/auth/me with a hand-mirrored struct whose flat `teams` field the server had re-nested under orgs[].teams: every field silently zeroed, so `teams switch` refused every team as 'not a member', `orgs switch` accepted only the already-active org, `orgs list` showed a single org, and `teams list` rendered an all-empty table with every row starred (wrong json tags on the same wire).
  • backends: claude_code nodes get only the resolved MCP set (#506) (#507) (fc3f15f)

    whyiterion resolves an explicit per-node MCP server set (mcp_server:/mcp: blocks, the repo's .mcp.json via autoload_project, its own ask_user/board servers) and passes it via --mcp-config — but without --strict-mcp-config the claude CLI MERGED the operator's personal user-scope servers (~/.claude.json) on top. Undeclared tools reached the agent, every node visit booted npx/servers/chromium (a CPU spike per iteration on loop-heavy persist bots — the observation behind #506), and personal API keys…
  • feed-watch: title the digest with its send date, window in the body (3596683), references #452 #452

    whyPR #452 titled the WINDOW the queue covers ('Veille Cyber — 21 → 24 août') whenever span_days > 1 — factually honest, but a Monday reader gets a period headline for what is an ordinary digest whose feeds simply published late over the weekend (observed live: gopuyrust '30 juillet → 24 août' after two quota-dead Mondays). Operator arbitrage: the headline names digest_title + the send date ONLY; the anti-breaking spirit of #452 stays as one body clause ('covers the period since <oldest>') when…
  • pipelines: adopt finished recovery forks on the cloud board (#379) (#502) (d538808)

    whyreconcileFinishedTickets — the sweep that adopts a finished fork as the ticket's LastRunID and files it done (cascading waiting_deps dependents) — is gated to local mode, while the board projection that lets the fork replace its dead parent on the card also runs in cloud. In cloud a finished fork therefore took the card (Closed) but the ticket stayed in_progress forever and its dependents parked in waiting_deps.

3.58.2 (2026-08-24) ​

Bug Fixes ​

  • dsl: align {{input.*}} in edge with-mappings with runtime (#500) (cb56808), closes #479

    whyCompiler C034 validated edge {{input.x}} against the source node's input schema; the runtime resolved it from the source output overlaid on run-level inputs. Those are not the same namespace, so a mapping could be rejected though it ran, or compile and resolve from a different source than the author/compiler implied (Copi's {{input.reviewer}} worked because --var reviewer=on also landed in run inputs).

3.58.1 (2026-08-23) ​

Bug Fixes ​

  • observability: record the model that actually ran (#474) (#501) (481e857)

    whyEffectiveModel was captured on delegate.Result, used for a cost fallback and a log-only drift warning, then dropped before anything durable was written. CLI backends have no llm_request.model escape hatch, so a finished run could not say which model served it.

3.58.0 (2026-08-23) ​

Features ​

  • backends: grok and kimi enforce the permission gate (#498) (a34a071), closes #476, references #497

    whyA bot declaring `permission: ask|deny` could not use grok or kimi at all — C176 refused every route. The refusal was correct; the coverage was the problem, and it locked out precisely the two backends whose credentials are a CLI forfait rather than a metered API key.

3.57.2 (2026-08-23) ​

Bug Fixes ​

  • dispatcher: a dispatched bot can run its subbots (#499) (4b7ec73)

    whyThe direct engine path built its engine with ten runtime.With… options and no WithSubbotRunner, so every `subbot` node of a dispatched bot died with "no SubbotRunner is wired". The CLI (pkg/cli/run.go, resume.go) and the studio (pkg/runview/service_launch.go) each wired one; this path never did, and the ADR-046 route that would have borrowed the studio's is inert — WithRunLauncher has no non-test caller, so r.launcher is always nil and ITERION_DISPATCH_VIA_SERVICE cannot switch it on.

3.57.1 (2026-08-23) ​

Bug Fixes ​

  • dsl: refuse an unresolvable tool name before the run starts (C135) (#497) (28f6ebe), closes #478, references #478

    why`tools: [read_file, list_files]` on a claw node compiled clean. The run started, the worktree was prepared, the sandbox came up — and the first LLM node died on `unknown tool "list_files"`. The name is right there in the .bot: the failure was fully determined by the source, and it cost a launch to discover (found while dogfooding Copi, #478).

3.57.0 (2026-08-23) ​

Features ​

  • runtime: bound the worktree pool — a full checkout per failed run, forever (#477) (dc9a205)

    whyA `worktree: auto` run parks a FULL checkout of the repository under `<store>/worktrees/<run-id>`. A clean exit removes it; a failure keeps it, deliberately, for inspection. Nothing ever came back for those: `runs prune` only touches runs/, and `iterion clean` is a command you have to know exists. So a store whose runs fail grew by one checkout per failure, with no ceiling and no signal — 355 MB each on this repo, 309 MB of it the vendored tree. A studio left alone for forty minutes reached 32…

Bug Fixes ​

  • feed-watch: a push refused for credentials is not a rebase conflict (#457) (a1059c2)

    whyThis morning's ux-metier digest died on

3.56.4 (2026-08-23) ​

Bug Fixes ​

  • pipelines: a dispatcher give-up reaches Needs attention, not Closed (#495) (95f1460), closes #494

    whyWhen `iterion dispatch` exhausts `agent.max_attempts` it files the ticket into `agent.failed_state` (default `blocked`) itself — the same terminal state the board's Close writes. The projection read any terminal ticket as "the operator already filed this" and put the card in Closed, so the one class of failure the Needs-attention lane exists for (a pipeline that died and wants a human) was precisely the one it never showed: a deterministic failure burns the whole retry budget on every run.

3.56.3 (2026-08-22) ​

Bug Fixes ​

  • runner: banking verifies the export delivered the pod's final tree (a5c8b87)

    whyAn export-based sandbox (kubernetes) hands the runner a COPY of the pod workspace. When that copy arrives stale or empty, the host clone reads exactly like a run that made no commits, and bankRepoWorkspace concluded 'nothing to bank' — a silent total loss of a finished run's work (run 01a02a4b: gate converged citing its commit hashes, FinalBranch empty, zero commits recorded).
  • sandbox: stale host loose refs no longer shadow the exported pod refs (09fb3bb)

    whyAdversarial review of the banking invariant found a reproducible false refusal: tar cannot delete, so when a pod-side `git gc` / `pack-refs --all --prune` moves a ref into packed-refs, the export overlay leaves the host's pre-run LOOSE ref in place — and git resolves loose before packed, so the exported clone reads a pre-run HEAD while every object actually arrived. The guard then refuses work that is sitting right there (and before the guard existed, this exact shape was a SILENT loss — it…

3.56.2 (2026-08-22) ​

Bug Fixes ​

  • runtime: an unknown cost is not a free call (#475) (12dd63d), references #473

    why`cost.Annotate` omits `_cost_usd` when no price resolves, and its doc is explicit that a zero there means "no cost data", never "this call was free" — callers must not record a $0 sample. The budget was the caller that did: `extractUsage` collapsed absent and zero into one float and `RecordUsage` added it to costUsed, so a run whose models are unpriced accumulated 0.00 per node. `max_cost_usd` never fired and the run finished with no budget event, indistinguishable from one that stayed under…

3.56.1 (2026-08-22) ​

Bug Fixes ​

  • studio: show the resolved model and fallbacks on editor nodes (#472) (0a32460)

    whyThe canvas replaced every ${VAR} model spec with the word "env", so gpt-5.6-sol / terra / luna were invisible next to the backend chip. Resolve the literal (authored default + /api/resolve-model), shorten the id, and render the authored fallbacks: chain on the card and in the inspector.

3.56.0 (2026-08-21) ​

Features ​

  • arbitrate: Themis — a doctrine-bound judge for blocked divergence cases (379ef20)

    whyA modernisation programme stalls each time a lot blocks on a divergence it may not close: the decision (canonicalise the platform noise, re-baseline through the ledger's rite, open a defect lot, or stop) belongs to the contract owner, and until now only a human could hold it. This bot delegates the DERIVABLE part and nothing else: one adversarial judge applies the target repository's WRITTEN arbitration doctrine — refuse by default, every proof read from a committed artifact, anything the…

Bug Fixes ​

  • dsl: refuse bounded-iteration edges inside parallel branches (C244) (#471) (55e44f5)

    whyfan_out_all, fan_out_each, and llm multi run through execBranch, which has no local loop counters. A declared loop compiled and was then skipped; a foreach with no `when` was taken as an unguarded unconditional back-edge. C243 rejects those edges. The runtime skip now covers IsBoundedIteration() (loop and foreach) as defence.

3.55.0 (2026-08-21) ​

Features ​

  • merge: repo-targeted runs merge server-side — clone, merge, push to the forge (7787036)

    whyA repo-targeted run's workspace is wiped when it returns, so `runs merge` had nothing to stand in: mergeRepoRoot resolved to nothing and every merge died with "no resolvable repo root" — the storage branch the runner banked was unreachable by the product's own merge.

Bug Fixes ​

  • golden-master: honour route exclusions written in the refusal's own format (9b2c343)

    whyThe perimeter refusal prints uncovered routes as "METHOD /pattern" and asks for their exclusion in route-coverage.json — but the lookup only matched bare patterns, so an exclusion transcribed verbatim from the message could never count and the gate refused the same 51 routes it had just dictated. Both key shapes are now honoured: a bare pattern excludes the route for any method, a "METHOD pattern" key excludes exactly what the message named. Self-checks falsify both directions (transcribed form…

3.54.0 (2026-08-21) ​

Features ​

  • clean: reclaim ${PROJECT_SCRATCH_DIR} — the one directory nothing swept (#469) (a72c295)

    whyA workspace's out-of-tree scratch had no reaper at all: `runs prune` only touches runs/, the worktree sweep only worktrees/. One project reached 54 GiB of it — 59 state dirs of 1.1 GiB, none younger than three days, none reachable by any command.

3.53.1 (2026-08-21) ​

Bug Fixes ​

  • fork: a repo-targeted fork carries its clone coordinates, never a dead pod's path — plus a named 400 for unforkable nodes and a budget hint that names the way out (7c1d577)
  • runner: bank a repo-targeted run to the forge — worktree finalization never fires on this path, so a finished run's commits existed nowhere the server could reach (38f1082)

3.53.0 (2026-08-20) ​

Features ​

  • health: the probes echo the usage-cap policy — an unobservable guard cannot be trusted (a984b10)

  • probes: a lame-duck window, so a deploy stops refusing live connections (#467) (d98af76)

    whyOn SIGTERM the server went straight to Shutdown. Endpoint removal is asynchronous, so for the 1-10s it takes to propagate, the listener was already closed while traffic was still routed here — a connection-refused: a 502 in the studio, a dropped delivery for a forge webhook. With server.hpa enabled by default that happened on every scale-down, not just on deploys.
  • sandbox: ship a ready JVM truststore beside the egress CA — six ecosystems read the CA env vars, a JVM reads none (4ad6768)

Bug Fixes ​

  • delegate: the CLI's no-credential render is an auth failure, not an answer (e7a721e)

3.52.0 (2026-08-20) ​

Features ​

  • platform-creds: DB-backed platform LLM credentials — rotate the fallback without a redeploy (#466) (ecea9d5)

    whyThe credential a tenant-less run inherits — historically the runner pod's env (CLAUDE_CODE_OAUTH_TOKEN from the iterion-forfait k8s secret, ANTHROPIC_API_KEY et al.) — can now live sealed in Mongo, managed by super-admins and rotated with one call that every new launch AND resume picks up (SubmitResume re-resolves credentials). The env stays as the final backstop: an empty platform store keeps today's behaviour byte-identical.

3.51.1 (2026-08-20) ​

Bug Fixes ​

  • bots: golden-master and modernize declare forge_token — repo-targeted cloud launches cloned credential-less (8b31a54)

  • errtrack: the independent transaction rides a cloned hub (#464) (40eaaeb), references #463 #463

    whyRevi's post-merge advisory on #463, verified red-first: sentry.StartSpan installs the new span on the hub's scope (tracing.go:210) and doFinish only restores the previous span for NON-transactions (tracing.go:472) — so StartIndependent on the process-global hub left its finished llm.generate transaction on the global scope forever, and every later captured error/panic inherited that unrelated trace context (reproduced: the regression test failed on the exact trace_id match before the fix). The…
  • runner: a credentials-inject error fails the attempt, and a credential-less clone failure names its probable cause (3f4a56e)

3.51.0 (2026-08-20) ​

Features ​

  • golden-master: standard 3 — the net states its FEATURES, not only its routes (7b04458)

    whyA route the corpus touches once is not a feature it exercises. A net declaring standard 3 must ship a feature_probe (two independent witnesses: the served navigation graph and the tree's own catalogues) and a committed feature-coverage.json mapping every feature to corpus entries or excluding it in writing. The gate refuses unmapped, stale and broken-evidence inventories by name; existing nets keep their declared standard, and the verdict carries the figure so a below-standard net is visible,…
  • golden-master: the seal opt-in becomes committable — config.json's seal_committed, auditable like the rest (d8ee9ef)

  • modernize: defect disposition — fixed and recorded by default, preserved only by written business decision (8af1388)

  • modernize: outcomes — what the programme owes becomes a conjunction term of its convergence (f0e5332)

  • observability: opt-in Sentry tracing — API transactions + LLM-call spans (Obsy dogfood) (#463) (39ebe8a)

    whyTracing rides the SAME client and DSN as error tracking; sentry-go does not read SENTRY_TRACES_SAMPLE_RATE on its own, so Init resolves it and sets EnableTracing/TracesSampleRate. Unset, 0, unparsable or out of [0,1] ⇒ strictly off even with a DSN — and the refusal is loud, but never costs error tracking. NaN is rejected explicitly: it fails every ordered comparison, so the naive range check would let it through.

Bug Fixes ​

  • golden-master,modernize: the adversarial review's findings — a ratchet for the standard, named refusals everywhere, and skills that say exactly what is checked (a6d5b8f)

    whyH2: standard-mark makes a silent 3->2 downgrade a named refusal in both drift directions. M2/M3: malformed feature-coverage and mistyped seal opt-ins bail with their cause instead of a traceback or a silence. M1: holdout_awaiting_gate is a report field, not only a notice. H1/C1/B2: the skills now state exactly what the harness checks (union-level source labels), where the outcomes conjunction is enforced today (the campaign runner, not this graph — roadmap named), and that SSR stacks DO expose…
  • golden-master: a gate never seals a COMMITTED held-out set — it awaits its own gate (26bd79a)

    whyThe seal moves the set out of the tree, which strips TRACKED files when the set was committed by an earlier authoring run: the finalize then refuses the dirty tree, and the set's single scoring burns on a gate that does not own it. A committed set is now left in place and noted; the convergence gate that owns it opts in explicitly with GM_SEAL_COMMITTED=1 — a flag that can only widen what the gate consumes, never soften a verdict. Selftest 45 -> 48.
  • golden-master: round-2 adversarial findings — every refusal named, in every type (703cd81)

    whyNon-string reasons and feature ids, unreadable standard-marks, mistyped GM_SEAL_COMMITTED spellings, valid-JSON-non-object ledger blocks: each was a traceback or a silence, each is now a named refusal. A feature mapped twice or both mapped and excluded refuses (nobody chose). A mid-path ** in a route pattern refuses (the doctrine says a tail). The standard-raise doctrine now states the PAIRED-commit rule the code enforces. Selftest 57 -> 66.
  • golden-master: round-3 findings — the refusal envelope survives every path (b99964f)

    whyA ledger block that is a dict without a string id escalates as UNPARSEABLE instead of a KeyError two functions later; the seal opt-in refusal reaches the supervisor through bail()'s JSON envelope like every other refusal; a feature excluded twice refuses (two reasons, nobody chose). No false-green found this round. Selftest 66 -> 68.
  • modernize: the boolean-attribute probe becomes mechanical — rendered DOM, anchors included (4ad6952)

  • modernize: the judge's INPUTS are immutable too — a corpus can be amputated in silence (fb4a453)

    whyNarrowing corpus.json, or widening the coverage exclusions, shrinks what the net measures without moving one reference — and a green cannot report that shrinkage. The immutability check now covers what decides the verdict in all three families: the references compared, the machinery comparing them, and the inputs saying what to measure. Paths are quoted.
  • modernize: the judge's own machinery is as immutable as its references (9f6ed9a)

    whyA lot that rewrites verify-oracle.sh or harness.py makes every later gate answer green without measuring anything, and no reference moves — measured as a real gap. refs_untouched now diffs the runner, the harness and the canonicaliser alongside refs/.

3.50.2 (2026-08-19) ​

Bug Fixes ​

  • bots: the clean-tree ratchet reads the whole clause, and the verb reaches the goal (#462) (62b1d84), references #461

    whyRevi's re-review of #461 (on the zero-touch lane's own commits) left two verified findings:

3.50.1 (2026-08-19) ​

Bug Fixes ​

  • fleet: fold Revi's advisory findings into the follow-ups (#461) (2b7abab)

    whyThe bare `!third_party/codex-agent-sdk-go/.claude/` negation re-included the WHOLE subtree, not just the fork's committed `rules/`: the root-anchored `.claude/skills/` rule on line 145 does not reach that depth, so `…/.claude/skills/JUNK.md` and `…/.claude/settings.local.json` came back as untracked. That is exactly the runtime junk `**/.claude/` exists to stop, and the fork is an active work target whose campaign bots commit with `git add -A`.

3.50.0 (2026-08-19) ​

Features ​

  • observability: Sentry/GlitchTip error tracking + JSON-default logs (Obsy dogfood) (#459) (e37a117)

    whyAdds Logger.SetHook: a callback invoked for every record at or above warn with the record's message and a private copy of its inherited fields. The slot is shared across WithField/WithFields forks like the writer and mutex, so a hook installed on the root logger reaches forks made before or after SetHook.

3.49.0 (2026-08-19) ​

Features ​

  • instrument: Obsy — Sentry/GlitchTip error tracking + log standardization bot (#458) (40c96da)

    whyNew catalog bot on the proven ADR-058 chassis (feature-dev/Billy shape): ONE adaptive campaign agent + deterministic build/test gate + in-loop adversarial review + bounded continuation loop + opt-in PR tail.

3.48.3 (2026-08-19) ​

Bug Fixes ​

  • dispatcher: a paused human-review run survives a restart (#455) (bdc14ef)

    whyAfter a reboot, sweepStaleLocalClaimsAtBoot frees the claim that parked a card whose last run sits on a human node, and resumableRunID excludes paused_waiting_human by design — so the next tick minted a fresh run from the workflow entry, silently superseding the paused one.

3.48.2 (2026-08-19) ​

Bug Fixes ​

  • feed-watch: a flaky aggregator costs one retry, not a day of veille (#456) (1f71fed)

    whyhnrss.org returned 502 for 9 of 69 feeds on a production run and served all of them seconds later. A feed missed is not a feed deferred: nothing re-reads the window nobody fetched, so nine sources went silently absent from that day's digest.

3.48.1 (2026-08-19) ​

Bug Fixes ​

  • byok: a key created for another team is stamped with THAT team (#436) (26dc032)

    whyThe api-keys store derives tenant_id from the context — on write it stamps the row, on read it filters — and requireAuth stamps the CALLER'S ACTIVE team. The team-scoped routes never re-scoped, so a key created for a team other than the caller's own landed as (scope_team = target, tenant_id = caller's active team): listable from the context that created it, and invisible to the runs of the team it was meant to fund.

3.48.0 (2026-08-19) ​

Features ​

  • devbox: a run that reads a repo need not build it (#450) (34ba661)

    whyTwo devbox.json files can supply a run's binaries — the bot's own and the target repo's — and until now both installed, always. But "this repo pins a toolchain" and "this run needs that toolchain" are different claims. A review reads a diff and writes comments; it built nothing, and it paid iterion's own 319 Nix paths, 406 MiB downloaded, 1.8 GiB unpacked, a desktop GUI stack included, before its first node. Twice today that cold realise outlasted the window the sandbox had to come up, and the…
  • feed-watch: a digest that has nothing to say, says so (#454) (2902cac)

    whyAn empty queue makes a digest exit at plan -> load_pending -> done: no LLM, no post, status finished. Correct behaviour, and indistinguishable from a healthy quiet week — every morning, for as long as it lasts. From 13 to 18 August five daily cyber digests did exactly that while the collector fed nothing, and nobody learned anything until the operator asked.

3.47.2 (2026-08-18) ​

Bug Fixes ​

  • runtime: a run drained before its first node is resumable (#449) (4314aa0)

    whySetup runs on the same ctx the node loop does, so the same two interruptions reach it: a runner drained mid-rollout, and an operator cancelling. The node loop classifies both — handleContextDoneWithCheckpoint writes failed_resumable for a drain and cancelled for an operator — while every pre-execLoop phase wrote a flat, terminal "failed".

3.47.1 (2026-08-18) ​

Bug Fixes ​

  • store: a directory holding only a lock is not a run (#437) (d9a6814)

    whyLockRun mkdirs the run directory to place its .lock, so an id that is locked and then never created — an abandoned launch, a crash between the lock and the first write — leaves a directory carrying nothing else. ListRuns reported it as a run, permanently: every LoadRun on it fails, and a consumer that reads the first id it is handed waits on a run that will never load. That is what made TestProcessBoardCardCarriesPRLaunchContext hang for its full 30s while the run it was waiting for sat behind…

3.47.0 (2026-08-18) ​

Features ​

  • feed-watch: a digest dates the window it covers, not today (#452) (1a6eb42)

    whyA digest drains a QUEUE, not a day. That distinction is invisible until something interrupts the run: a shut usage window, a paused schedule, a feed that came back. Then the queue holds five days of material and the digest presents it as this morning's news — dated today, ranked as breaking.

3.46.2 (2026-08-18) ​

Bug Fixes ​

  • usagecap: "contains a model node" is not "will call one" (#453) (785288c), references #451

    whyThe guard shipped in #451 asked whether a workflow CONTAINS something that can call a model. That is not the question a pre-flight needs, and on the very bot it was written for it answers wrong.

3.46.1 (2026-08-18) ​

Bug Fixes ​

  • usagecap: a cap on model spend must not stop a run that spends none (#451) (e88f65e)

    whyThe pre-flight refused every claimed run while the window was shut, without asking whether the run could draw on it. A workflow made of tool and compute nodes cannot: there is no model call to bill. Refusing it protects nothing.

3.46.0 (2026-08-17) ​

Features ​

  • usage-cap: stop below the provider's wall, at a percentage you choose (#438) (ccf178d)

    whyA subscription meters two rolling windows and refuses every call once one is exhausted. iterion already survived that refusal — the run parks and a durable retry resumes it when the window reopens. It could not stop BEFORE the wall, and the wall is rarely where an operator wants to be: the same subscription usually pays for their own interactive work, so a fleet of bots that drives it to 100% takes the human down with it.

3.45.1 (2026-08-17) ​

Bug Fixes ​

  • forge: a GitLab gate claim over an existing claim is claimed, not failed (#435) (7411369)

    whyGitLab's commit-status state machine refuses pending -> pending with HTTP 400 ("Cannot transition status via :enqueue from :pending"), where GitHub accepts the same POST as a no-op. The only writer that posts pending is the merge gate's in-flight claim, so the rejection meant the check read "absent" while a review was in fact running -- observed live on a repo where a second bot was invoked on a head another had already claimed.

3.45.0 (2026-08-17) ​

Features ​

  • clean: a run you can resume still owns its worktree (a902f11)

    whyEighth pass. The first whose end-to-end campaign — 18 worktrees, every class, both layouts, concurrent sweeps, re-runs — found no way to lose data. What it found instead was a question asked wrong since the first commit.
  • clean: the leftover checkouts reclaimed — landing decides, not age (c116029)

    whyA `worktree: auto` run that succeeds removes its checkout; one that fails or is interrupted leaves it behind for inspection and never comes back for it. On a long-lived store that pool is where the disk goes, and `runs prune` cannot reach it — its own doc says it never touches worktrees/.
  • golden-master: a converged campaign leaves a mergeable tree (d2bbf23)

    whyTwo sources of end-of-run dirt, each of which forced a human landing: the harness self-copy now materialises ONE canonical form and writes only on change (no more header churn between the standalone and the inlined node), and emit_runner commits its own emissions — runner, report, harness, a bytecode-cache gitignore — scoped to the oracle dir. A landed-by-hand net is where a hardened runner got clobbered once.
  • golden-master: a pending re-baseline request blocks the gate (369f6d3)

    whyFour requests sat unacted behind four consecutive green convergences: each one quarantines known-diverging entries out of the verdict, so the gate was green AROUND a narrowing net — the failure this bot exists to catch, one level up. Pending requests are now a conjunction term, parsed from the ledger's machine blocks; supersedence becomes the declared 'replaces' field (prose does not count), and an unparseable block is an escalation, never a guess. Selftest 32 -> 38.

Bug Fixes ​

  • clean: a bare repo is still a repo, and a photograph is not the present (c6f610f)

    whyThird adversarial pass, aimed at what the second one rewrote — which is where it found most of what follows.
  • clean: a guard buried in err == nil is a guard that disappears (3404f19)

    whySixth pass, on the fifth's own code.
  • clean: a repository that answers about itself proves nothing (cf3c94b)

    whyFifth pass. It overturns one of the fourth's fixes, which is the honest outcome: what round 4 removed as a false positive was the only thing standing between a self-contained clone and its own destruction.
  • clean: git answers in absolute paths, and a store dir need not (ec3f9cb)

    whySeventh pass. Six rounds had been finding regressions of the round before; this one found something that had been there since the first commit and that every guard added since silently depends on.
  • clean: git must be talking about THIS directory, and merged must mean built upon (2c0050a)

    whyAn adversarial pass broke the first cut in ways that destroyed work at the default level. Each of these was reproduced before it was fixed.
  • clean: re-derive the whole verdict before deleting, not just the dirty bit (5de5151)

    whyFourth and last adversarial pass, aimed at what the third one wrote.
  • clean: take the run's lock, and stop calling "we could not tell" a leftover (9e53b63)

    whySecond adversarial pass. The first one's fixes were right and incomplete: what it left open destroyed work, and what it over-refused made the command nearly useless.
  • golden-master: the emitted runner keeps the log out of the verdict it re-reads (61324fe)

    whyThe template piped the harness's stderr into the JSON report, so the runner died parsing its own progress messages — a campaign found it, hardened its materialised copy, and the next emission reintroduced the defect from this template. Fixed where it is emitted from.
  • server: resume dispatcher child gates from persisted source (#433) (4876453)

3.44.0 (2026-08-14) ​

Features ​

  • golden-master: the corpus states its perimeter, and watches what teams skip (75024e2)

    whyFive required corpus probes — a creation, the error-then-corrected journey, a case pair, a text sort, a login case variant — enforced mechanically before boot, exactly like the mutant archetypes: a tag without its shape does not count. A routes_probe + route-coverage gate names every route the corpus never touches, unless its exclusion carries a written reason. Write entries gain multi-step journeys in one session. The write surface gains a create_lost archetype. Fixture doctrine: seeded id…
  • modernize: crossing a major owes a sweep — eight drift classes on the record (dbdc109)

    whyA major redefines semantics under unchanged lines: routing edge forms, implicit binding conversion, query strictness, template attribute semantics, dialect functions, seed allocator state, collation, and defaults that flip silently. The upgrade-archetypes skill carries the eight classes as data; each is instantiated from the migration notes of the exact major crossed, probed mechanically, and recorded in the tree. The plan contract gains crosses_major, and the sweep record is checked by the…

Bug Fixes ​

  • golden-master: seal the held-out set where every runtime can write (7ef20a1)

    whyThe sealed pile lived beside the workspace, which assumes a writable parent — a sandboxed run mounts the worktree's parent read-only and the first seal died on it. The default root is now the system temp dir, and the name hashes the absolute workspace path: sibling worktrees of one repo share a basename, and under a common root they would otherwise seal into each other's pile. GM_SEALED_DIR and GM_SCRATCH still override.
  • pipelines: stop leaking review-scope banners when stepping turns (#427) (b80ed51)

    whyReviewScopePanel and HumanPromptForm shared the same React key as siblings. React's remaining-children map keeps only the last child per key, so Prev/Next unmounted the form and left every previous ReviewScopePanel mounted. A card with dozens of human gates then stacked "No file diff for this review" once per visit.

3.43.0 (2026-08-13) ​

Features ​

  • studio: preview JSON, markdown and text on human gates (#425) (919efc3)

    whyInbound file fields only rendered images, audio and video. A planner outline.json or a review brief therefore collapsed to Download, so the operator answered the gate without seeing the document.

Bug Fixes ​

  • backends: restore Codex as a supported backend (#419) (9db47d2)

3.42.0 (2026-08-13) ​

Features ​

  • ultra11y: an accessibility auditor whose findings no model can drop (#409) (e5ca08f), references maxgfr/ultra11y#15

    whyAcci's own bilan records the failure this bot exists to remove. On run 019f3d3b-7aea the RGAA review found four real defects, emitted them without the `status` field the gates count by, and the report published "0 non conformes" — four true findings erased between the agent and the deliverable. The gates were right. The DETECTOR was a language model, so a dropped field was indistinguishable from a clean repo, and the fix that day (count a status-less finding as NC) is damage control around the…

3.41.1 (2026-08-13) ​

Bug Fixes ​

  • vetty: a verify that overruns is a red build, not a crashed run (#422) (004abce), references iterion#386 #412

    whyObserved on iterion#386 today: a 20-minute verify ended the RUN, not the build. TimeoutExpired was caught and then the handler itself raised — e.output is bytes on the timeout path even though text=True was passed, because the decode happens after communicate() returns normally and a timeout attaches the raw buffer instead. bytes + str is a TypeError, so the operator got failed_resumable and a traceback where a verdict belonged.

3.41.0 (2026-08-13) ​

Features ​

  • forge: GitLab reads commit statuses, and a repo launch URL is canonical (#421) (543910d)

    whyGitLab's AdminClient gains ListCommitStatuses (the CommitStatusLister capability), the read half of the merge gate: without it the gate reconciler must abstain on GitLab — a review that dies leaves the required context absent forever — and the auto-fix lane can never launch, since it refuses to act on a gate it cannot see. GitLab returns every status row on a commit (retries included), so the result keeps only the newest row per name; a gate reader handed raw history could match a stale verdict…

3.40.6 (2026-08-12) ​

3.40.5 (2026-08-12) ​

3.40.4 (2026-08-12) ​

3.40.3 (2026-08-12) ​

Bug Fixes ​

  • vetty: a hold must say what broke, and a task body is not a command (#412) (c033cdd)

    whyThree holds on 2026-08-12 reported "build/tests not green" over an excerpt containing nothing but a list of ok lines and a bare FAIL. The report carried out[-4000:], and a test runner prints its per-package successes after the package that failed — so the blind tail is systematically the wrong excerpt. It now carries the matched failing lines too.

3.40.2 (2026-08-12) ​

Bug Fixes ​

  • expr: concat() is the array builtin — string joins use +, and a guard now says so (8125c06)

    whyA compute expression written concat('prefix: ', outputs.x.log) parses, compiles and validates clean, then dies at evaluation with "want array". It lives in notice/fail_log fields inside if(converged, …, <the concat>) — the FAILURE branch — so it crashes only once something else has already gone wrong, turning a reported failure into a dead run. It had shipped in a bundled subbot's refusal path; a sibling bot hit the same shape on its first real run and exposed the class.
  • review-env: an attached skill is a FILE, not a registry entry (82b8eff)

    whyFirst real run: the plugin mirrored deploy-target.md into the workspace's .claude/skills/, the agent asked the Skill tool for it by name, got "Unknown skill" — that registry carries the bundle's own skills — and correctly refused to deploy. The refusal worked; the premise was wrong. The prompt now names the mirrored PATH as the authority (the pattern app-dev's validated deploy phase already used) and says a missing registry entry is not evidence of a missing skill.
  • review-env: the cluster is the authority on pullability, not an anonymous probe (e14360c)

    whyTwo runs refused to deploy a NAMED image because a probe of the private registry answered 401 — a probe the agent was never meant to run: the pull credential lives in the namespace as an operator-provisioned imagePullSecret it cannot read, by design. So it predicted the failure instead of measuring it, and left the only question that matters unanswered. The prompt now says: once you can name the image, apply and let the rollout answer; ImagePullBackOff is a measurement you report.
  • review-env: the mounted path is the rendered template, not the env var (e18e9bc)

    whyMeasured on the first deploying run: a secret's env: declaration is injected into TOOL nodes (executor_tool, verified actions), not into a delegated agent's shell — $DEPLOY_CREDENTIAL was unset and the agent had to recover the path from the task header. The prompt now gives {{secrets.deploy_credential}} as the reliable path and says the unset env var is expected, not evidence of a missing credential.

3.40.1 (2026-08-12) ​

3.40.0 (2026-08-12) ​

Features ​

  • review-env: a live review environment as one leasable capability (1757a67)

    whyDeploys the workspace's already-CI-published image to the operator-attached platform and hands back a live https URL — realism for end-to-end tests, captures and review that localhost cannot give. The platform lives ENTIRELY in the attached deploy-target skill (one plugin enabled per instance; swapping infrastructure = swapping the plugin + the deploy_credential secret, never the bot), the credential is used strictly by reference, the image is the repo's own CI's, and the URL verdict is…

Bug Fixes ​

  • the last two Vetty holds, and the git-env scrub everywhere it belongs (#407) (bcc0952), references #390 #405 iterion#398 iterion#398

    whyTwo of the eleven Dependabot PRs from 2026-08-10 were held as "build/tests not green" without a build ever having been established.

3.39.0 (2026-08-11) ​

Features ​

  • campaign: a deterministic supervisor carries a whole programme, lot after lot (e693a02)

    whymodernize takes ONE lot per run by design; the programme is a suite of runs, and someone has to be the suite. That someone was a human first: a full modernisation programme was replayed end to end under manual supervision and the interventions were counted — nearly all mechanical, and the acceptance criterion held on every re-record act (observed diff == announced set, every time). This bot mechanises exactly what was mechanical and routes the rest to a human.
  • golden-master: the ledger speaks a machine-readable protocol — request, act, verdict (6eee2c2)

    whyA worker that may not re-record announces; the party that owns the net answers. Three HTML-comment blocks in REBASELINE.md carry the protocol so a supervising process can execute the separation of powers mechanically: a request names its lot, its cause and the EXACT expected paths; an act is written only when the observed diff equals that announcement; a verdict only when the full counter-test replayed green on the committed tree.

Bug Fixes ​

  • campaign: bool/json inputs render as JSON literals — name the atoms (1545155)

    why{{input.moved}} arrives in a python script as the token 'false', and any json field can carry true/false/null inside. Found by the bot's second real run: the steward crashed on NameError. One line gives the three JSON atoms their Python names before interpolation.
  • campaign: the engine's materialised node script is not work in flight (15ab7b0)

    whyEvery script tool sees its own .iterion-script-*.py in the workspace for the lifetime of the node (executor_tool materialises it there), so the supervisor's clean-tree refusal fired on every run, and the steward would have counted it in every observed diff. Found by the bot's first real run: preflight refused a freshly created worktree. Excluded from all three status reads — it is execution machinery, never part of a verdict.

3.38.1 (2026-08-11) ​

Bug Fixes ​

  • git: the pkg/git suite must not report on its own environment (#405) (40231ab), references #392 #395 #397 #399 #394

    whyFour of the eleven Dependabot PRs opened on 2026-08-10 were held on a red build none of them caused: #392, #395, #397 and #399 all failed `TestLogAllowsTabsInUserControlledFields` with `author: got "iterion-forge-61934180[bot]"`.

3.38.0 (2026-08-11) ​

Features ​

  • cloud: carry loop_budget_guard onto the queue and into detached runs (#406) (a6b7494)

    whyThe run-level override stopped at the launch boundary: a cloud pod re-resolved the guard from the workflow and its own empty environment, so `--loop-budget-guard off` on a bot that declares nothing ran guarded anyway, and `on` against a bot declaring `off` could still strand its work at the cap. An operator's explicit choice, quietly re-made elsewhere — the failure `auto_memory` closed at schema v6.

3.37.0 (2026-08-11) ​

Features ​

  • dsl: loop_budget_guard through the full precedence chain (#404) (c9014fd)

    whyThe affordability guard shipped with a process-env escape hatch only, so turning it off was a deployment-wide decision with no per-run or per-bot say — the one engine dial that did not follow the chain every other one uses.

3.36.4 (2026-08-11) ​

3.36.3 (2026-08-11) ​

Bug Fixes ​

  • a lost alignment must not merge as a clean bump (#400 post-mortem) (#401) (0e33942)

    why`commit_check` decided between the `committed` and `clean` verdicts from the shas alone. An unmoved head has two causes, though, and they are opposite verdicts: the bump genuinely needed no alignment, or `align` produced one that never reached the branch. Both are green under the old wiring, and `clean` is what the required check reports.

3.36.2 (2026-08-11) ​

Bug Fixes ​

  • runtime: decline a loop back-edge the budget cannot fund (#402) (a62939a)

    whyA loop that banks work as it goes — commits in stride, a published report, a PR opened by a tail node — used to start an iteration it could not pay for, die mid-iteration on BUDGET_EXCEEDED, and leave the tail that would have delivered the work unreached. iterion's own docs-refresh weeklies lost 31 and 29 alignment commits that way, on a clone that died with the pod.

3.36.1 (2026-08-10) ​

3.36.0 (2026-08-10) ​

Features ​

  • bots: non-blocking teach-back on ambiguous missions (6418393)

    whySwitch the feature-dev and whole-improve-loop campaign nodes from interaction: human to interaction: async (ADR-081) and rewrite mission item 5: on an ambiguous mission (or a self-picked axis), the campaign posts a teach-back via ask_user_async — the goal restated in its own words plus the load-bearing assumptions — and KEEPS WORKING under those stated assumptions; answers fold in mid-run via the message queue. The blocking ask_user stays reserved for genuine hard stops, and unanswered…

3.35.3 (2026-08-10) ​

Bug Fixes ​

  • runner: a weekly cap parked four reviews with nothing coming back (#389) (c710817)

    whyusageWindowRetryAt documents three evidence sources for "the provider's window is shut": the typed error, a classified runtime code, "and the flattened message is a last resort for a host that has neither — which is not hypothetical, since a runner with no dispatcher wired classifies nothing at all." The third one was never implemented. usageWindowEvidence returned false unless a type or a code survived, so on any host where neither does, the provider's own words — sitting right there in…

3.35.2 (2026-08-10) ​

3.35.1 (2026-08-10) ​

Bug Fixes ​

  • gate: a review in flight is indistinguishable from one that never ran (#387) (da6afa1), references buildkit-operator#19

    whyThe merge gate only ever posted a verdict, at the END of a run that takes minutes. For that whole window the required context carries NO status, which a forge renders as "Expected — waiting for status to be reported" — byte-identical to a review that was never launched. Read next to the reviewer's comment on the previous commit, it looks exactly like "the bot commented but the gate never went green". Reported from production on buildkit-operator#19 today.
  • modernize: a blocked lot was re-attempted on every run, forever (98324c9)

    whyMeasured on a replay campaign: a lot declared itself blocked after forty-two minutes — the toolchain raise it carried changes observable behaviour, and it said so in a committed report rather than force its gate green. The NEXT run picked the same lot again, redid the same work, and hit the same wall. In a single-run workflow that is harmless, since a human reads the report and decides. In a loop it wedges, and it burns the budget doing it.

3.35.0 (2026-08-10) ​

Features ​

  • golden-master: a write surface, and two archetypes that were required nowhere (3cd5a6e)

    whyThe net only ever read. A corruption applied when content is STORED — a tag lost, an attribute normalised, an identifier drawn afresh on every save — moved no reference and passed the gate green, so proving it took a script outside the net. That is where such proofs end up when the net cannot write.
  • golden-master: the report names which mutants could no longer be applied (a994d8c)

    whyThey were already said, in free text, in the middle of the log. A mutant goes invalid for two very different reasons — it mutates nothing, so it never proved anything; or its anchor vanished under a legitimate change, so it DID prove something and has stopped. Only the second is mechanically repairable, and telling them apart means reading a field rather than matching a phrase.
  • modernize: repair a mutant the lot invalidated, by delegating to the net's own bot (2e682d2)

    whyA modernisation lot is entitled to rename a method or restructure a template. When it does, a mutant that patched the old form stops patching anything. The harness calls it INVALID, correctly, and the surface it probed stops being covered — while NOTHING goes red, because an invalid mutant is excluded from the score and the figure looks no worse. A lot can go green while the net gets narrower, which is the one failure a green cannot report.
  • modernize: surface which mutants the oracle could no longer apply (383bf9a)

    whyA lot may legitimately remove the thing a mutant hooks into — a security major withdraws the matcher idiom one named, a front-end major replaces the configuration block another edited. The patch stops applying, the harness marks the mutant invalid, and an invalid mutant neither scores nor dilutes: it simply stops proving anything, quietly, on whichever lane it covered. The gate can stay green while the counter-test that made that lane worth trusting has gone dark.

Bug Fixes ​

  • golden-master: collateral blamed the mutant without ever testing a third cause (9ee97e8)

    whyA control entry that differs from its reference was attributed to the mutant, and the message offered exactly two explanations: an under-declared blast radius, or a capture that is not isolated. There is a third, and it is the only one in which the mutant plays no part — THE CONTROL ENTRY DOES NOT REPRODUCE ITSELF. Whichever mutant happened to sample it wears the blame.
  • golden-master: the two copies of the harness had drifted, and the test said otherwise (c19926e)

    whyThe harness exists twice: inlined in main.bot's oracle_run node, which is the copy that runs, and as oracle-harness.py, which is the copy a human reads. A test claimed to keep them in sync. It pinned the set of top-level function names and the report fields, on the stated grounds that verbatim comparison was impossible.
  • runview: reset dropped nodes' execution state on run_rewound (#382) (353c0c2)

    whyA rewind invalidates the dropped nodes' checkpoint outputs, but the snapshot the studio renders node colours and infos from is folded from the append-only event log — and none of the three reducers handled the run_rewound event Rewind appends. The pre-rewind node_started / node_finished records kept folding in, so rewound nodes stayed painted with their pre-rewind status, duration and error instead of resetting to never-run.

3.34.2 (2026-08-09) ​

Bug Fixes ​

  • golden-master: a reference carrying a carriage return never reproduced itself (ab9c83c)

    whyThe harness wrote references without a newline setting and read them back without one either. On Linux the write default translates nothing and lets carriage returns reach the disk; the read default enables universal newlines and turns them into line feeds. From the moment such a reference is recorded it differs from what the capture produces — permanently, with no code having moved.

3.34.1 (2026-08-08) ​

3.34.0 (2026-08-08) ​

Features ​

  • improve-loops: the ratchet — name it, and ask for it before the report (37644da)

    whyThe asymptote says why a run stops. Nothing said why the next run does not re-earn what this one banked, though the machinery for it ships everywhere: a gate reading a real exit code, a commit landed per verified unit, a diagnostic that makes a defect class impossible to repeat, a bilan that outlives the run. docs/improvement-ratchet.md gives those parts one name and one image each, and states the divergence from the family of ideas everyone will recognise: continuous improvement is unending,…

Bug Fixes ​

  • rewind: scope the workspace restore to what the run recorded changing (#381) (4e29c0a), closes #380, references #380 #380 #380

    why`iterion rewind` forced the ENTIRE workspace back to the pivot's snapshot. On the default run shape — no `worktree: auto` — that workspace is the operator's live checkout, so one rewind reverted 38 tracked files and deleted 2 that no node of the run had ever written (#380).

3.33.1 (2026-08-07) ​

3.33.0 (2026-08-07) ​

Features ​

  • golden-master: a canvas lane, and the browser plumbing it shares (276ee97)

    whyA canvas is the one surface nothing else can observe: the served document carries an empty tag, and the DOM stops changing once the image is painted. An accessibility audit says so itself — a datum rendered only as colour or as canvas is not restituted. A chart that stopped drawing entirely would leave every reference in a repository identical to the byte.

Bug Fixes ​

  • runtime: keep DSL-fail runs rewindable by preserving the checkpoint (#376) (5f56229), closes #373

    whyA run that reaches the DSL fail node was definitively unrecoverable: the fail path wrote status failed without a checkpoint, and the status transition purged the one that existed, so neither resume, rewind, nor cancel could bring the run back to a rewindable state — even though the on-disk state was coherent and the workspace snapshots survived.
  • runview: let a fork replace its dead parent on the pipeline board (#377) (e4c17c7), closes #374

    whyA run launched from a board card and recovered via fork disappeared from its card for good: the card kept showing the dead parent with no way to detach it, while the fork — the operator's actual recovery — ran invisibly. Since fork is the only way to recover a terminal run, recovery came at the price of board tracking.
  • runview: stop the skip-run and finalize log loops (#378) (a8e3c11), closes #375

    whyRun ids whose run.json was gone stayed listed by the store and were reloaded on every UI poll, each producing a WARN line — several lines per second, indefinitely, drowning the instance log (50 MB of noise on an active instance). Same family: the finalize recovery re-warned 'cannot read worktree HEAD' every minute on deleted worktrees.

3.32.0 (2026-08-07) ​

Features ​

  • cross-backend model fallback chain (fallbacks:) (#365) (38d4f07), references #1

    whyDischarges the cross-API deferral ADR-004 recorded in its Decision (5) and Alternative #1, and records why the obvious generalisation (flip providerFallbackEligible) is unsafe: a backend swap re-shapes seven delegate.Task fields, three pre-run analyses read a single static backend name, and both the run-level usage-window retry and the credential-pool donor cooldown key on the terminal error's type.
  • e2e-coverage: Endy — matrix-anchored e2e coverage completion bot (f3de156)

    whyADR-058 v2 shape (one campaign + deterministic gate + bounded continuation), specialized for FEATURE-level e2e completeness: a committed feature×coverage matrix is the inventory, the living todo, the done-oracle and the audit trail. The verify_run gate enforces the matrix contract deterministically — parse, allowed statuses, justified exceptions, and a claims check where every covered-* row must cite a test that resolves in the tree (an orphan claim is a red gate). new_test_code is…

Bug Fixes ​

  • cli+e2e: two round-2 test findings — a base64 blind spot and a 210s detection (c0d3080)

    whyThe secret round-trip claimed 'no plaintext on disk' but grepped only the raw bytes; the sealed field is a []byte, which json writes as base64, so a Seal/Open pass-through mutation (no encryption at all) survived the assertion. It now greps the base64 forms too — measured: the mutation that survived is killed, naming the store file.
  • e2e-coverage: close eight false-green bypasses in the matrix gate (7dcf452)

    whyAn adversarial review executed seven distinct ways to make the gate say matrix_ok=true on a matrix that proves nothing, counts less than it claims, or is not even the table the operator reads:
  • e2e-coverage: inventory the nine surfaces the audit found missing (c5521d5)

    whyThe matrix claimed completeness while nine operator-observable surfaces had no row at all — an omission is exactly what the inventory promise forbids. Six were already covered and only needed citing (bots install core, `bots templates`, plugin lifecycle run, /api/v1/pipeline-board, /api/v1/limits/cost, /api/backends/detect); three are real gaps now visible: `iterion server` and `iterion runner` CLI boot, and /api/effort-capabilities (reached today only as a readiness probe that asserts nothing…
  • e2e-coverage: repair eleven more mis-citations found by the second audit (9ecb92b)

    whyA second adversarial pass sampled 40 fresh rows (70 across both audits) and found the same failure mode as the first: a row cites the mechanics of a helper while the WIRING that invokes it goes untested.
  • e2e-coverage: repair four façade citations found by the matrix audit (dc7d422)

    whyAn adversarial audit sampled 30 covered rows and found four whose cited test would pass while the promised feature is broken:
  • e2e-coverage: round-2 gate hardening — a false POSITIVE and six more bypasses (83e588d)

    whyThe round-1 hardening was itself reviewed adversarially. The worst finding is a false positive, which in a blocking gate costs as much as a hole: the test-file regex required a slash on BOTH sides of tests/ and spec/, so a ROOT-level tests/ (Rust, pytest), spec/ (RSpec) or __tests__/ (Jest) was rejected — this gate would have refused the legitimate matrix of most non-Go repos and could never converge there.
  • e2e-coverage: round-3 — the round-2 hardening had narrowed the gate to Go (2e8d7a7)

    whyRound 2 fixed a false positive and introduced two more, in the same place: the gate had quietly narrowed to matrices whose citations look like Go test function names — which is this repo's shape and almost nobody else's.
  • e2e-coverage: verify.sh must be overwritten and workspace-relative (0af0da7)

    whyThe scratch dir is per-project, shared across runs: V3's verify_build found V1's script pinned to a dead worktree path. The prompt now mandates overwrite + $PWD-relative commands (the gate already runs the script with the repo root as cwd).
  • e2e: two live fixtures died on a cd into an unexpanded variable (3c822d1)

    whyfeat_worktree.bot and feat_compress.bot both opened their tool command with cd "${PROJECT_DIR}". That placeholder is only expanded in a bot var DEFAULT, never inside a command, so the shell got cd "" and the node failed before doing anything — TestLive_Feat_Worktree and TestLive_Feat_Compress could never pass, whatever the feature did.
  • queue: a message from a newer server is transient, not malformed (ca2ef58)

    whyA runner that could not decode a delivery Termed it, whatever the reason. For a malformed payload that is right — no consumer will ever decode it. For a payload from a NEWER server it destroys a run: the queue entry is gone while the run document stays `queued` forever, and the only trace is one line in one pod's log. Nothing surfaces to the operator, who sees a run stuck in "queued" with no explanation.
  • runtime: a structured LLM call now anchors a turn, like a text one (5d162d8)

    whyTurn capture lived only in GenerateTextDirect. Every node declaring an output: schema — which in iterion is most of them — runs through GenerateObjectDirect and so anchored NOTHING: no TurnCheckpoint, an empty per-node timeline, and `iterion fork` failing with "turn not found" on a run that had plainly executed. The Fork API and the timeline were effectively blind to the majority of claw nodes.
  • studio: a workflow with no LLM nodes no longer crashes the Launch view (457374d)

    whyPOST /api/runs/preview-cost answers {"nodes": null} for a workflow with no agent/judge node, and CostPreviewChip dereferenced data.nodes.length — the whole Launch view fell into its error boundary, so a tool+compute-only bot could not be launched from the studio at all. Found by the new studio UI Playwright suite (V4 dogfood, run 019fd6e6); the KNOWN-BUG tripwire test now asserts the positive contract instead.
  • test: detect nested checkouts by their .git, not by directory name (bbe2dd9)

    whyThe previous fix hardcoded .claude and .works — one is this harness deal, the other is a purely local convention of one operator. Where someone parks their worktrees and sibling clones is not this repo business.
  • test: the bot-identity audit walked into the operator nested checkouts (8c42f09)

    whyTestEveryExecutorConstructionDecidesTheBotIdentity walks the tree to find executor constructions that skip the bot identity, but its skip list did not cover .claude/worktrees or .works — git worktrees and sibling repos an operator keeps on disk. None of their files are tracked here, and their older copies report as offenders of a rule they predate, so the test passed inside a worktree and failed in the main checkout.

3.31.1 (2026-08-05) ​

Bug Fixes ​

  • runtime: iterion's own scaffolding kept converged runs from landing (597b066)

    whyAt run start iterion mirrors the bundle's skills into the worktree under `.claude/skills/`. Finalize then read those untracked files as "the bot left work uncommitted", banked them as a wip commit — and a wip-banked HEAD is never merged, by design. The result: a run whose gate CONVERGED did not land, and the only thing standing in the way was iterion's own scaffolding.

3.31.0 (2026-08-05) ​

Features ​

  • dsl: add auto_memory: — a per-node MEMORY.md switch, off by default (0283edc)

    whyAuto-memory behaved differently on each backend, silently, and no .bot author could control it: claude_code's own default is ON, so every node of every run read and wrote the operator's personal ~/.claude/projects/<cwd>/memory/, while claw and pi had no MEMORY.md at all. On a cloud pod, anything written to a pod-local directory died with the run.

Bug Fixes ​

  • runtime: a run launched from a linked worktree described another branch (ca72827)

    why`iterion run` resolves the repo root up to the MAIN repository — that is where .git lives and where worktrees are registered, and it is correct. It then read `HEAD` there too, which is not: a linked worktree has its own HEAD and its own branch, and the run silently anchored on whatever the main checkout happened to be on.
  • sandbox: host_state=none dropped the git identity along with the mount (365c048)

    whyUnder host_state: auto the operator's ~/.gitconfig is bind-mounted, and that mount is what gives an in-sandbox `git commit` an author. Turning host state off — the documented setting for multi-tenant and cloud runners — removed the mount and, with it, the identity: every commit-producing bot then dies on "Author identity unknown", for a reason unrelated to what it was asked to do.
  • sandbox: the shipped images declare no locale, so the JVM read ASCII (0223a9e)

    whyMeasured on the slim image: `LANG` and `LC_ALL` are both empty. That is not a neutral state — it IS the C/POSIX locale, and a JVM derives `sun.jnu.encoding` from it and decodes filenames as ASCII. A build whose resources carry an accented name fails on "Problems opening file input stream", naming a file that is plainly on disk. An agent working in the sandbox has to discover this and prefix its own commands to get past it.

3.30.13 (2026-08-05) ​

Bug Fixes ​

  • dep-update-guard: the drift-gate precheck loops back instead of discarding the run (#370) (722bacf)

    whyTwice in one day the aligner's correct Vite 8 migration was thrown away because verify.sh omitted the repo's CI drift gate and the deterministic precheck fired at VERDICT time (rc=3 → hold_unstable, alignment discarded). The omission is an authorship defect, not a red build: the agent scopes 'bump-relevant' and rationalises away repo-wide gates (a studio bump 'cannot drift' the Go openapi — but §1b gates are never scoped out).

3.30.12 (2026-08-05) ​

Bug Fixes ​

  • dep-update-guard: verify.sh mirrors CI's exact strictness (#369) (d514f52), references #19

    whyEstimateUSD consults claw's LIVE pricing registry first, so the price tests' expectations depended on whatever the network returned that day — flaky on the host, and reliably wrong inside sandbox pods, where this failure has now twice painted a Vetty verify red on an otherwise CI-green PR (#19's Vite 8 alignment being the second). Pin the tests to the fallback path via CLAW_DISABLE_LIVE_REGISTRY=1 except where the live-cache behaviour is itself under test (seeded explicitly).
  • golden-master: the dirty-tree notice mangled the first path it named (8314a52)

    why`git status --porcelain` writes `XY <path>`, and X is a space for an unstaged modification. Stripping the whole output before splitting ate that leading space on the first line only, so it shifted by one and lost a character: `build.gradle` came out as `uild.gradle`. Later lines were intact.
  • modernize: refuse when the contract cannot be READ, instead of finishing green (7276c3c)

    whyA run completed with Status FINISHED having executed no lot at all. The plan reader could not find yq, emitted nothing_to_do, and the graph routed straight to done. Every failure path in that reader called the same emit(), which exits 0 — so three very different outcomes collapsed into one benign one:
  • sandbox: make devbox actually work for every bot and every repo (ac609c9)

    whyA bundle or a repo could declare devbox packages and get NONE of them, with the run continuing as if they were there. Three causes, each measured on a real run rather than reasoned about, and each fixed where it belongs.

3.30.11 (2026-08-05) ​

3.30.10 (2026-08-04) ​

3.30.9 (2026-08-04) ​

3.30.8 (2026-08-04) ​

3.30.7 (2026-08-04) ​

Bug Fixes ​

  • runtime: a failing llm half of llm_or_human degrades to the human pause (#367) (5819b04)

    whyA human node's llm_or_human half runs through GenerateObjectDirect, which takes provider/model-id and has no backend to infer the provider from; the escalate node carried a bare 'claude-opus-5' default from birth. Every prior run took the clean/committed routes around it, and the FIRST needs_decision bump in production (plugin-react 4→6, 2026-08-04, run 019fcd8e-fe0b) crashed with 'invalid spec' at the exact moment the workflow existed to hand over.

3.30.6 (2026-08-04) ​

3.30.5 (2026-08-04) ​

Bug Fixes ​

  • forge: keep the issue-lane label allowlist across re-provisions (#363) (2dec4f1)

    whyNarrowing which freshly-applied issue label dispatches the implementer (`label_allowlist`) was a webhook-config PATCH, and Provision rebuilds that config as a whole literal from the manifests. Any bot-set change — the studio Integrations tab PATCHes bot_ids and nothing else — therefore dropped the narrowing, silently and fail-OPEN: an empty allowlist matches every label, so the repo returned to starting a feature-dev campaign on any label added to any issue.

3.30.4 (2026-08-04) ​

Bug Fixes ​

  • dep-update-guard: the escalate node could never fire — bare model spec crashed the direct generation path (#366) (a78b6a7)

    whyA human node's llm_or_human half runs through GenerateObjectDirect, which takes provider/model-id and has no backend to infer the provider from; the escalate node carried a bare 'claude-opus-5' default from birth. Every prior run took the clean/committed routes around it, and the FIRST needs_decision bump in production (plugin-react 4→6, 2026-08-04, run 019fcd8e-fe0b) crashed with 'invalid spec' at the exact moment the workflow existed to hand over.

3.30.3 (2026-08-04) ​

3.30.2 (2026-08-04) ​

3.30.1 (2026-08-04) ​

3.30.0 (2026-08-04) ​

Features ​

  • show a node's file changes in the run console (#352) (b6f1b11), references #351 #349 #349 #351

    whyIterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node. `iterion rewind` re-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, so `iterion resume` picks up from there. Same run id — distinct from `fork`, which mints a child for an alternative future and leaves the parent intact.

3.29.0 (2026-08-04) ​

Features ​

  • a review gate shows everything changed since the previous gate (#351) (d9bd4cc), closes #349, references #349 #349 post-#349-squash #349

    whyIterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node. `iterion rewind` re-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, so `iterion resume` picks up from there. Same run id — distinct from `fork`, which mints a child for an alternative future and leaves the parent intact.

3.28.1 (2026-08-04) ​

Bug Fixes ​

  • credpool: a donated credential must reach the agent, and be accounted for (#360) (a4926ab)

    whyThree defects a live end-to-end run on production surfaced. None was reachable by reading: the chain works right up to the runner pod, and breaks in the layer below it.
  • runtime,runner: a budget death is terminal-acked, never redelivered (#361) (5f64a87)

    whyThe engine's per-node budget checks built a bare RuntimeError (code only, no sentinel Cause), so the runner's terminal-ack carve-out — which matches errors.Is(err, ErrBudgetExceeded) — missed it and naked the delivery back to JetStream. Observed live (run 019fcc30-b9be): a 96% duration hard limit at the last node turned into six ~40s resume/refail turns, each re-provisioning a sandbox to instantly re-hit the same spent budget. The branch scheduler's twin checks already wrapped the sentinel;…

3.28.0 (2026-08-04) ​

Features ​

  • iterion-owned workspace versioning (#349) (88fb897)

    whyIterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node. `iterion rewind` re-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, so `iterion resume` picks up from there. Same run id — distinct from `fork`, which mints a child for an alternative future and leaves the parent intact.

3.27.2 (2026-08-04) ​

3.27.1 (2026-08-04) ​

Bug Fixes ​

  • studio: file in-progress pipeline tickets as done when their run finishes (#359) (3f73783)

    whyThe studio admission loop moved a launched ticket to in_progress and stamped last_run_id, but nothing moved the ticket back out once the run reached a terminal status. The run's status drives the /pipelines column, yet hard blockers only count ticket state == done (native.BlockerSatisfied), so a cleanly finished ticket stranded in in_progress forever and every dependent parked in waiting_deps.

3.27.0 (2026-08-04) ​

Features ​

  • mcp: operator MCP server exposing local + remote iterion (iterion mcp) (0727783)

    whyAdd the public `iterion mcp` command: a stdio MCP server any client (Claude Code, desktop, Cursor) registers to drive iterion end to end, with two tool families (41 tools, readOnlyHint annotations):

Bug Fixes ​

  • mcp: harden the operator MCP server per adversarial review (7a37113)

    whyAddress every finding of the opus adversarial pass on 603161446:
  • server: relaunch claim is per-bot; rune-safe reason truncation (#358) (3080fb2), references #357

    whyTwo follow-ups from Revi's review of #357 (R41df5e, R60c7c8):

3.26.1 (2026-08-04) ​

Bug Fixes ​

  • server,dep-update-guard: a dead merge-gate run recovers instead of silently blocking its PR (#357) (bc9a99c), references SocialGouv/iterion#354 353/#355

    whyA run that owed a merge-gate status and died as failed_resumable was never reconciled — on the theory that it would resume. Only usage-window failures arm a retry; a budget-exceeded or exhausted run sat forever and its PR stayed silently unmergeable behind an absent required check (observed in production 2026-08-03: Vetty run 019fc8e5 on SocialGouv/iterion#354).

3.26.0 (2026-08-04) ​

Features ​

  • credpool: lend keys of any provider + fixes from Revi and a live prod run (#356) (d417368)

    whyA pledge now offers a Credential — a (source, ref) pair — instead of an OAuth kind alone: `oauth/claude_code` as before, or `api_key/anthropic`, `api_key/openai`, … for a personal BYOK key of any provider iterion knows.

3.25.0 (2026-08-04) ​

Features ​

  • rewind a run in place to an earlier node (#348) (582e891)

    whyIterating on a bot's configuration meant relaunching from scratch: edit a prompt, and the only way to test it was to re-pay for every upstream node. `iterion rewind` re-anchors an existing run's checkpoint on a node it already executed and invalidates what the replay will regenerate, so `iterion resume` picks up from there. Same run id — distinct from `fork`, which mints a child for an alternative future and leaves the parent intact.

3.24.1 (2026-08-03) ​

3.24.0 (2026-08-03) ​

Features ​

  • credpool: mutualise contributors' unused LLM subscription quota (#350) (8ad6af0)

    whyDevelopers lend the unused part of their Claude Pro/Max or ChatGPT subscription; a run with no credential of its own draws on it, bounded by ceilings the lender sets and revocable at any moment.

Bug Fixes ​

  • delegate: include the recovery formatting pass's CLI cost in annotation (6c6383e)

    whyrunRecoveryFormatterPass folded its tokens into the running totals but its ResultMessage never reached annotateCost, so the cost annotated after a recovery pass was Pass 1's stale total_cost_usd (or missed the pass entirely under per-invocation accounting). Return the pass's ResultMessage and feed it to the same max-across-messages selection the two-pass path already uses. Found by adversarial review.
  • delegate: price claude_code cost with the effective model and CLI-reported cost (d304cf3)

    whycost.Annotate received task.Model — the node-declared model, empty on every node that relies on backend auto-detection — so EstimateUSD priced against an unknown model, emitted no _cost_usd, and the whole run reported tokens but no cost (the studio report then shows its 'no cost recorded' placeholder forever; observed on the feed-watch cloud runs, where system/init resolved claude-opus-5 but the node declares no model).
  • feed-watch: canonicalize item URLs through redirects before synthesis (03467f3)

    whyAggregator feeds (FeedBlitz, FeedBurner) serve tracking URLs whose host is the aggregator's, not the article's. The synthesize agent web_fetches those items, lands on the canonical article and naturally cites its final URL — which the deterministic verify_message gate then rejects as off-item, failing the whole digest (observed live: the java digest run 019fc65e died on two baeldung.com links served as feeds.feedblitz.com items).
  • forge: re-mint the github_app managed token at every repo-targeted launch (0c14674)

    whyEnsureManagedSecret returned the stored managed-secret id without touching its plaintext — a ONE-HOUR GitHub App installation token minted at provision time. On a quiet connection (no provision, no worker rotation) every repo-targeted launch pinned that dead token and failed its clone with 'Invalid username or token' (observed live on prod relaunching the feed-watch java digest; the daily schedules never noticed because they resolve the team's forge_token binding instead). Re-mint at the point…
  • runner: fire run-outcome side effects only on final delivery dispositions (41c7e6f)

    whyA generic engine failure Naks for redelivery, and every failed delivery fired the completion webhook + run.<outcome> event anyway — one 'run failed' notification episode per redelivery (the episode key folds updated_at precisely so a later REAL re-failure notifies again), i.e. up to MaxDeliver pushes for a single deterministic failure within a minute (observed live: feed-watch run 019fc65e re-failed 8 times in 70s on its checkpointed verify input).
  • studio: render token-only run reports instead of the no-cost placeholder (86f2bfb)

    whyA run whose model could not be priced records _tokens without _cost_usd; the Report tab treated that as 'no cost yet' and showed the fills-in-as- nodes-finish placeholder forever, even on finished runs. The report now renders whenever any usage (cost OR tokens) exists: bars and rankings scale by tokens when no cost was recorded, and cost cells show an explicit '—' rather than a fake $0. Buckets tie-break by tokens so a tokens-only report still ranks meaningfully.
  • studio: show per-bucket cost as unavailable instead of a fake $0.00 (2143f6a)

    whyIn a mixed run (some nodes priced, some not) the report-level hasCost flag rendered unpriced buckets as "$0.00 / 0%" next to their real token counts, reading as "this node was free". The cost and percent cells now decide per bucket: no recorded cost renders as an explicit unavailable marker. Found by adversarial review.

3.23.2 (2026-08-02) ​

3.23.1 (2026-08-02) ​

Bug Fixes ​

  • studio: closed pipelines read newest-first (#346) (cb62642)

    whyThe inventory's sort was one value shared by both tabs, defaulting to priority. But priority is a launch-order key, and a pipeline that already ran will never be launched by it again: on Closed the ranking buried this morning's run under a months-old P9.

3.23.0 (2026-08-02) ​

Features ​

  • runtime: a tool node can hand a file it produced to the run (#347) (fd0d0b8), references iterion#332 #336

    whyA gate previews a `file` value by fetching `GET /api/runs/{id}/attachments/{name}` — the descriptor's path is a host or sandbox bind-mount path and is not reachable from a browser (iterion#332, #336). So attachments only ever entered a run from a PERSON: the launch form, a `file`-typed gate field, the 📎 button.

3.22.3 (2026-08-02) ​

Bug Fixes ​

  • permission: a grant lasted exactly one tool call (#345) (e7a80bf)

    whyThe pause tells the operator their answer is added "to the allowlist for the rest of this run". It was added to one policy, for one re-invocation, and then dropped.

3.22.2 (2026-08-02) ​

Bug Fixes ​

  • tool: a json field holding a list broke out of its shell assignment (#341) (4a517a2)

    whyA schema field declared `json` reaches shellEscapeValue as []any and falls into the scalar-slice arm, which space-joins individually-quoted elements. In an assignment position that is not one value, it is several words:

3.22.1 (2026-08-01) ​

3.22.0 (2026-08-01) ​

Features ​

  • studio: a needs-attention lane that reserves its concurrency slot (#344) (b49d74b)

    whyA pipeline that died mid-flight used to file itself under Closed and hand its concurrency slot straight to the next queued card. Closed means "this reached its end", and a crash did not — so the failure read as done, and by the time the operator fixed it the slot was gone.

3.21.0 (2026-08-01) ​

Features ​

  • runner: graceful lame-duck drain of in-flight runs on deploy (3ec777f)

    whyIn cloud mode a rolling deploy SIGTERMs the runner pods. Previously a gracefully-drained in-flight run was checkpointed as `cancelled`, which the redelivery reconciliation deliberately drops (anti-resurrection guard) — so the run needed a MANUAL resume and every deploy emitted a false "run cancelled" web-push. Only the crash/timeout paths auto-resumed.

Bug Fixes ​

  • drain: close what auto-resume re-opened (3e3c2f0)

    whyMaking an interruption auto-resume turns a rare, operator-driven recovery into a routine one, so paths that were safe because nobody took them are now taken on every scale-down. Found by an adversarial pass over the rebase.
  • gate-autofix: the unattended launch reached Mongo with no tenant (b68f8a3)

    whyThe auto-fix lane stamped the auth identity the admission gate reads and not the store identity every tenant-scoped query asserts on. A bus handler is not an HTTP request and carries neither; the inbound-webhook middleware stamps both and this lane copied half of it.
  • runner: don't resurrect an operator cancel during a lame-duck drain (f700b5b)

    whyAdversarial review found a HIGH regression: the shutdown-vs-operator distinction inferred "shutdown cancel" from parent.Err() (the loop ctx), which only means "a SIGTERM happened" — not "this run's cancel came from shutdown". In the default lame-duck mode the pod holds the run for up to DrainTimeout (8h), during which an operator cancel (via the iterion.cancel.<run_id> subject) would be misclassified as a shutdown interruption, promoted cancelled→failed_resumable, and auto-resumed —…
  • runtime: route mid-node interruptions through the cause-aware handler (b14e11c)

    whyAdversarial review of the cause-threading refactor found the clean handling only fired for the between-node/pre-run window (the top-of-loop select). The COMMON real case — a deploy drains a run WHILE an LLM node is executing — surfaced the cancellation as the node's execErr and routed it through failRunWithCheckpoint, which stringifies the error and loses the ErrRunInterrupted sentinel: the runner then fired a spurious "run failed" notification and stamped finalStatus=failed, even though the…

3.20.0 (2026-08-01) ​

Features ​

  • studio: resizable pipeline card drawer + expandable input/output values (#335) (c871a29)

    whyA value box that never puts its content permanently out of reach: short values render whole, long ones collapse to a preview with a "Show all N lines" toggle that expands IN PLACE. Every value carries a copy button, and a JSON value (structured, or a JSON string as bot_args carry them) gets a raw/pretty toggle.

Bug Fixes ​

  • runtime: an edge mapping wrapped in literal text lost the literal (#337) (262ff04)

    why`resolveMapping` never interpolated. With exactly one reference it returned that reference's value and dropped everything around it; with two or more it returned the raw template, unresolved. Only a mapping whose whole value was a single `{{…}}` came out right — which is why this survived: that is the shape almost every mapping has.

3.19.0 (2026-08-01) ​

Features ​

  • studio: render a human gate's inbound payload so operators see what they are validating (#336) (79d6110), closes #332, references iterion#332

    whyA paused gate could not show the operator WHAT they were validating. The data was already there end to end — `Engine.persistPause` resolves the node's incoming `with {}` mappings and stores them as `Interaction.Questions`, which the run console and the pipeline board both receive — but the form is driven by the node's OUTPUT schema, so the inbound half reached the browser and was dropped on the floor. Authors worked around it by stringifying the plan/diff/verdict into `instructions:`; anyone…

Bug Fixes ​

  • e2e: pin the fixture's model so it compiles without a host credential (82371ea)

    whyThe hand-off publish test passed locally and failed on main. C018 rejects an agent node that names neither `model:` nor `backend:` UNLESS the runtime can detect a credential — so a developer machine with Claude Code OAuth compiles the fixture and CI, which has none, does not.
  • handoff: publishing the fallback let a crashed review shadow a complete one (4bd82c8)

    whyFound by an adversarial pass over the publish fix, and caused by it.
  • handoff: the node a manifest names as a source has to publish something (f066c0c)

    whyThe live dogfood found the hand-off resolving to nothing, and the first read of the evidence — no artifacts on any cloud run, zero `artifact_written` events — pointed at cloud storage. That was wrong, and the correction is the whole fix.
  • launch: a bot launched off the board could not publish anything (5157144)

    whyThe cloud coordinator launches a card from its BotArgs alone, so two things the webhook tail composes inline never reached it: the forge-publish grant and the repo's launch policy. A board-mode fixer therefore pushed its commits and then had nowhere to post — no verdict, no ledger, no merge-gate status — leaving the repo's required check on the pre-push revision, which blocks the PR on a check that is absent rather than red. Measured side by side with the reviewer on the same PR.
  • launch: the stale provisioning was winning a re-provisioned repo (cdfedc1)

    whyResolving one repo among several provisionings by lowest id is deterministic and arbitrary. On the e2e repo it picks the row from 2026-07-17, on a personal token, over the GitHub App it was deliberately re-provisioned onto — so the verdict would be posted under the operator's own account, which is the identity whose comments the loop guard refuses.
  • webhooks: the fixer's push was the one delivery the gate never saw (db2676c)

    whyThe iterion-bot guard skips a pull request our own loop produced, keyed on the sender. On a merge-gate resync the sender is by construction our own forge bot — a fixer that just pushed onto someone else's PR — so the guard swallowed the delivery the whole gate depends on.

3.18.1 (2026-07-31) ​

3.18.0 (2026-07-31) ​

Features ​

  • branch-improve-loop: let the operator choose where they sit in the loop (d81c0bf)

    whyA reviewer alone already puts the human in the middle: findings land, the human decides which to act on and how. The fixer is the more invasive option, and offering only it would take that arbitration away — you either read findings yourself, or you hand the whole thing over.
  • branch-improve-loop: speak for the head you pushed, and never green it for free (6398c48), references #322

    whyA fixer that pushes moves the head, and the merge-gate status stays on the old one. A required check that is ABSENT is indistinguishable from one still running: the pull request waits for a context that will never arrive, with every other check green and nothing pointing at why. That is the failure #322 was written for, and pushing commits creates it on purpose.
  • detect: report pi, so the preference variable and the studio can see it (a4be02b)

    whypi was reachable only by naming it on a node. Auto-selection filters on what Detect reports, so ITERION_BACKEND_PREFERENCE=pi was inert, and the studio's backend panel and Settings → Backends could not show it at all — for the one backend whose whole proposition is "you already hold a credential for one of ~36 providers".
  • gate: a red merge gate can launch the fixer itself, if the repo asks (057be3a)

    whyonly those. The default stays as it was, and that is the decision rather than caution: a reviewer already leaves the developer in the middle (findings land, they choose what to act on, and a comment hands the work over whenever they want). Turning the hand-over automatic everywhere would take that arbitration from every developer on the repo to save one comment.
  • handoff: a reviewer and a fixer cooperate without the engine naming either (fc3e342)

    whyThe run-to-run hand-off was written as a pair of hardcoded identities: a `botID != "branch-improve-loop"` guard, a producer pinned to "review-pr", and that bot's three node names (`converge`, `merge_reviews`, `diff_precheck`) as engine constants. So the engine knew which bot reviews, which bot fixes, and the internal graph shape of one of them — the coupling CLAUDE.md forbids, and the reason a second reviewer or a second fixer needed an engine PR rather than a bundle.
  • handoff: the fixer answers the review, finding by finding (cbdad61)

    whyThe hand-off ran one way. A reviewer handed over N findings with stable ids, and the reply was a prose summary: the PR author was shown N problems and then told "hardened this PR", with no way to see which were answered, which were dismissed, and on what grounds. A later review, given nothing, re-raised whatever the fixer had silently decided against — the oscillating relay ADR-058 removed from the catalog, reconstituted across two bots.
  • pi: drive the openai-codex provider from the host's Codex login (83cb5e5)

    whypi reaches ~30 providers through an API-key environment variable and `openai-codex` through none of them: that provider is OAuth-only, its tokens live in pi's own auth.json, and they are minted by an interactive `/login`. So a host already holding a working Codex credential — the very file iterion reads for claw's ChatGPT-forfait path — could not hand it to pi at all, and `backend: "pi"` on a ChatGPT plan was unreachable. Verified two ways before building anything: pi's provider table lists an…
  • pi: keep per-run state out of the target repo's checkout (74b71f0)

    whyFive review rounds in a row found holes in the guards protecting <WorkDir>/.iterion/pi — a symlink at a component, at the leaf, at the .gitignore inside it; a relative root that made containment fail open; a pre-seeded .gitignore whose last effective rule re-included our files. Each fix was correct and each left an adjacent hole, because they all patch the same premise: iterion writes a credential and a session transcript into a directory the target repository controls.
  • reviews: the review hand-off carries the patch it already wrote (208852f)

    whySeeding the fixer with the reviewer's findings exists to save it a round. The digest was dropping the fields that do exactly that.
  • runtime: the engine reports which skills it owns, so a backend can trust them (b4abbdf)

    whyThe durable fix for a boundary my last two attempts only pretended to hold. pi is launched with `--no-approve` to refuse the target repo's project-local resources, but `--skill` paths bypass that gate — so handing pi `<workDir>/.claude/skills` loaded whatever the repository itself ships there, as a trusted skill, on every node. For a webhook-launched review or triage bot against an untrusted repo that is attacker-authored prompt text.

Bug Fixes ​

  • delegate: the other two writers under <WorkDir>/.iterion refuse a repo symlink too (8183b6b)

    whyRevi raised this as an open question rather than a finding: if a repo-committed `.iterion` symlink is in the threat model for the credential, it is in scope for the composed system prompt and the session guard, which MkdirAll through the same path. It is — and the primitive is arguably worse there: a repo choosing where a host file lands, with directories created along the way, out of a path iterion picked.
  • gate: the zero-touch lane had no working brake (b066d3a)

    whyA simplify pass and an adversarial pass over the new lane. The two agreed on the same root cause from opposite directions: the launch tail was hand-rolled instead of reusing `launchWebhookTarget`, and everything that copy silently dropped was load-bearing.
  • handoff: five ways the gate went green when it should not have (e862915)

    whyAn adversarial pass over the branch. Two of these would have merged unfixed code through a required check.
  • handoff: the board lane is where a /command bot actually launches (cb60ac0)

    whyMoving the seed into the shared launch tail covered every lane but the one that matters most for a `/command` bot: with a cloud dispatcher active, a board-mode command materialises a card and RETURNS — the card is the launch, and the coordinator launches from BotArgs only. The tail is never reached, so the seed was dropped on exactly the path `/billy` takes in production, with no error and the bot falling back to its DSL default.
  • pi: --skill must not smuggle the target repo's own skills past --no-approve (a951e6f)

    whyA security regression I introduced with the bundle-skill fix, caught by Revi. Widening the gate from `len(task.SkillHints) > 0` to "the mirror directory is non-empty" changed WHAT gets handed over, not just how much: `<workDir>/.claude/skills` is a checkout of the TARGET repository under `worktree: auto`, so a repo committing its own `.claude/skills/x/SKILL.md` had that markdown loaded into every pi node as an explicitly-supplied skill.
  • pi: a bundle's skills must reach pi (1dd232c)

    whyThe --skill flag was gated on `len(task.SkillHints) > 0`, which is the wrong signal. SkillHints carries ONLY the DSL `skills:` field — the skill LIBRARY — while every BUNDLE skill is mirrored into <workDir>/.claude/skills/ without ever touching it. So for a bundle bot the flag was never emitted: pi had zero skill awareness, and an agent whose own prompt ordered "LOAD YOUR SKILLS FIRST" was left hunting for files it had no way to see.
  • pi: a relative workdir disarmed the guard entirely, and the walk covered only the credential (67fd79e)

    whyAdversarial round 3, again aimed at round 2's fix. Third time the defect was in the previous round's patch.
  • pi: absolutise the seed root — a relative --store-dir broke the symlink guard OPEN (56a4116)

    whystore.ResolveStoreDir returns a --store-dir override VERBATIM, and `iterion schedule` renders it into cron lines as given, so the seed root can be relative. Three consequences, and the first fails open:
  • pi: containment decides the location for every branch, not just the operator's (1f5f744)

    whyAdversarial round 5. One finding, and it is a regression against the parent rather than an unclosed gap.
  • pi: containment fails CLOSED, the leaf guard is unconditional, and restore 7 deleted tests (0006090)

    whyAdversarial review (opus, max effort). Two HIGH findings, both verified by execution, and both regressions this series introduced.
  • pi: gitignore is last-match-wins, so a * anywhere proved nothing (1d13476)

    whySame fail-open shape as the symlink series, one layer up in the PARSING. piWriteIgnoreGuard short-circuited on any line trimming to `*`, but git resolves ignore rules last-match-wins: a repo committing `.iterion/pi/.gitignore` with `*\n!auth.json` (or plainly `*\n!*`) made the function return "already guarded" having written nothing, leaving the seeded ChatGPT access + refresh token stageable by a campaign agent's `git add -A` and fast-forwarded onto the operator's branch.
  • pi: guard the directory pi actually writes into, and stop failing a legitimate store symlink (5a39c1e)

    whyThe previous commit put the symlink refusal in writeSystemPromptFile, which was wrong twice over. It could never be the boundary — that function is SKIPPED for a node with an empty system prompt, and for pi `AppendToNative` means an agent node with only a `prompt:` yields exactly that — so `.iterion/pi` as a tracked symlink still redirected the extension bundle and pi's own session transcripts, which carry the node's full conversation. And it was over-broad: it refused a symlinked `.iterion`,…
  • pi: guard the token wherever it lands in the worktree, not just under sandbox (0581b17)

    whyRevi caught an asymmetry I created in the previous commit: I added the ignore-guard verification to the SANDBOXED branch only, while the store branch writes to <StoreDir>/pi with no equivalent check — and this repo's own dogfood instructions prescribe `--store-dir "$PWD/.iterion"`, which puts the store inside the git repo. So the non-sandboxed path carried the exact `git add -A` exposure the sandboxed one had just been hardened against, and the new refusal made the inconsistency look deliberate.
  • pi: ITERION_PI_BIN is a host path, and a source name is not a set variable (f36e7e1)

    whyRevi round 19, both real.
  • pi: keep the ChatGPT token out of git, and stop refusing nodes we do not fund (7903c78)

    whyTwo more from Revi, both medium.
  • pi: make state files reach a copy-based sandbox (040b59e)

    whyA driver whose workspace is a COPY of the host's (kubernetes tar-streams it at pod start) never sees a host-side write made afterwards. pi is handed three files BY PATH — the iterion extension, the composed system prompt, the openai-codex credential — and resolves them inside the pod, so all three were missing there. Caught by the first live cloud run of the backend (019fb968): every attempt died on `Extension path does not exist`, and the other two would have degraded silently, which is worse.
  • pi: make the zero-skill warning reachable, and stop the guard eating a .gitignore (b4c9e20)

    whyThe diagnostic that says "pi got no skills" was written and then never wired: piSkillArgs took a logger, both transports passed nil, and the failure it exists to report — the agent hunting for files its own prompt told it to load — stayed as silent as before. Both argv builders now carry the backend's logger, and the warning fires on the case that matters: the engine named skills and none of them resolved.
  • pi: one --skill per skill, and make detection read what the run reads (b56fbf3)

    whyThree from Revi, all consequences of the previous two commits.
  • pi: StateDir reports WHO can plant, and the pre-flight call is finally tested (a2ada61)

    whyAdversarial round 4. Two findings, and both are about the previous round's claims rather than its logic — the helper work held.
  • pi: the codex bridge refreshed on every node and broke under sandbox (8cf9bab)

    whySix defects from an adversarial review of the bridge. The two that mattered were both mine reasoning backwards from an untested assumption.
  • pi: the codex bridge worked on run and not on resume (c5b0e76)

    whyTwo defects a resumed run exposed, neither reachable from `iterion run`.
  • pi: the containment fix was killing the credential it protects (2e919f1)

    whyAdversarial round 2, aimed at the previous round's FIXES because that is where the last five rounds found the defect. It held.
  • pi: the ignore guard must not follow a symlink the checkout shipped (a37a99d)

    whyThe path guards walk TO the seed root; none of them looks INSIDE it. And MkdirAll is a no-op on a `.iterion/pi` the checkout pre-populated, so a repo can ship `.iterion/pi/.gitignore` as a tracked symlink. Two things follow, and the second is the one that matters:
  • pi: the last two Revi findings, one of them my own argument turned on me (4fcef55)

    why**detect's Codex probe was more optimistic than the bridge that consumes the credential.** codexChatGPTAvailable hand-rolled a parse accepting `auth_mode: "chatgpt"` alone, while piCodexSeed gates on CodexCredentialsView.IsChatGPTMode — which also requires an access token and an account id. So a partially-written or logged-out Codex state made detect report pi available, the preference variable resolved there, and the bridge then stepped aside and left the node to die with "No API key found for…
  • pi: the sibling ignore guard had the same symlink hole, one level up (c83e885)

    whyThird instance of one shape, so this shares the CHECK rather than adding a third copy of it. piHideWorkspaceSessionDir used a FOLLOWING os.Stat on <WorkDir>/.iterion/.gitignore, which a repo can ship as a tracked symlink. Both outcomes were bad and the second was silent: a DANGLING link made os.WriteFile create an attacker-chosen host file, and a link to any existing path made the stat succeed, so the function returned as if the workspace were guarded — leaving pi's session transcripts and the…
  • pi: treat Revi's five findings on the pi follow-ups (d269907)

    why**high — the stale-seed sweep deleted a live peer's credential.** I added the sweep to fix an adversarial-review finding (a SIGKILL strands tokens) and asserted an invariant that is false: "anything already here when a new node starts is by definition abandoned". The root is SHARED — by every node of a run under sandbox, and off it by every run in the store — while iterion permits parallel branches and the studio runs several pipelines at once. A second openai-codex node therefore swept the…
  • pi: write our own ignore guard instead of trusting one we did not author (1ee62d1)

    whyRevi's high finding, and it is the root cause of the two patches before it: the guard verified `<workDir>/.iterion/.gitignore` no matter where the seed root actually landed. That file is best-effort, is deliberately never overwritten when the repo already tracks one, and says nothing about a root `--store-dir` put elsewhere under the worktree. So the check could pass while the credential sat somewhere it did not cover.
  • runtime: back the sandbox scratch with a shared, persistent host dir (#330) (9c8e0e2)

    whyA sub-bot child runs in its OWN container. ${PROJECT_SCRATCH_DIR} resolved to a container-local /tmp/iterion-scratch, so the file a child wrote there was invisible to the parent that later read the same path. The child reported success, the parent read an empty directory, and the run only failed much later — as "not enough results", far from the cause.
  • runtime: library skills ride the ownership channel, and the seed root refuses a symlink (8121570)

    whyA skill hint is not provenance. One is recorded for every skill the workflow's `skills:` field references — INCLUDING one the target repo pre-empted, because the hint describes what the agent will see, not who wrote it. piSkillArgs derived `<workspace>/.claude/skills/<name>` from that name, so an untrusted checkout that shipped a same-named skill got its own file handed to pi as a trusted `--skill`: precisely the routing-around-`--no-approve` the gate exists to close.
  • runtime: own the FILE, not the directory a repo can pre-populate (cbee1e1)

    whyA flat bundle source writes <stem>/SKILL.md, and MkdirAll succeeds happily on a <stem>/ the checkout already ships. Reporting that DIRECTORY as owned therefore vouched for whatever the target repo planted beside our file: a repo committing .claude/skills/whats-next/evil.md (no SKILL.md, so nothing shadows) got the whole directory back on the owned list. Naming the one file we wrote cannot carry a sibling. Library skills had the identical shape and get the same treatment; the directory-form…
  • runtime: plugin skills reach pi, and directory skills survive a resume (65227b0)

    whyTwo regressions the engine-owned skill list introduced, both silent.
  • secrets: state only the billing arrangement that was measured (2fdcb80)

    whyThe shared subscription-OAuth warning asserted that "third-party apps bill against your EXTRA USAGE balance, not your plan limits" for ANY provider, interpolating only the name. That sentence describes Anthropic's arrangement, measured live during the pi work. Applied to OpenAI it states a billing model nobody here verified — and an operator reading a confident sentence acts on it.

3.17.7 (2026-07-31) ​

Bug Fixes ​

  • cli: anchor the run store on the working directory (#328) (61af254)

    why`iterion run project/bots/x/main.bot` resolved its store from the .bot's own directory, so a bot living inside the project it drives was keyed on that subdirectory: the run landed in ~/.iterion/projects/<project-bots-x-key>/ while resume, inspect, issue, dispatch and the studio all resolve <project>/.iterion. Launching succeeded and every follow-up reported "run not found" — the run was invisible to the board and could not be resumed.

3.17.6 (2026-07-31) ​

3.17.5 (2026-07-31) ​

3.17.4 (2026-07-30) ​

Bug Fixes ​

  • golden-master: five defects the a11y lane only revealed on a real runner (6fbf1ba)

    whyThe lane worked on the machine that wrote it and failed six times in a row on a CI runner. Every fix below is now verified by a green pipeline — full conjunction, 19 of 19 mutants, on a real GitLab runner with the lane active.

3.17.3 (2026-07-30) ​

Bug Fixes ​

  • golden-master: the browser stalls in a container instead of failing (f29dd4e)

    why/dev/shm defaults to 64 MB in a container, and the renderer blocks there rather than erroring. The symptom is not a crash but a page that never finishes loading: the load event does not come, and the load ceiling fires while blaming the host's speed. Measured on a real runner — 240 seconds on the first page, instant on the same page locally.
  • server+studio: show the paused node's instructions on review cards (#326) (a46527d)

    whyA board review card rendered an answer box with no question above it. The operator saw "Awaiting input", a run link and an empty "Message" field — nothing else.

3.17.2 (2026-07-30) ​

Bug Fixes ​

  • server: a review that dies must still leave a verdict on the PR (#322) (d337007), references #314 #318 #314

    whyA required check that is ABSENT is indistinguishable from one still running. The PR waits for a context that will never arrive, and nothing — not the run, not the PR, not the check — says why. Only someone who knows to re-trigger the bot can unstick it.

3.17.1 (2026-07-30) ​

Bug Fixes ​

  • review-pr: a mono review must not report cross-family confirmation (#320) (34bd008), references socialgouv/buildkit-operator#6

    whyEvery comment ended with "0 finding(s) cross-confirmed by both model families", including under the default mono topology where a single family reviews. That reads as two families having looked and agreed on nothing — a statement about a comparison that never took place. Spotted on socialgouv/buildkit-operator#6.

3.17.0 (2026-07-30) ​

Features ​

  • golden-master: an a11y lane that audits the rendered page, not the markup (c354a8a)

    whyA net that watches HTTP responses sees the markup change; it cannot say what degraded. Removing an accessible name moves an HTML reference and nothing in the report distinguishes it from a reworded label.
  • golden-master: put the target's own test suite through the same trials (7ab1596)

    whyThe net and the existing test suite both watch the same repository and both claim to protect against regression. Comparing them by assertion is worthless; `suite-vs-net.py` applies every mutant in turn and runs the target's own suite, so one set of trials produces both figures.

3.16.1 (2026-07-30) ​

Bug Fixes ​

  • dep-update-guard: don't let the merge check claim an alignment that never happened (#317) (4476135), references socialgouv/buildkit-operator#15

    whyObserved on socialgouv/buildkit-operator#15, the first PR to travel the whole loop unattended: the required check displayed "supply-chain audit clean; alignment committed, build verified" while the commit step had reported committed=false — the branch needed no alignment at all.
  • review-pr: the stale-anchor guard compared a template string to a sha (#318) (7b87b5f)

    whyRevi has published nothing since the runner picked up #290's reviewed-SHA guard. Every review ends:

3.16.0 (2026-07-29) ​

Features ​

  • golden-master: an asset lane that inventories the build, not the worktree (0abe16a)

    whyA net that watches HTTP responses and rendered documents does not watch the files a page loads. On the repository this bot was exercised against, the whole client layer — every stylesheet, every vendor script, the view framework itself — was absent from the environment and answered 404, and not one reference moved. A total absence of the client layer was indistinguishable from its presence.

Bug Fixes ​

  • runtime: a subbot child carries its parent from its first write (3a61d2d)

    whyThe engine created every run with CreateRun and stamped ParentRunID in a follow-up SaveRun. Between the two writes the row existed, was `running`, and had no parent — and a row with no parent is indistinguishable from a top-level run, which is exactly what the orphan reconciler judges. Every subbot child goes through this path: the runtime spawns them, and only Service.Launch pre-creates its own rows.

3.15.0 (2026-07-29) ​

Features ​

  • pi: pi as a first-class execution backend (ADR-085) (#308) (0710335), references #168

    whypi (pi.dev) is a multi-provider agent harness reaching ~36 first-class providers behind one agent loop. It is the backend to reach for when a node needs a model claude_code and claw cannot run.

3.14.0 (2026-07-29) ​

Features ​

  • dsl+studio: file schema fields — operator uploads at a human gate (#315) (a6edd19)

    whyAdds a `file` schema field type so a human node can ask the operator for bytes, not just text: the studio renders a file picker at the gate, the answer is uploaded to the run, and the runtime promotes it to a run attachment before the workflow resumes. `iterion resume --answer key=@./path` accepts the same fields from the CLI.

Bug Fixes ​

  • dep-update-guard: merge the PR the forge already reports as green (#314) (a140746), references socialgouv/buildkit-operator#5

    whyenablePullRequestAutoMerge only accepts a PR with something left to wait for; GitHub answers UNPROCESSABLE "Pull request is in clean status" otherwise. The audit takes longer than CI, so that is the ordinary case: a live run on socialgouv/buildkit-operator#5 posted its gate green and then armed nothing, leaving the PR open with every check passing.
  • golden-master: a gate on an uncommitted tree judges a tree that never existed (0f99ce5)

    whyMutant reverts are `git checkout -- <file>`, restoring HEAD. The gate captures references from the working tree it starts with, the first file mutant snaps those files back to HEAD, and every capture after that describes something else. Uncommitted work is destroyed on the way, silently, and the verdict belongs to no tree that ever existed.
  • pluginsource: publish a plugin checkout atomically, one clone per key (#313) (9065d52)

    why`git init` creates .git before the fetch and checkout land, and Fetch treated the presence of .git as "this tree is complete". On a cold pod taking several launches at once, the losers of that race were handed a directory holding nothing but .git — and the plugin loader then reported it as "has no plugin.yaml and no skills/", a 502 that names the wrong cause and blocks every launch for the tenant.

3.13.0 (2026-07-29) ​

Features ​

  • projected improvements from the AIDD framework (skill lint, fit/rot lens, memory supersede, dependency gating, reviewed-SHA guard, hold-labels) (#290) (eb0ecbe), references #13

    whyScanFrontmatter (the shared SKILL.md parser used by both the skill library and runview's bundle-skill catalog) only read the value on the same line as `description:`. For the common `description: >` / `description: |` block-scalar form it therefore returned just ">" or "|" — so the router (Nexie) and discovery saw a one-character description for every skill authored that way.

Bug Fixes ​

  • bots: mono must not hand the merger a raw template (74c46e2)

    whyIn mono only one reviewer runs, and an {{outputs.<absent node>.<field>}} reference renders as a LITERAL placeholder rather than as nothing — so the merging agent was shown what looks like a broken template instead of "that family did not run". Observed live: a mono review's own reviewer mistook it for a reviewer failure and said so in its report.
  • golden-master: the emitted runner names a missing interpreter instead of blaming its own tests (446f9bf)

    whyA missing python3 surfaced as "the canonicaliser tests FAIL" — a message that accuses the net when the environment is what is absent. Seen for real on a CI image nobody had checked carried an interpreter. The whole net is Python; saying so plainly costs one line and saves an hour of looking in the wrong place.
  • golden-master: the replayability check no longer reports success when it cannot tell (fd7bd99)

    why`git check-ignore` returns 0 for ignored, 1 for not, and 128 when there is no repository or no git. The check only looked at 0, so on any workspace that is not a checkout it discriminated NOTHING and left `runner_replayable: true` — the good outcome, reported for the one reason it could not see. Some CI runners hand the job a COPY of the tracked files rather than a checkout, which is exactly where this happens.

3.12.1 (2026-07-28) ​

3.12.0 (2026-07-28) ​

Features ​

  • cli: add --commit flag to version command (#52) (a8af8ee)

    why`iterion version --commit` prints only the bare git commit SHA on a single line (via cli.RawCommit()), so scripts can capture the SHA directly without parsing the full human-readable version string. The default `iterion version` output is unchanged. Output now goes through cmd.OutOrStdout() so the command is testable against a captured buffer.

3.11.1 (2026-07-28) ​

3.11.0 (2026-07-28) ​

Features ​

  • studio+runtime: pipeline-board & human-review overhaul + subbot editor UX (WIP snapshot) (#300) (991ee1d), references #244

    whyAnswering a paused `human` node from the studio (pipeline-board card or run console) could silently do nothing and lose the reviewer's notes. Verified end-to-end in a real browser (Playwright against the built SPA).

Bug Fixes ​

  • golden-master: promote_audit commits what it promotes (ae26833)

    whyMoving files into the worktree is not publishing them. The worktree is destroyed when the run ends, so a promotion that only moves has produced nothing that outlives the run -- while reporting promoted: 8.
  • reviewtopology: make mono the default review topology (03309fd)

    whyRevi ran BOTH family reviewers on every review, unconditionally: it never declared the ADR-052 topology vars (its only review_mode-looking var is pr_review_mode, an unrelated inline/summary publish setting), so InjectIfDeclared no-op'd on it and there was no frugal path at all. With the merge gate wired, review_on_sync re-reviews on every push — so each push cost two full reviewer passes on an instance that should be running mono.

3.10.4 (2026-07-28) ​

Bug Fixes ​

  • dep-update-guard: a redirect must not degrade the publish POST into an unexplainable 401 (#312) (9d5efc6)

    why`forge_publish_url` is the FULL endpoint URL, not a base — Revi uses it verbatim. Vetty appended the path to it, so it POSTed to `…/api/v1/forge/publish-review/api/v1/forge/publish-review`. That path is not the auth-exempt route, so the global auth middleware answered 401 "authentication required" and no commit status was ever posted.

3.10.3 (2026-07-28) ​

3.10.2 (2026-07-28) ​

3.10.1 (2026-07-28) ​

Bug Fixes ​

  • make paused resumes reliable and compact run details (#301) (876e847)

3.10.0 (2026-07-28) ​

Features ​

  • webhooks: /revi approve — maintainer override for the merge gate (#292) (737d2fe)

    whyThe human-arbitration escape hatch for the Revi merge gate: a trusted maintainer comments `/revi approve [reason]` on a PR to force-green the `revi/review` commit status on the current head, for a finding they dispute — without launching a re-review and without needing admin merge-queue bypass.

Bug Fixes ​

  • dep-update-guard: publish to the endpoint the server injected, not under it (#309) (1867b14)

    why`forge_publish_url` is the FULL endpoint URL, not a base — Revi uses it verbatim. Vetty appended the path to it, so it POSTed to `…/api/v1/forge/publish-review/api/v1/forge/publish-review`. That path is not the auth-exempt route, so the global auth middleware answered 401 "authentication required" and no commit status was ever posted.

3.9.1 (2026-07-28) ​

Bug Fixes ​

  • golden-master: one rule for the sealed path, asked rather than copied (b8ab790)

    whyThe morning's fix made the campaign and the gate DERIVE the sealed path from the same rule instead of one dictating it to the other. The very next commit added a third party -- the node promoting a spent set to published evidence -- carrying its own hand-written copy of that derivation. It resolved into a different repository's scratch entirely, promoted nothing, and reported success.
  • runview+cli: a paused run advertised resumable before it was, and the group guard demanded a contract three groups already had (#307) (2cf72f3), closes #5

    whyWhen a run parks on a human gate the engine writes paused_waiting_human to the STORE, returns ErrRunPaused, and only then does the goroutine carrying it call Deregister on its way out. Between those, the public signal says "resumable" while the handle is still held — and the studio and the pipeline-board sidebar offer Resume on exactly that signal. A resume landing in the window failed with `run "..." is already registered`, which reads as a bug to an operator and is one to any automated chain…

3.9.0 (2026-07-28) ​

Features ​

  • bots: modernize (Morphy) — gate-to-gate lots against an oracle it cannot rewrite (f7b72e9)

    whyThe unit of work is the LOT, not the package. A dependency-upgrade pipeline whose failure path is revert-this-package-and-continue cannot express a runtime move that touches nine hundred files at once, so this is a separate bot rather than a widening of that one.
  • golden-master: a spent held-out set becomes published evidence (8ce6b92)

    whyThe sealed set protected the hardening loop and then vanished with the run, which left the committed net unable to support its own headline claim. A third party could read '7/7 held-out detected' and had exactly as much reason to believe it as they have to believe any delivery's self-reported figures — which is to say none, and that is precisely the criticism this bot exists to make.
  • model: move the fleet to the Claude 5 family, and read the generation instead of listing it (1978c3a)

    whyReasoning capability was decided by a list of known model ids. A list is silent when it is wrong: a model absent from it is classified as non-reasoning, extended thinking is never requested, and the run pays full price for a degraded answer without a single warning. claude-opus-5 matched none of the five patterns.
  • models: audit committed prices against the ones already being fetched (02d84c0)

    whyiterion downloads model pricing from the spec aggregator, caches it for 24h, and never reads it: InputCostPerM and OutputCostPerM were parsed and dropped, with no consumer anywhere in the tree. Meanwhile the cost estimator asks a different live source and falls back to a hand-maintained table. Two sources of truth for the same number, never compared.
  • reviews+deps: one merge gate per repo, and Vetty guards Renovate PRs end-to-end (#306) (eac354f), references #300

    whyA repo webhook could only ever launch a single bot per delivery: SelectBot() returns "" as soon as two bots are enabled, so the lane fell back to the hardcoded "review-pr". Co-enabling a dependency guard and a reviewer therefore lost the guard entirely, and — because the shared AuthorAllowlist is the union of every bot's, nil as soon as one bot is open — the guard's author filter was discarded too.

Bug Fixes ​

  • golden-master: a spent held-out set is not a broken seal (b14faca)

    whyPublishing the set created a third way to have nothing to score, and the harness knew only two. A replay after promotion accused the operator of having cleared the seal, when the set had in fact been scored once and published exactly as designed.
  • golden-master: enforce the corpus width floor, on distinct references (0b428ec)

    whymin_corpus existed only in the campaign prompt. The harness never read it and the gate never checked it: a corpus of three entries passed as long as the three were seen. Same shape as the seal that was guaranteed by a sentence in a skill — an obligation stated to the agent with nothing behind it.
  • golden-master: fail the gate when the emitted net cannot be replayed (5b23373)

    whyThe first net this bot emitted was not runnable from a clean checkout. The campaign gitignored harness.py — defensible-looking, since it is a copy of a bundle file — so the committed oracle was references plus a runner shelling out to a script that does not travel. Checked out fresh, verify-oracle.sh exits 2 with 'No such file'.
  • golden-master: resolve the base URL instead of baking it (b3b6804)

    whyThe emitted net recorded base_url as a literal. Ports are derived from the repository path — the fix for two checkouts fighting over one port, where the bad case is not a refused start but the second copy capturing the first one's application and recording a net that describes a different tree. A literal therefore pins the net to the machine AND the path that recorded it.
  • golden-master: the emitted runner exits red when the gate is red (f77b3f0)

    whyverify-oracle.sh printed the report and exited 0 no matter what the verdict was. The graph never noticed, because it reads the JSON and computes the conjunction itself — but this script is the entry point for CI and for humans, and both read exit codes. A runner that reports a red gate and exits 0 IS a blind judge, one level above the one this bot was built to catch.
  • golden-master: the negative control covers the whole corpus (e773e6c)

    whyIt sampled the first six entries, which left every later entry never once confronted with its own reference. A reference could be stale, or frozen against a world that had since moved, and nothing would say so unless a mutant happened to target it. That is a hole in the one guard whose job is to prove the comparators are not noisy.
  • model: resolve bare model names deterministically, by consensus (c2d00a3)

    whyThe bare-name index was built by assigning into a map while ranging over one. Go randomises map iteration, so a model published by several providers resolved to a DIFFERENT provider's numbers on every process start. Five consecutive runs of the same command produced five different prices for glm-5.2, one of them zero.

3.8.1 (2026-07-28) ​

Bug Fixes ​

  • server: a malformed comment no longer costs the review and the merge gate (#305) (189caa6), references #304 #304

    whyTwo couplings, both hit live on PR #304.

3.8.0 (2026-07-28) ​

Features ​

  • bots: golden-master (Goldy) — behavioural non-regression net that proves it can see (e404343)

    whyRecords what an existing app observably does, then PROVES the references are not blind with a deterministic mutation counter-test: injected divergences must all be seen, a no-op mutation must leave the oracle silent.
  • golden-master: binary lane — PDF/spreadsheet capture and the blind-judge diagnostic (238f8ff)

    whyAjoute poppler (pdftotext, pdftoppm) au bundle et un skill binary-lane cable sur la campagne. Les deux archetypes binaires (content_empty, value_change) etaient deja exiges par le harnais ; il manquait l'outillage et le guide.
  • golden-master: selfcheck mode, mode-aware reports, and a mechanical seal (53a9c18)

    whyTrois faiblesses residuelles du run 005.
  • model: show the rejected payload when a tool call fails (addfc99)

    whyUne erreur d'outil qui nomme une propriete manquante n'est pas exploitable sans la charge qui l'a omise. Aujourd'hui la ligne de log dit seulement

Bug Fixes ​

  • golden-master: derive the seal path so campaign and gate agree on it (065fe48)

    whyThe previous fix scoped the seal to the run by forcing GM_SEALED_DIR at the gate. The campaign seals too — the golden-master skill has it run selfcheck — in another process, without that environment. It therefore fell back to the shared gm-holdout path and MOVED the held-out set there, after which the gate looked in the run-scoped path, found nothing, and would have bailed on a seal it had itself broken.
  • golden-master: emit_runner crashed on shell brace expansion (997a55e)

    whyLe DSL expanse les expressions d'environnement, y compris la forme ${VAR:-defaut}, AVANT d'executer le script. La clause de defaut court jusqu'a la premiere accolade fermante -- qui etait celle de la substitution de template suivante. Une accolade mangee, script Python impossible a parser, et le run echouait sur son dernier noeud APRES que la porte ait converge.
  • golden-master: four defects found by the first real run (2fd1a08)

    why1. The harness forced `sh` on mutant scripts, ignoring the shebang. On most systems /bin/sh is dash, which has no `source`: a helper file never loaded, every function it defined was 'not found', and the mutant died with a bare exit 127 giving no hint the interpreter had been swapped. Scripts now run honouring their shebang, with an `sh` fallback when not executable.
  • golden-master: per-run seal, and a campaign schema of one field (efb46be)

    whyDeux defauts trouves en surveillant le run 006, tous deux de conception.
  • golden-master: the emitted runner advertised a flag it did not implement (6ca64c2)

    whyL'en-tete annoncait un --self-check absent du script, et laissait croire que le mode par defaut ne rejouait pas le contre-test -- alors qu'il fait exactement cela. Un runner qui ment sur ce qu'il fait est un runner que personne ne relit.
  • runtime: stop warning that bundle skills are absent from the skill library (086d1cf)

    whyLe miroir de bibliotheque tourne APRES ceux du bundle et des plugins, qui le supplantent (ADR-059). Quand un bundle fournit ses propres skills, la reference est deja satisfaite -- mais on avertissait quand meme, une ligne par skill a chaque demarrage. C'est vrai, inutile, et ca se lit comme un run casse : le bundle golden-master en emettait six a chaque lancement.
  • wait out a provider quota window instead of burning 8 pods against it (#304) (41d3330)

    whyA terminal node failure was rebuilt as a plain string, so both the classified error code and the original error were destroyed at the point the engine gave up on the node. Two consequences, neither visible from either side alone:

3.7.5 (2026-07-27) ​

Bug Fixes ​

  • review-pr: recover findings when the merge step degrades + honest gate note (#302) (cd2ffeb), references #300

    whySecond occurrence, live on PR #300 (run 019fa02b): converge returned `findings` as the prose "See structured findings array." while total_findings said 8. The publish step parsed nothing, so the review published "0 findings kept" with 0 inline comments — 8 real findings never reached the author — and the fail-closed gate reported "1 blocking finding(s) >=high", sending the operator hunting for a finding that was never published. The 0.5.4 prompt hardening was necessary but not sufficient: an…

3.7.4 (2026-07-26) ​

3.7.3 (2026-07-25) ​

3.7.2 (2026-07-25) ​

Bug Fixes ​

  • plugin: repair two codeindex rewriter defects found on review (#297) (472f7d0)

    whyBoth would have shipped broken, and neither is caught by manifest validation.

3.7.1 (2026-07-25) ​

Bug Fixes ​

  • review-pr: harden emit findings contract (JSON array, not prose) (#299) (6496bb4), references #292

    whyFound live on the merge gate's own PR #292 (run 019f98ed): revi/review posted `failure | 1 blocking finding` while the review body said "0 findings kept". Root cause: the converge/emit LLM returned `findings` as a PROSE STRING ("4 findings kept (0 critical, 1 high, 2 medium, 1 low)…") instead of the JSON array of objects the schema intends. `findings: json` accepts a string (the DSL has no object-array type), so nothing rejected it. Downstream, publish_review's `JSON.parse(FINDINGS)` fails →…

3.7.0 (2026-07-25) ​

Features ​

  • forge: iterion remote forge refresh — re-sync a connection's grants now (#298) (75c02e9)

    whyOperability tool motivated by the merge-gate rollout: after changing a GitHub App's permissions (e.g. granting Commit statuses: write), an operator had to wait for the periodic refresh worker or restart the whole server for iterion to pick up the new grant. This adds a targeted, explicit refresh.

3.6.1 (2026-07-25) ​

Bug Fixes ​

  • forge: request statuses:write in the runtime App token (unblocks merge gate) (#295) (0a3f9ed)

    whyLive prod e2e of the merge gate proved the full chain works end-to-end (Revi reviews → bot sends the deterministic gate verdict → server resolves the head SHA → SetCommitStatus), but the status did not land: the GitHub App lacks "Commit statuses: write", so SetCommitStatus returns 403 "insufficient scope". The code handles it exactly as designed — non-fatal, reported in gate_error, logged (`forge gate: … not posted: … insufficient scope`) — so the gate advises instead of blocking until the…

3.6.0 (2026-07-25) ​

Features ​

  • plugin: ship codeindex as a builtin plugin (#296) (193a33a)

    whycodeindex (https://github.com/maxgfr/codeindex) is a deterministic, zero-dependency repo-indexing engine on npm. This wires it in as a disabled-by- default builtin, alongside the other knowledge-graph explorers.

3.5.2 (2026-07-25) ​

Bug Fixes ​

  • review-pr: second publish_review shell bug — bare double-quote truncates (#294) (c06e0e1), references #293 #293 #292

    whyThe e2e re-run (after #293) still failed: publish_review produced EMPTY output (exit 0, no forge review, no revi/review status), then publish_health crashed on the empty inputs. Root cause: a python COMMENT in the publish_review body contained bare double-quotes ("high"/"blocker"/"major"). The body is wrapped by the shell in `python3 -c "…"`, so a bare double-quote ends the string and silently truncates the script. Same trap as backticks; distinct from the #293 questions-array bug (both were…

3.5.1 (2026-07-25) ​

Bug Fixes ​

  • review-pr: publish_review shell exit-127 on multi-question reviews (#293) (34df1c1), references #292 #292

    whyThe v0.5.0 questions channel passed a JSON array of strings through the publish_review tool node as QUESTIONS={{input.questions}}. A `json`-typed field holding an all-string array decodes to []string, which the tool-command substitution SPACE-JOINS instead of JSON-encoding (known engine bug, executor_tool.go:1047) — so the 2nd+ question landed in shell command position and bash exit-127'd, crashing the entire review (no forge review, no revi/review gate status). findings escaped this because…

3.5.0 (2026-07-24) ​

Features ​

  • docs-refresh: /doki is a direct PR-scoped command (amend-on-PR activation) (5d2e481)

    whyThe /doki comment command switches from board/any to direct/pr, mirroring /revi: a developer commenting /doki on a PR launches docs-refresh directly on the PR head (no tracking card), self-switching to incremental amend via the generic pr_url/base_ref/source_branch the webhook stamps. The manifest half of activating the amend-on-PR trigger — the /command→bot route derives from this invocation, no engine code. v3.5.4.

3.4.0 (2026-07-24) ​

Features ​

  • review-pr: falsifiable questions channel + deterministic Revi merge gate (#291) (ad8e2d6), references #285 #290

    whyTwo improvements to Revi, motivated by a "0 findings / no comment" review (PR #285) that gave no signal of depth, and by the wish to let Revi arbitrate a merge without an LLM being the yes/no gate.

3.3.0 (2026-07-24) ​

Features ​

  • docs-refresh: agnostic amend-on-PR (v3.5.2) + engine stays bot-agnostic (d184a1f)

    whyDoki self-aligns a PR's docs and amends it (pushes onto the PR head + comments) when launched ON a pull request — keyed entirely on the GENERIC PR-context the engine already provides for ANY bot, not on bot-specific engine code:

Bug Fixes ​

  • docs-refresh: scope_check bases on run-start HEAD, not oldest reflog (40ae433)

    whyIn amend-on-PR mode the cloud runner clones the base branch (HEAD=main) then checks out the PR head, so the OLDEST reflog entry is main. scope_check diffed against it and folded the PR author's OWN code into the changed set, raising a phantom writeable-set violation that pinned scope_ok=false — so `converged` never fired and every amend run burned all its passes (live run 019f9429). Base the diff instead on the run-start HEAD: the newest reflog entry that is not one of this run's own `Bot:…
  • studio: "Open child bot" 404 → resolve child path against the right parent (#285) (c7f6aab)

    whyClicking "Open child bot" from the editor inspector could 404 (and, in a follow-up, silently open nothing): the child .bot path is resolved from the parent file with `resolveSubbotSource(parentFilePath, source)`, but `parentFilePath` (document store `currentFilePath`) can be null during the short route-hydration window — arriving from Pipelines activates the editor tab one render before EditorTabHost copies the file into the document store. With a null parent, a parent-relative `source` was…

3.2.0 (2026-07-24) ​

Features ​

  • docs-refresh: v3.4 — drop noop cache, author_docs, mark_issue (native paradigm) (1d2c482)

    whyThree more non-essential nodes removed, converging Doki on the native shape (one adaptive agent + a truth gate + the PR tail):
  • docs-refresh: v3.5 — incremental (git-detected base) + amend-PR modes (1a5eddf)

    whyTwo alignment strategies so Doki keeps docs fresh cheaply, on the native paradigm (one agent + truth gate + PR):

Bug Fixes ​

  • docs-refresh: anchor incremental base detection to the trailer line (bb2291e), references #288

    whygit log --grep 'Bot: docs-refresh' matched any commit MENTIONING the trailer in prose — including this bot's own v3.5 feature commit (caught on the first live check against origin/main, where it picked 1a5eddf29 over the real last alignment #288). Anchor to a line start (-E --grep '^Bot: docs-refresh') so only actual trailer lines count. Test now commits a prose-mention after the alignment commit and asserts the base stays the real alignment commit.
  • studio,desktop: point Documentation links to the Pages site, not the repo docs folder (474eecd)

    whyThe cloud landing, the About and Backends settings tabs, the desktop menu, and the desktop app-info binding all linked to github.com/.../tree/main/docs (raw repo folder) instead of the published docs site. Point them at https://socialgouv.github.io/iterion/ (and /backends for the backends deep link).

3.1.3 (2026-07-24) ​

3.1.2 (2026-07-23) ​

3.1.1 (2026-07-23) ​

Bug Fixes ​

  • feed-watch: make the SSRF guard proxy-aware for sandboxed runs (#287) (bc6c16d)

    whyA cloud/sandboxed run reaches the internet through iterion's egress proxy, injected as HTTPS_PROXY and advertised at the runner's own (necessarily private) pod IP — the trusted egress boundary and the secret-redaction point (started even in `network: open` whenever a SecretRewriter is present). urllib then dials the PROXY, not the feed host, so Vigie's socket-level getaddrinfo guard rejected our own proxy as "SSRF-unsafe address <pod-ip>" and every feed failed (run 019f8feb: all 69 fetches…

3.1.0 (2026-07-23) ​

Features ​

  • studio: surface a run's PR/deploy links as headline result-links (#286) (421f901)

    whyA run that opens a PR (finalize_mr) or deploys an app (Appy) buried the resulting URL in a node's structured output — nowhere prominent. Surface it like a CI run's "View deployment" button, at the top of the run summary.

3.0.0 (2026-07-23) ​

⚠ BREAKING CHANGES ​

  • docs-refresh: plan-then-execute comprehensive, asymptote on honest declaration (3.1.0)

Features ​

  • docs-refresh: plan-then-execute comprehensive, asymptote on honest declaration (3.1.0) (3b8dd24)

    why3.0 was fast/cheap but under-delivered — it handled the ~10 advisory hints, surveyed shallowly, and honestly-but-myopically declared docs_aligned after one ~15-min pass, producing tiny PRs on a 250-doc corpus with weeks of unread semantic drift. The fix is framing, not a coverage gate: an audited.json exhaustiveness checklist was drafted and REJECTED as exactly the excess determinism 3.0 removed (it makes the agent do bookkeeping instead of the job).
  • docs-refresh: self-orchestrated coverage — campaign fans out its own subagents (3.2.0) (cd6c92b)

    whyA live 3-way benchmark (this repo, 2026-07-23) settled why 3.0/3.1 shipped tiny PRs: a SINGLE campaign agent — exactly like a free native agent handed the same one-liner — self-scopes to the headline docs and misses the long tail. By rising coverage: Doki (~3 commits/pass, docs/ only) < native one-liner (6 fixes, missed cloud + bot READMEs) < native with a demanding prompt (reached the WHOLE corpus). The only run that got there DECOMPOSED into per-cluster sub-auditors ON ITS OWN.
  • server,studio: full cloud bot editing — team-authored bot store (8850fd6)

    whyAdds a writable, team-scoped bot store so the studio editor works in cloud, not only on a local filesystem. The cloud catalog stays baked read-only; tenant bots are editable and forkable.
  • studio: multi-file bundle editor for cloud bots (4444448)

    whyAdds a "Bundle files" drawer (Toolbar, shown only for a botsource:// tenant bot) listing the bundle's files. main.bot opens in the DSL Canvas; skills/*.md, manifest.yaml and any other file edit inline in a Monaco buffer and save per-file to the bot-source store. New files can be added and non-main files removed. Reuses the FileEditDialog Monaco pattern + inferMonacoLanguage; the tab-kind system is untouched.
  • webhooks: PR-open auto-reviews only (Revi); Billy on /billy with Revi's review (#283) (463646d)

    whyDecouple the mutating branch-improve loop (Billy) from PR-open auto-launch: a PR/MR open now ONLY ever auto-reviews (Revi / review-pr). Removes the selectForgePRBot ticket-PR→Billy routing; the merge-queue auto-heal path (NeedsAutoHeal) is unchanged.

Bug Fixes ​

  • docs-refresh: budgets sized for self-orchestration (3.2.1) (7213fc6)

    whyFirst 3.2.0 live run (019f8e08) aligned 40 docs across the whole corpus in pass 1 (~70 min / ~$16) — the win — but the old 2h/$60 caps guillotined it mid-pass-2 as a hard failed_resumable BEFORE finalize, and all 40 in-pod commits were lost (the exporter only runs on a clean finalize; engine gap tracked separately). Comprehensive self-orchestrated passes are long: max_duration 2h→6h, max_cost_usd 60→120, max_passes 8→4 so the asymptote reaches GRACEFUL exhaustion (which finalizes + exports +…
  • native: unique-title prefix must respect the caller's rune budget (89787a9), references #N #N #N

    whyThe atomic CreateUniqueTitle prepended "#N - " to the desired title without re-truncating, so a pipeline-board title already compacted to 80 runes became 85 once made unique — deterministically failing TestPipelineBoardTaskCreateEnsuresUniqueTitle (the server's list-then-check fallback already re-compacted; the atomic path didn't). CreateUniqueTitle now takes an optional `normalize func(string) string` applied to every candidate inside the lock; the pipeline board passes compactPipelineTitle so…
  • runner: budget-exceeded acks (no auto-resume) — stop the git-meta clobber (266e6ad)

    whyErrBudgetExceeded fell through to the generic Nak, so a budget-exceeded run (a resumable checkpoint) was auto-redelivered and resumed. That was doubly destructive: the same message carries the same already-spent budget, so a duration cap re-fails instantly in a pod-provisioning loop; and each redelivery re-provisions a FRESH pod whose recordRunGitMeta overwrites the first attempt's good git metadata with base==head — silently destroying the run's exported commits. Live: run 019f8e08 (Doki 3.2)…
  • server: tenant bot gallery slug — botregistry Path is the bundle dir (6e679a1)

    whytenantBotEntries re-keys a discovered tenant bundle to its store slug, since a forked bot's manifest name (e.g. "docs-refresh") differs from its slug and would otherwise collide with the catalog bot of that name and never surface under its own id. slugFromMaterializedPath wrongly required ≥2 path segments, but botregistry sets Entry.Path to the bundle DIRECTORY ("<root>/<slug>"), a single segment — so the slug was never applied and the tenant bot vanished from the gallery / 404'd on GET.
  • studio: reach editor home with tabs open + in-editor "Duplicate & edit" (d877725)

    whyTwo cloud editor UX gaps:
  • studio: read-only editor for catalog bots in cloud (no more 500 on Save) (05102a0)

    whyOpening a baked catalog bot in the cloud editor bound a filesystem path (/opt/iterion/bots/<bot>/main.bot); Save then hit /api/files/save and 500'd with "permission denied" (the image is read-only, and cloud has no writable workspace). Only a team-authored bot (botsource:// path) is writable in cloud.
  • studio: replace window.confirm/prompt with accessible dialogs (5511004)

    whyThe a11y source-discipline test bans window.confirm/alert (design-system Don'ts); the bundle-files delete used window.confirm and the new-file / fork-slug flows used window.prompt — reddening main's Tests check on the previous commit. Delete now goes through useConfirm(); the text prompts through a new promise-based usePromptText() hook (styled Dialog + Input, validation, Enter-to-submit), mirroring useConfirm's shape.