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/.

3.145.1 (2026-09-15)

Bug Fixes

  • golden-master: the seal declines per SET, never per directory (#1149) (6ceb323)

    why

    seal_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

    why

    The 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)

    why

    A 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

    why

    The 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

    why

    Keep 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

    why

    Show 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)

    why

    The 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)

    why

    The 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

    why

    The 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)

    why

    triage 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)

    why

    deepsec 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)

    why

    A "..." 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

    why

    A 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)

    why

    The 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)

    why

    Two 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

    why

    When 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)

    why

    Widening 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

    why

    Revi'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)

    why

    VitePress 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

    why

    Lot 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

    why

    The 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)

    why

    A 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

    why

    Four 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 continued.

3.132.5 (2026-09-10)

Bug Fixes

  • sec-audit: a failed scanner stops leaving an output file behind (#1079) (dd24c6d)

    why

    scan_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

    why

    The 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

    why

    The 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

    why

    delegateHooksFor 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)

    why

    The 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

    why

    All 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

    why

    Adversarial 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

    why

    Resolving 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

    why

    The 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

    why

    Ticket 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

    why

    A 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)

    why

    Provision 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)

    why

    The 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

    why

    The 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)

    why

    The 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

    why

    Outside 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)

    why

    Provisioning 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

    why

    The 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

    why

    Every 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)

    why

    CloudLanding 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

    why

    gateLaunch 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)

    why

    Rf213cf. 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

    why

    A 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

    why

    forgeUpstreamStatus 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)

    why

    Sharing 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)

    why

    Hook 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)

    why

    Nothing 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)

    why

    The 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

    why

    The 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

    why

    Surface 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

    why

    Production 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)

    why

    Addresses 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)

    why

    Measured 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

    why

    When 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

    why

    With 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)

    why

    The 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)

    why

    ListAllArtifacts 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)

    why

    From 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)

    why

    The 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)

    why

    The 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)

    why

    Three 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)

    why

    warnIfOverrideShadowsNewerBake 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

    why

    On 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)

    why

    A 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

    why

    A 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 $incs 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)

    why

    A 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)

    why

    The 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

    why

    A 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

    why

    A 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)

    why

    A 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)

    why

    The 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)

    why

    Two 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)

    why

    A 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

    why

    pkg/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)

    why

    A 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

    why

    Deployed 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)

    why

    On 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

    why

    The 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

    why

    Revi (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)

    why

    Measured: 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

    why

    Function 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

    why

    The 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)

    why

    The 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

    why

    Every 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

    why

    ChainHooks 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

    why

    Two 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

    why

    Four 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)

    why

    Third 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

    why

    Three 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

    why

    Claude 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)

    why

    Sixth 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)

    why

    Revi'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

    why

    The 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)

    why

    Measured 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

    why

    A 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

    why

    A 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

    why

    Two 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)

    why

    Third 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

    why

    Issue.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

    why

    has_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

    why

    Two 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

    why

    A 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

    why

    The 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

    why

    applyPRLaunchContext — 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

    why

    The 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

    why

    Measured 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

    why

    The __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)

    why

    A 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

    why

    The 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

    why

    forgeAdminFor 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)

    why

    Measured 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

    why

    The 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

    why

    Two 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

    why

    resolveRef'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)

    why

    An 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

    why

    A 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)

    why

    Measured 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

    why

    Revi'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

    why

    Revi'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)

    why

    The 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

    why

    ReconcileStatusOptions 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)

    why

    A 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)

    why

    Revi'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)

    why

    The 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)

    why

    The 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)

    why

    The 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)

    why

    branch-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)

    why

    An 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

    why

    forgeAdminFor 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

    why

    Delay 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

    why

    A 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

    why

    The 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)

    why

    SetStateFrom 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)

    why

    Measured 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

    why

    Advance 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

    why

    Retain 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

    why

    The 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)

    why

    Measured 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

    why

    AGENTS.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)

    why

    Third 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

    why

    A 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

    why

    A 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

    why

    GitLab 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

    why

    The 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

    why

    Three 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

    why

    Lets 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)

    why

    The 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

    why

    in-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

    why

    Two 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)

    why

    extension_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

    why

    A 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

    why

    A 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

    why

    The 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

    why

    The 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

    why

    Measured 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

    why

    A 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

    why

    The 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)

    why

    A 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

    why

    A 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

    why

    Two 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)

    why

    A 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)

    why

    The 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

    why

    Measured 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

    why

    A 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)

    why

    syncSchedules 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

    why

    Measured 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

    why

    Follow-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

    why

    Commit 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)

    why

    Nothing 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

    why

    Two 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)

    why

    The 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)

    why

    The 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)

    why

    A 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)

    why

    Given 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)

    why

    The 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)

    why

    The 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)

    why

    Replying 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)

    why

    The 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)

    why

    Re-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)

    why

    Completes 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

    why

    Rebuilt 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)

    why

    Two 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)

    why

    Measured 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)

    why

    The 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)

    why

    TestBudgetGraceCoversDuration 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

    why

    The 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)

    why

    Revi'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

    why

    An 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)

    why

    An 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)

    why

    The 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)

    why

    A 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)

    why

    PerformMergeCtx 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)

    why

    One 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)

    why

    The 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)

    why

    A 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)

    why

    Selftest 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)

    why

    An 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)

    why

    Production 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)

    why

    A 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)

    why

    TestLaunch_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

    why

    v3.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)

    why

    The 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)

    why

    One 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)

    why

    Findings 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)

    why

    Replicates 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

    why

    A 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

    why

    The 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)

    why

    The 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)

    why

    A 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)

    why

    A 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)

    why

    R79a75e. 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)

    why

    Bots 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)

    why

    The 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

    why

    The 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)

    why

    SubmitLaunch 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)

    why

    The 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)

    why

    Extends 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

    why

    buildNodeInputRS 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)

    why

    A 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)

    why

    An 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)

    why

    publish_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)

    why

    New 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)

    why

    Iterating 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)

    why

    A 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)

    why

    A 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

    why

    The 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)

    why

    The 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)

    why

    A 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)

    why

    A 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)

    why

    The 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

    why

    Keep 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

    why

    A 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)

    why

    Two 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)

    why

    feature-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)

    why

    Revi'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)

    why

    A 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)

    why

    A 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

    why

    The [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)

    why

    The 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

    why

    Four 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)

    why

    A 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)

    why

    The 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)

    why

    iterion 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

    why

    PR #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)

    why

    reconcileFinishedTickets — 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

    why

    Compiler 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)

    why

    EffectiveModel 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

    why

    A 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)

    why

    The 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)

    why

    A 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)

    why

    This 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

    why

    When 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)

    why

    An 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)

    why

    Adversarial 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)

    why

    The 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)

    why

    A 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)

    why

    fan_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)

    why

    A 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)

    why

    The 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)

    why

    A 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)

    why

    On 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)

    why

    The 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

    why

    Revi'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)

    why

    A 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)

    why

    Tracing 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)

    why

    H2: 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)

    why

    The 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)

    why

    Non-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)

    why

    A 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)

    why

    Narrowing 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)

    why

    A 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

    why

    Revi'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)

    why

    The 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)

    why

    Adds 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)

    why

    New 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)

    why

    After 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)

    why

    hnrss.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)

    why

    The 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)

    why

    Two 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)

    why

    An 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)

    why

    Setup 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)

    why

    LockRun 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)

    why

    A 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

    why

    The 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)

    why

    The 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)

    why

    A 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)

    why

    GitLab'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)

    why

    Eighth 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)

    why

    A 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)

    why

    Two 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)

    why

    Four 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)

    why

    Third 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)

    why

    Sixth pass, on the fifth's own code.

  • clean: a repository that answers about itself proves nothing (cf3c94b)

    why

    Fifth 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)

    why

    Seventh 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)

    why

    An 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)

    why

    Fourth 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)

    why

    Second 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)

    why

    The 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)

    why

    Five 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)

    why

    A 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)

    why

    The 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)

    why

    ReviewScopePanel 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)

    why

    Inbound 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

    why

    Acci'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

    why

    Observed 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)

    why

    GitLab'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)

    why

    Three 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)

    why

    A 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)

    why

    First 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)

    why

    Two 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)

    why

    Measured 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)

    why

    Deploys 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

    why

    Two 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)

    why

    modernize 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)

    why

    A 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)

    why

    Every 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

    why

    Four 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)

    why

    The 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)

    why

    The 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)

    why

    A 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)

    why

    Switch 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)

    why

    usageWindowRetryAt 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

    why

    The 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)

    why

    Measured 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)

    why

    The 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)

    why

    They 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)

    why

    A 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)

    why

    A 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)

    why

    A 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)

    why

    The 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)

    why

    A 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)

    why

    The 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)

    why

    The 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)

    why

    A 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

    why

    A 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

    why

    A 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

    why

    Run 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

    why

    Discharges 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)

    why

    ADR-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)

    why

    The 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)

    why

    An 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)

    why

    The 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)

    why

    A 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)

    why

    An 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)

    why

    The 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)

    why

    Round 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)

    why

    The 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)

    why

    feat_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)

    why

    A 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)

    why

    Turn 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)

    why

    POST /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)

    why

    The 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)

    why

    TestEveryExecutorConstructionDecidesTheBotIdentity 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)

    why

    At 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)

    why

    Auto-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)

    why

    Under 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)

    why

    Measured 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)

    why

    Twice 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

    why

    EstimateUSD 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)

    why

    A 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)

    why

    A 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)

    why

    A 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)

    why

    Narrowing 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)

    why

    A 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

    why

    Iterating 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

    why

    Iterating 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)

    why

    Three 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)

    why

    The 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)

    why

    Iterating 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)

    why

    The 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)

    why

    Add 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)

    why

    Address every finding of the opus adversarial pass on 603161446:

  • server: relaunch claim is per-bot; rune-safe reason truncation (#358) (3080fb2), references #357

    why

    Two 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

    why

    A 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)

    why

    A 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)

    why

    Iterating 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)

    why

    Developers 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)

    why

    runRecoveryFormatterPass 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)

    why

    cost.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)

    why

    Aggregator 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)

    why

    EnsureManagedSecret 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)

    why

    A 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)

    why

    A 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)

    why

    In 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)

    why

    The 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

    why

    A 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)

    why

    The 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)

    why

    A 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)

    why

    A 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)

    why

    In 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)

    why

    Making 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)

    why

    The 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)

    why

    Adversarial 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)

    why

    Adversarial 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)

    why

    A 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

    why

    A 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)

    why

    The 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)

    why

    Found 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)

    why

    The 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)

    why

    The 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)

    why

    Resolving 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)

    why

    The 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)

    why

    A 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

    why

    A 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)

    why

    pi 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)

    why

    only 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)

    why

    The 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)

    why

    The 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)

    why

    pi 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)

    why

    Five 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)

    why

    Seeding 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)

    why

    The 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)

    why

    Revi 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)

    why

    A 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)

    why

    An 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)

    why

    Moving 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)

    why

    A 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)

    why

    The --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)

    why

    Adversarial 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)

    why

    store.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)

    why

    Adversarial 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)

    why

    Adversarial 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)

    why

    Same 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)

    why

    The 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)

    why

    Revi 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)

    why

    Revi round 19, both real.

  • pi: keep the ChatGPT token out of git, and stop refusing nodes we do not fund (7903c78)

    why

    Two more from Revi, both medium.

  • pi: make state files reach a copy-based sandbox (040b59e)

    why

    A 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)

    why

    The 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)

    why

    Three from Revi, all consequences of the previous two commits.

  • pi: StateDir reports WHO can plant, and the pre-flight call is finally tested (a2ada61)

    why

    Adversarial 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)

    why

    Six 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)

    why

    Two defects a resumed run exposed, neither reachable from iterion run.

  • pi: the containment fix was killing the credential it protects (2e919f1)

    why

    Adversarial 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)

    why

    The 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)

    why

    Third 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)

    why

    Revi'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)

    why

    A 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)

    why

    A 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)

    why

    A 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)

    why

    Two regressions the engine-owned skill list introduced, both silent.

  • secrets: state only the billing arrangement that was measured (2fdcb80)

    why

    The 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)

    why

    The 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)

    why

    A 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

    why

    A 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

    why

    Every 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)

    why

    A 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)

    why

    The 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

    why

    Observed 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)

    why

    Revi 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)

    why

    A 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)

    why

    The 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

    why

    pi (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)

    why

    Adds 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

    why

    enablePullRequestAutoMerge 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)

    why

    Mutant 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

    why

    ScanFrontmatter (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)

    why

    In 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)

    why

    A 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

    why

    Answering 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)

    why

    Moving 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)

    why

    Revi 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)

    why

    The 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)

    why

    The 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

    why

    When 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)

    why

    The 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)

    why

    The 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)

    why

    Reasoning 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)

    why

    iterion 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

    why

    A 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)

    why

    Publishing 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)

    why

    min_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)

    why

    The 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)

    why

    The 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)

    why

    verify-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)

    why

    It 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)

    why

    The 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

    why

    Two 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)

    why

    Records 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)

    why

    Ajoute 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)

    why

    Trois faiblesses residuelles du run 005.

  • model: show the rejected payload when a tool call fails (addfc99)

    why

    Une 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)

    why

    The 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)

    why

    Le 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)

    why
    1. 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)

    why

    Deux defauts trouves en surveillant le run 006, tous deux de conception.

  • golden-master: the emitted runner advertised a flag it did not implement (6ca64c2)

    why

    L'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)

    why

    Le 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)

    why

    A 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

    why

    Second 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)

    why

    Both 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

    why

    Found 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)

    why

    Operability 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)

    why

    Live 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)

    why

    codeindex (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

    why

    The 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

    why

    The 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)

    why

    The /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

    why

    Two 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)

    why

    Doki 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)

    why

    In 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)

    why

    Clicking "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)

    why

    Three 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)

    why

    Two 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

    why

    git 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)

    why

    The 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)

    why

    A 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)

    why

    A 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)

    why

    3.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)

    why

    A 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)

    why

    Adds 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)

    why

    Adds 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)

    why

    Decouple 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)

    why

    First 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

    why

    The 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)

    why

    ErrBudgetExceeded 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)

    why

    tenantBotEntries 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)

    why

    Two cloud editor UX gaps:

  • studio: read-only editor for catalog bots in cloud (no more 500 on Save) (05102a0)

    why

    Opening 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)

    why

    The 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.