Conversation
… terminal ISS-148 shipped the reader for `nodeTools` and stopped at "writing the config is the operator's call". Wrong stopping point: this repository already has the blocks for exactly this, and leaving an operator to hand-edit JSON is the failure mode those blocks exist to prevent. TWO CORRECTIONS TO WHAT JUST SHIPPED: - SHAPE. `nodeTools` was an ARRAY. `planCatalogDeclaration` refuses a block that is not a record, so an array was a declaration no guided command could ever write — the built-and-never-wired pattern, committed an hour after writing it down. It is now a catalog block keyed by command, like `delivery`, `connections`, `surfaces` and `workspaces`. The key being the command also settles what two entries naming one binary mean: they cannot exist. - PATH. `health.ts` hardcoded ".refarm" while the new reader resolved `sovereignConfigRelativePath`. Two readers disagreeing about which file is the node config. Both now go through the same helper. `refarm tools add` composes prompt-contract-v1 (channel-abstract), operation-consent-v1 (whole-file diff, durable record, undo) and catalog-authoring (keyed write preserving unowned fields, ISS-036). Re-implements none of them. The only thing `tools.ts` owns is measuring the binary BEFORE asking, so the floor the operator authorises is a number they were shown — the line between a draft accepted and an inference nobody decided. THE ATTENDED HALF IS PROVEN, not asserted. Live against the node at 127.0.0.1:42001: `--attended-elsewhere` published the journey to the pending-prompt hub, `GET /prompts` served the consent select with all three options and the measurement in its notices, an external client POSTed `authorize`, and the command woke, returned authorized and wrote the declaration with an undo record. The terminal was never used for the decision. Two regression tests hold the wiring: the flag must select the attended channel, and no publisher must yield the same `deferred` as a headless terminal. THREE ARCHITECTURAL GUARDS CAUGHT SHORTCUTS, each pointing somewhere better: process-boundary refused `node:child_process` in app source, which moved `measureTool` into @refarm.dev/health beside the auditor that already spawns; ancestor-option refused `--args` (collides with Commander's positional bag) and then `--version-args` (variadic, invisible to that guard's probe — ISS-151), so the flag takes one token, which also removed speculative generality; probe-coverage required `tools list` to be accounted for, now registered in the directory-independence probe with its reason. Refusals that are the point: a tool that did not run is refused rather than declared; the consent notes say that authorising this ALSO authorises an execution, because `health` runs the binary on every audit; with nobody to ask, nothing is written. ISS-150. 240 files / 3613 tests green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
Measured three times on 2026-08-18: `agent finish --lane after-edit` reported FAIL on a step whose entire output was turbo's startup banner, at elapsedMs=180106 against a 180000ms ceiling. Nothing had failed. The step was killed before it could finish, and the envelope had no way to say so. The suppression was one line of ordering. `commandPlanSpawnErrorMessage` returned `stderr` whenever it was non-empty and produced its timeout sentence only when stderr was EMPTY — so any process that printed anything at all before being killed reported that instead. A startup banner was enough. The one fact that explained the result was the one fact hidden by it. Two wrong diagnoses were drawn from that envelope before `elapsedMs` was read by hand: first that something in the package had broken, then that the lane's `--maxWorkers=1` was to blame (it comes from the package's own test script and applies to standalone runs identically). The distinction now survives the whole path to the operator, because it is useless if it stops early: - the step carries `timedOut` and `timeoutMs`, derived from the signal and `ETIMEDOUT`. A signal with no declared ceiling is somebody else's kill and is not claimed here; - the message names the kill FIRST and keeps the partial output beneath it — the kill explains the result, the output is evidence, and both are wanted; - the RUN gains a third status. A caller that retries on failure would retry a killed step forever at the same budget; - the resume record and summary carry it, so coming back tomorrow reads "killed at its time ceiling" instead of "complete the failed validation handoff", which sends an operator hunting a defect that does not exist. The near-miss worth recording: `resume` returned recovery commands only for `status === "failed"`, so splitting the outcome would have quietly cost a killed gate its own recovery — a worse bug than the one being fixed. The predicate is now "left work behind", which both outcomes are. ISS-149's remaining three answers are untouched and still the operator's: raise the ceiling, split `test` out of the validation step, or warm the cache inside the lane. What changes here is that choosing between them no longer starts with discovering that nothing was broken. 333 tests in @refarm.dev/cli, 240 files / 3613 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…unfused ISS-073, first slice. The operator ruled on 2026-08-12 that the spend record must cover work this node did not dispatch AND separate the two, because a record counting only its own dispatches answers "is this tool expensive" instead of "how much of my quota is left". He made it conditional: the provider has to give the number in a form that is easy to audit. MEASURED 2026-08-18, and the condition is now met — by work that landed earlier the same day. `credential quota` reads github-copilot's meters live (plan, sku, resetsAt, per-meter kind), and openai-codex answers `cannot-ask`, which is the ruling's third state arriving on its own. THE RULING'S MIDDLE FIGURE CANNOT BE COMPUTED, and the reason is worth more than the figure: - the provider meters PREMIUM INTERACTIONS; the record counts tokens and requests. `consumed − dispatched` does not typecheck across those units; - the meters reset on a date, so counting all-time dispatches against them is the same category error moved into the time dimension; - and the model measured that day landed on an `unlimited` meter, while nothing records WHICH meter a dispatch spends. Subtracting would assert an attribution no one measured. So this module refuses to emit `notDispatched` — there is a test whose whole job is that the string never appears in the output. What it emits is both numbers with the attribution between them NAMED as `unknown`, which is the ruling's own third state applied to a question the ruling did not know it was asking. A reader shown two numbers and no attribution will subtract them; saying "this is unattributed" is the only thing that stops it. `dispatchedHere` is `null` when no window was declared, rather than an all-time count that silently spans a different period than the provider's. Recording the meter a dispatch spends is what turns `unknown` into a number. That is a change to the dispatch path, not to this reader. 9 tests, 120 in the package. The prose is brand-free: this contract is meant to be reused by surfaces that are not this CLI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
ISS-073 steps 1 and 2. The contract that decides what the pairing means shipped in a4fba35 and nothing called it — the pattern this session has been catching all day. `refarm budget quota` calls it, composing the two readers that already existed: `readQuotaRows` for the provider, the BudgetObservation record for this node. MEASURED against the operator's real node, and the number is the point: corporativo · github-copilot — August 2026 (derived-from-reset) this node dispatched 16 request(s) in that period premium_interactions: 8294 of 10000 consumed, 1706 left His corporate seat is 83% spent and this node accounts for at most 16 requests of it. That is the question the ruling said mattered — "how much of my quota is left" — answered without pretending refarm caused the rest. THE WINDOW is what makes the two counts comparable, and it is derived, not declared: the provider states a reset date and a monthly sku, and the period is an inference from those two. So it is labelled `derived-from-reset` in every row. A mid-month reset is REFUSED rather than guessed — that could be monthly-from-signup, weekly, or something this build has never seen, and a guess would produce a window that looks measured and is not. When no window can be established the count comes back `null`, which is a different statement from the measured `0` an account with a known window and no dispatches gets. Three absences stay separate because three repairs do: a provider that could not be asked (`cannot-ask` — "not the same as having none left"), a dated dispatch naming no account (36 of them here, counted against nobody's quota), and a dispatch with no timestamp that no period can claim. The dispatch count is printed once per ACCOUNT, not per meter. Three meters repeating it read as three times the traffic, and a reader who adds them gets a number nothing measured. THE REFUSAL GUARD CAUGHT what I had not thought about: this command reaches TWO networks. `readQuotaRows` already degrades a provider failure into a row that says so; the local record did not, and an unreachable runtime threw straight out of `parseAsync`. A command that crashes where its siblings refuse is one an operator cannot tell apart from a broken node. Now guarded, and registered in the directory-independence probe as `network`, beside `credential quota`. 10 + 6 + 4 tests. 241 files / 3624 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…fusable ISS-073 step 3 — the one that turns UNATTRIBUTED into a number where a number is honest. Two cheaper answers were tried first and both were measured away on 2026-08-18: - THE PROVIDER DOES NOT SAY. A Copilot completion returns twelve response headers and not one mentions quota, limit, premium, usage or remaining; - A HARDCODED model→meter table would age in silence, which is precisely the failure this whole slice exists to stop: a wrong denominator wearing a measurement's clothes. What is left is this node's own measurement — read the meter, dispatch once, read it again. Done live: `gpt-4o` on github-copilot moved neither premium meter (1500/1500 and 1706/10000, before and after). That single dated fact is what lets a row say something definite instead of shrugging. A fact without `measuredAt` is DROPPED rather than trusted. One nobody can re-check is one nobody will. The attribution has two values and the missing third is deliberate. `none` is claimable — every model dispatched was measured not to touch the meter. A NUMBER is not: this provider publishes per-model multipliers for premium interactions, so "it spends the meter" does not say how much, and counting one dispatch as one interaction would be a rate this node invented. ONE UNMEASURED MODEL POISONS THE CLAIM, and it fired on the real node the moment it shipped: `pessoal` now reads "NONE of that consumption was this node", while `corporativo` stays UNATTRIBUTED and names why — `gpt-5.5` also ran there and nothing has measured it — and states the three steps that would settle it. A BUG THE TESTS CAUGHT, of exactly the kind this slice is about: an account that dispatched traffic whose model could not be READ was claiming `none`. "I sent nothing" and "I sent things I cannot classify" are different facts, and only the first supports a claim that a meter went untouched. `modelUnknownByAccount` keeps them apart; an unclassifiable dispatch now sends the row to `unknown`. 8 + 4 tests added, 136 in the contract, 241 files / 3624 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
ISS-073 closed. The document a future reader needs is not "here is the command" — it is the three walls that make the obvious arithmetic wrong, because the next person to look at a consumed figure beside a dispatch count will try to subtract them, exactly as the original ruling did. Units, time, meter. Each measured, each with the date it was measured. Also records the three-step procedure that turns `unknown` into `none` for a model, since it is the only honest source: the provider does not say, and a table would age in silence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
MEASURED on the operator's node: 36 of 57 budget observations named no account, and they were NOT historical — the newest unattributed row is newer than the oldest attributed one, so the hole was still growing. Every one of them was dispatched from a directory with no workspace binding, and every one of them spent a real seat while the record said nobody paid. The payer came only from a workspace→account binding. That is right when a binding exists and wrong as the only source: `refarm ask` run from anywhere unbound resolved a route, spent whatever seat the host chose, and recorded nothing. The seat is knowable exactly when the provider has ONE usable account, and that covers 34 of the 36 — they were openai-codex, which this node holds a single account of. WITH TWO SEATS IT STILL REFUSES, and that is the half worth keeping. Nothing in the CLI knows which of two Copilot seats the host chose, and naming either would attribute spend to an account that may not have paid — the same silent substitution ISS-131 removed from the resolver. `unattributed` stays the true answer there rather than becoming a guess wearing an attribution. `healthy` only: an `incomplete` account has no secret to spend, so it cannot have paid, and counting it toward the ambiguity would throw away an attribution that is actually determined. FOUND WHILE MEASURING, and filed onto ISS-121 rather than fixed here: two of those observations carry `gen_ai.request.model: __refarm_ancestor_option_probe__`. A conformance test did not merely poison the model route — it caused dispatches that are now permanent rows in the node's spend ledger. The route was repairable by writing the right value back; an append-only record of something that happened is not, because "a test did it" is not a state the record can express. The recurrence cause is that the harness drives the real CLI against the real node base with no scratch SOVEREIGN_BASE. 5 tests. 241 files / 3624 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
ISS-123's closing condition was "when `undecided` reaches zero on his real node". It was one file: `~/.refarm/session.lock`, 57 bytes, and `hasUndecided` is driven by that list alone — so a single unclassified lock stood between the operator's node and a bundle he could trust. A lock names a LIVE process. Carried into a bundle and restored elsewhere it points at a pid that does not exist there, and a stale lock is worse than an absent one: it can make the restored node refuse to start, or believe a session is already held. So it is `cache` with a `rebuiltBy` — the process that takes it takes it again on the next run. KEYED ON THE SUFFIX, not the filename. A rule naming `session.lock` would leave the next lock undecidable, which is exactly how this entry came to exist. The layout's own refusal text asks for the entry to be added deliberately; this adds the class, not the instance. MEASURED AFTERWARDS ON THE REAL NODE, because the point was never the rule: hasUndecided true → false undecidable 1 → 0 bundle 34 files, 1.1MB, verify ok secrets 0 of 3 candidates harvested from the live silo appear in it manifest telegram.token → re-obtain, ca.key → re-establish, cert → re-issue The split holds on his real data, not only in the suite that built its own node. 23 tests in the inventory suite, 241 files / 3627 in apps/refarm. WHAT THIS DOES NOT DECIDE, and both are the operator's: the bundle needs a durable destination (one written to /tmp is not a backup), and `ca.key` does not travel. Regenerating it is not recovery — every device that trusted the old one must be re-enrolled by hand — while `--include-secrets` makes the bundle itself a credential, and a backup is precisely the thing people copy to places they do not think hard about. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
ISS-123 closed. What kept it open was never the instrument — inventory, export and restore all shipped in August — but that the operator's own node was not backed up by it. Re-measuring found the pile had already shrunk to one file. Records what the re-measurement settled, so nobody re-opens it looking for the 104 `foreign` entries: those are a DECISION, not an absence of one. 89 peer ids and 9 databases for namespaces this node never declared, plus six hand-made config backups — not carried and not deleted, because carrying every undeclared file would bury the 34 that stand the node up. The two open questions are the operator's and are named as his: a durable destination, and whether the CA key travels. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…k about
MEASURED 2026-08-18 against the operator's real business seat, and found
sideways: a number changed between two reads while building something else, and
the raw endpoint was consulted instead of calling it transient.
premium_interactions:
has_quota: false ← this reader called it "does not apply to this plan"
entitlement: 10000
remaining: 0
credits_used: 10000
GitHub sends `has_quota: false` when the meter is EXHAUSTED, not only when it
does not apply. The assumption was plausible — the field reads like an
applicability flag — and its cost was asymmetric in the worst direction: a seat
at zero reported as "the provider would not say", which an operator reads as
missing information and keeps dispatching against.
The entitlement separates them. Nothing allotted means the plan has no such
meter; something allotted with nothing left means it is spent.
This lit machinery that already existed and had never fired. `exhaustedMeters`
now returns the seat, and `credential quota` ends with a handoff the operator
can act on: "corporativo/premium_interactions is out. Bind the workspaces that
spend it elsewhere, or wait for the reset."
ALSO HERE, from the workspace axis (ISS-064 step 3, the seeing half): each seat
in `budget quota` now shows which workspaces spent it, largest first —
`rcdc5 → pessoal`, `refarm → corporativo` on this node. Dispatches that named no
workspace get their own line rather than being diluted into a share or left for
the reader to find by subtraction.
Seeing precedes capping: the enforcement half needs a REQUESTS axis, and
`BudgetAxis` has only `deadlineMs | maxTokens | maxUsd`. A subscription meter
counts requests regardless of size, so no token ceiling protects a shared seat.
That is a cross-language change and now has a measured reason to weigh it
against — this seat went from 1706 remaining to zero during the session.
11 + 3 tests. 241 files / 3630 in apps/refarm.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…ot express ISS-129 closed as superseded — its verdict "github-copilot cannot say" was right about `copilot_internal/v2/token` and wrong about the provider, which answers on `copilot_internal/user`. The code recorded that correction in August; the ledger never caught up. ISS-064 re-stated against what is now measured. Steps 1 and 2 are done: the denominator is extracted, not declared, and a provider that exposes nothing says so. Step 3 splits cleanly — seeing shipped, capping cannot be built without a requests axis that does not exist. Records the limit anyone building that axis needs first: a ceiling governs what this node dispatches, not a share of the provider's meter, because other clients spend the same seat. The surface must not let the two be confused. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
You asked for a requests axis on the budget fold. Three measurements say it would not have done what it was wanted for, so this builds the thing that does. WHY THE AXIS WAS THE WRONG SHAPE: - `resolveBudget` bounds ONE dispatch — `ceilings_for_payload` puts its ceilings into that dispatch's payload. A `maxRequests` there caps the turns of a single `ask`; - nothing anywhere reads accumulated spend before dispatching. The BudgetObservation is written after; - and the Rust port mirrors the TS conformance list BY HAND, so adding an axis to one side is a silent divergence rather than a build failure. A per-run ceiling cannot stop five hundred dispatches of one request each, which is how the operator's shared seat went from 1706 premium interactions remaining to zero while every token cap on the node stayed untouched. The axis was reverted; a field named `maxRequests` on a per-dispatch fold would also have read as reserving a share of the seat, which nothing here can promise. WHAT A SUBSCRIPTION ACTUALLY NEEDS is a per-MONTH allowance, checked before dispatching, and that is what `workspaceAllowances` is. refarm ask: this node has dispatched 8 of the 2 request(s) allowed for "rcdc5" this month. Raise the allowance, wait for the month to turn, or bind the workspace to another account. This bounds what THIS NODE sends — other clients spend the same seat. Refuses before spending, exits non-zero, and SUMS ACROSS ACCOUNTS: the allowance bounds the workspace, and counting per account would let one workspace spend its full cap against every seat the node holds. THREE OUTCOMES, and the third is why this can sit on a dispatch path: - `unbounded` — nobody declared a limit, which is what a node that never asked to be capped must keep getting; - `exceeded` — refused; - `cannot-check` — the record could not be read, so it PERMITS and says so. Refusing work because the node cannot count would make it unusable exactly when its runtime is down; permitting in silence would make the allowance a fiction. Permitting out loud is neither. The window is called a MONTH because a month is what the code counts. Naming it "period" while counting months is the units error this surface exists to avoid, one layer up — `budget quota` still reports against the provider's own stated reset, and for github-copilot the two coincide. A LIVE TEST THAT LOOKED LIKE A BUG AND WAS NOT: an allowance declared on `refarm`, run from the repo, refused nothing. The record showed why — the dispatch carried `workspace: rcdc5, source: declared`, because an active session declares a workspace that overrides the directory seed. The gate had run, correctly, on the workspace that was actually spending. 10 + 6 tests. 242 files / 3636 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
Written for the two things that mislead rather than for the field list: it does not reserve a share of the provider's meter, and it applies to the workspace the SESSION declares rather than the directory you are standing in — the second cost a live test that looked like a broken gate and was not. ISS-152 records what stays the operator's: whether `cannot-check` should permit (a conditional promise) or refuse (paralysis when the runtime is down), and whether an allowance is a fact of the node or of the workspace. The second only starts to bite with the second node, and only after allowances exist on several workspaces — which is exactly when it becomes expensive to change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
MEASURED 2026-08-19 on the operator's node: every `refarm ask` failed with `HTTP 401: IDE token expired: unauthorized: token expired`, and a runtime restart fixed it. The credential was fine. The message sends an operator to re-authenticate something that does not need it. THE CAUSE IS STRUCTURAL. The host reads `MODEL_ACCOUNT_CREDENTIALS` from its own process environment (`std::env::var`, wasi_bridge/core.rs), fixed at spawn. Renewal runs when the model capability is provisioned. So: a credential with a finite life + a process handed it once = a node that expires Nothing renews in place and no endpoint re-provisions a live runtime. The window is about a day — long enough for the failure to look random, short enough for a daemon to die daily. This does not fix that. It stops the node from reporting it as a provider refusal: `ask` now checks the stored expiry before dispatching and says what it is and which command restores it. A GUARD THAT COULD NOT FIRE, caught by measuring instead of trusting the unit: the first version read `expires` as seconds and multiplied by 1000. The field is MILLISECONDS — 1787193667000 is a day out in ms and the year 58603 in seconds. Every credential would have read as fresh for fifty thousand years. That is worse than no check, because it reads as coverage. Verified against the real stored credential: 1431 minutes left, not 29756775988. WHAT STAYS THE OPERATOR'S, and it is not a preference: whether `ask` should renew and restart the runtime by itself. It is the convenient-looking option and the only one of three that can kill work in flight — on a node meant to serve a phone and a PWA, "in flight" includes the operator's own work from somewhere else. The real fix is the third path: the host reading its credential as live data rather than receiving it once at spawn. 4 tests. 243 files / 3640 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…hat it grants THE OPERATOR'S DESIGN, 2026-08-19, in his words: a workspace carries a suggested allowance as a baseline of what working on it is expected to cost, the node holds the division, and it may keep its own configuration, canonise the workspace's announcement, or duly honour a workspace that already announces one. The strongest thing about it is that it is not new. `docs/CONFIG_TIERS.md` already says it from the other side — "a workspace states a need, it never holds a grant" — and the safety rule already exists in `resolveBudget`: a scope cannot grant capacity the machine lacks. He arrived at the repository's own boundary rather than inventing a third one. ONE ASYMMETRY DOES ALL THE SECURITY WORK, with no allowlist and no per-repo approval: asking for LESS takes nothing from anyone, so a tightening announcement binds — including when the node granted nothing, since a self-imposed cap is not an escalation. Asking for MORE would let a repository widen the operator's spend by being cloned, so the node's grant wins. His three cases fall out of that single rule: keep the node's config the node declares; an announcement can only tighten canonise the workspace write the announcement into the grant (not built yet) honour the announcement automatic, because it only ever arrives restricting PROVEN LIVE IN BOTH DIRECTIONS against the operator's node. A workspace announcing 1 with no node grant refused at "12 of 1". A workspace announcing 99999 against a node grant of 3 refused at "12 of 3" — the announcement could not widen itself. `node` is reported when the two are EQUAL: nothing changed hands, and naming the workspace would send an operator raising the node's grant to the wrong ceiling. The announcement reaches the REFUSAL, not just the reconciliation — a rule that stops one function short of the gate is a rule nothing enforces, and there is a test that holds it there. 15 + 11 tests. 243 files / 3645 in apps/refarm. NOT BUILT: canonising. Turning an announcement into a grant is a hand edit today; the guided path is the one `tools add` already walks — measure, show the diff, write with an undo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…agree The three postures an operator wanted — keep, canonise, honour — are not modes. They fall out of one asymmetry, and the table is the shortest way to see it. Records the equal-values case explicitly, because it is the one that misleads: the node is reported as binding, so raising a grant that already matches an announcement has a visible effect rather than none. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
The node died daily. Measured 2026-08-19: every dispatch failed with `token expired` about a day after the runtime started, and only a restart fixed it. The credential was fine. a credential with a finite life + a process handed it once = a node that expires The host already re-read its map on every call. What was missing was a source anything could rewrite — a process cannot have its own environment updated from outside, and no endpoint re-provisions a live runtime. THREE PARTS, and the smallest one is the Rust: - the host prefers `MODEL_ACCOUNT_CREDENTIALS_PATH` over the inline copy, and falls back to it when the file is missing or EMPTY. A vanished file leaves a stale credential, not a broken node, and a half-written rewrite must not blank a seat that is perfectly good; - provisioning writes `~/.refarm/model-accounts.token` — 0600, and by rename, because the host reads this on a dispatch path; - `ask` renews what has lapsed and rewrites that file instead of refusing. The host picks it up on its next call. NO RESTART, deliberately. Restarting to pick up a renewal kills work in flight, and on a node meant to serve a phone and a PWA that includes the operator's own work from somewhere else. RENEWAL STAYS IN THE CLI. Putting it in the host would give a process that only dispatches three new responsibilities: carrying a refresh token, speaking OAuth to GitHub, and handling network failure. The CLI already does all three; the host only needed to re-read. THE NAME ENDS IN `.token` so `SOVEREIGN_LAYOUT` already classifies it as a secret — never carried into a backup bundle, and named in the manifest as something to re-obtain. No new rule. PROVEN LIVE, by trying to make it FAIL: an invalid token written into the file broke the dispatch (`HTTP 400: Authorization header is badly formatted`) while the inline copy was still valid — so the host read the file. Restoring only the file, with no restart, dispatched again (`gpt-4o, 2393 in / 3 out`). An earlier version of that test "passed" and proved nothing: `cargo check` does not produce a binary, so the running host had none of this code. A test that passes for the wrong reason is worse than one that fails, because it ends the investigation. 1504 Rust lib tests, 245 files / 3654 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
ISS-153. One failure in roughly seven `cargo test --lib` runs, five clean re-runs after, no reproduction. The name is unknown because `--quiet` prints the result line and suppresses the block that identifies the test. Same shape as ISS-149: the output reporting a problem omits the field needed to act on it, and the fix is at the invocation rather than in any test. Records what measurement ruled out — the live daemon, five clean runs with it up — so the next observer does not spend the afternoon re-running. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
The host can be handed a fresh credential without a restart (5791626). What was missing is something that hands it one when nobody is typing: renewal ran at provisioning and inside `refarm ask`, so a dispatch arriving from a phone, a PWA or a scheduled automation still met a stale token on a node that had been up a day. `refarm credential renew` is that something — a COMMAND rather than a daemon, because this node already supervises declared processes and the cadence is the operator's declaration, not a timer somebody hardcoded. It is honest when there is nothing to do: "Nothing had lapsed — no provider was asked." No network call happens on a healthy node, which is what makes running it often nearly free. PROVEN END TO END by forcing a real expiry on the operator's node: the stored credential went stale, `renew --json` returned `refreshed`, the token changed (26cde4 → 5fecf6), the live file carried the new one, and the node dispatched — `gpt-4o, 2248 in / 3 out` — with no restart between any of those steps. Registered in the directory-independence probe as `network`, beside `credential quota`: probing it from four directories would renew four times. The margin needed no invention. `REFRESH_MARGIN_MS` already stores `expires` five minutes before the provider's own deadline, so "expired" fires before the token actually dies. NOT DECLARED HERE, deliberately: writing a timer into the operator's node that talks to GitHub every N minutes is his call, and `refarm process add` is guided. The measurement that informs it: the token lives about a day, the command costs nothing when nothing lapsed, so the only real question is how long a dispatch from another device may fail if a renewal is missed. 245 files / 3655 tests in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
The operator asked the right question: if renewal is near-obligatory, why leave
each operator to discover it? Discovering it by the node stopping is what
happened to him, and it is the worst way to learn anything.
CANONICAL MEANS THE NODE TELLS YOU, not that the node decides for you. Declaring
a supervised process writes a timer that talks to a provider every few minutes
into someone's machine — that is an operator's decision. What the node owes them
is that the decision is MADE deliberately rather than discovered by a failure.
So `refarm health` reports it: this node holds a short-lived credential and
nothing declared here renews it. The chain already carries it the rest of the
way — `sow` ends by pointing at `refarm check`, `check` composes health, and
health now knows. A second copy of the sentence inside `sow` would be two
paraphrases to keep in step.
NO SECRET IS READ. Whether a provider's token expires is a fact about its auth
model, so the check reads descriptors and a declared process list. A list rather
than a heuristic, deliberately: "it stores an `expires` field" would report a gap
for every provider that records one without enforcing it, and a false finding is
expensive precisely because it teaches operators to skim findings.
NOT COUNTED IN `issueCount`, and the line matters because its sibling IS counted:
nodeTools the operator declared `gh >= 2.40` and it is not — broken NOW
renewal nothing is broken, the node dispatches, and nobody declared a
position on it — a PREDICTION
The lane made me find that by breaking: `check` went red on a working node, and
would have gone red on every node holding a Copilot account. A gate that is red
about the future teaches its reader to skim red, and then the real fault the next
day goes unseen. Advice rides as a recommendation; `issueCount` stays what is
broken now against something declared.
RE-MEASURED ON CACHE HITS, for the third time in this session and the same
reason: which accounts a node holds is a fact about the MACHINE, and the audit
cache fingerprints the repository. `refarm check` reads exactly that cached path,
so a cached all-clear would have kept saying nothing was wrong.
6 tests, 90 in @refarm.dev/health, 245 files / 3655 in apps/refarm.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
The operator tried to declare a process and the terminal made it nearly impossible. His transcript, verbatim: Qual processo? (refarm já sabe propor: web-serve) (web-serve) [web-serve]: refarm delivery: a question is waiting and could not be delivered — … THREE DEFECTS, and his read was right: the blocks were fine, the callers used them in ways the blocks made easy to get wrong. 1. THE DELIVERY NOTICE WAS WRITTEN INTO THE PROMPT LINE, once per question. The notice is correct when nobody is looking — a question waiting, an operator never told, nothing saying so. It is noise at someone who is READING THE QUESTION, and repeating it per prompt teaches them to read past the one line that would have mattered on the day nobody was there. `attendedLocally` silences it when a terminal is attending and keeps it when none is. 2. THE SAME VALUE THREE TIMES: the caller's prose, the placeholder, and the default. A placeholder SHOWS the shape of an answer; a default IS one — and passing one string as both makes one fact read as three. `textPromptHint` now collapses them, which protects every future caller rather than this one, and the question stopped naming what the channel already prints. 3. ENTER RE-ASKED IN SILENCE. The parenthesised example reads exactly like a default that Enter would accept, so an operator cannot tell a rejected answer from a question that did not register. It now says the example is a placeholder and that Enter does not accept it. TWO OF THE THREE FIXES ARE IN THE CONTRACT, not in the command, because the contract is what made the mistake easy: when a block is misused by a caller, the question worth asking is whether the block invited it. Here it did, twice. MY OWN TESTS PASSED FOR THE WRONG REASON FIRST: the adapter factories declared no `capability`/`unattended`, so no delivery was ever attempted and "it stayed quiet" was vacuously true. The sibling test — the one demanding the notice when nobody is at a terminal — is what exposed it. 139 in prompt-contract-v1, 45 delivery, 50 process-add, 245 files / 3657 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
The operator's argument, and it is the right one: renewal should belong to holding a credential rather than to remembering. Storing a token that expires CREATES the obligation — leaving him to meet it later, from a health advisory or from the node stopping, puts a gap where none needs to exist. It is also not a new capability. Authorising this node to hold a GitHub credential authorised it to speak to GitHub as him; renewing is maintenance of that, not another door. What IS a system change is installing a supervisor unit, so this PROPOSES through the same consent journey `process add` walks — the diff, the record, the undo. One question, in context, instead of an advisory to act on later. FOUR REFUSALS, so it never becomes a wizard that nags: - a provider whose credential does not expire on a clock gets no proposal. A timer that runs forever and does nothing is worse than silence; - something already renewing means no proposal, and it says which one; - a decline is remembered by the trail, so this asks once; - and it refuses when it cannot name its own binary. THAT LAST ONE IS THE SUBTLE FAILURE. The command is derived from `process.argv[1]` — the binary as actually invoked — never the string `refarm`. A unit naming `refarm` works in the operator's shell, where PATH carries `~/.local/bin`, and fails at boot, where a supervisor's does not. It is the classic supervised-unit failure: correct when tested by hand, broken when the system brings it up alone, and logged somewhere nobody reads. A broken declaration is worse than none — one you notice, the other looks done. The interval is 120s against a five-minute margin, with a test holding it: a check interval must fit INSIDE the tolerance rather than tie with it. The health advisory stays, with its role changed: from the front door to the safety net for credentials stored before this existed, and for an operator who declined and changed their mind. NEVER THROWS out of the credential path. A proposal that fails must not undo work the operator already authorised. 5 tests, 246 files / 3662 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
MEASURED ON A REAL TERMINAL, 2026-08-19: declaring one process in a 259-line config printed the WHOLE file twice — "Como está agora" and "Como fica" — about 530 lines to scroll for an addition of ten. The operator stopped reading, which is the failure: a proposal is authorisation, and authorisation of something unread is a formality. R2's reasoning was right and its implementation was the problem. "A diff you can only see three lines of is a category, not a change" is true; "so show all of them" does not follow. Twelve lines each side is enough to see which block the change lands in, and short enough to read. BOUNDED IS ONLY HONEST IF IT SAYS SO. A truncated view and a complete one look identical, and the operator is authorising exactly what they can see — so the render states what it left out: "… 23 linhas acima, não mostradas". A silent elision would be worse than the wall of text it replaced. AND THE TWO SIDES NOW LOOK AT THE SAME PLACE. Measured on the same proposal: the current view elided 247 lines and showed the file's TAIL while the result view elided 23 and showed the middle, where the change actually lands. The operator was comparing the end of the old file with the middle of the new one. The tail-slice was correct when every insertion was an append — a shell profile gaining a PATH line — and wrong for a catalog declaration, which lands inside a named block. The current view is now anchored at the insertion, and both sides elide the same count. 530 lines to 83, against the operator's real config. The fix is in the BLOCK, not the caller: every consent journey renders through this, so `delivery add`, `process add` and `tools add` all get it. 68 tests in @refarm.dev/operation-consent-v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…swer Second half of the same terminal measurement, 2026-08-19. Every question the wizard asked carried this into the prompt line: De qual diretório ele roda? […] [/home/s095407044]: refarm: could not publish this question to the node at http://127.0.0.1:42001 — fetch failed. It is answerable at this terminal only; attending devices will not see it. The sentence is true and the timing makes it noise. The operator is READING the question and about to answer it — the fallback the notice describes is the thing they are already doing. Written over the line they are typing on, it reads as an error about the answer rather than a fact about the node. Silenced when a local terminal is attending, said in full when none is — which is the case it was written for: a question published nowhere, nobody told, and the wizard waiting on a device that will never show it. Same shape as the delivery notice fixed in 330f6e3, and the same reason: a correct warning delivered at the wrong moment teaches its reader to skip warnings. The `fetch failed` was real — the node does not come back after a reboot unless something declares it, which is a separate gap the operator is now looking at. 246 files / 3662 tests in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…s so ISS-154. Chasing "why did the node not come back after the reboot" found something bigger than the answer. systemd unit → ~/.local/bin/refarm → ~/github/refarm/apps/refarm/dist/index.js `~/.local/bin/refarm` is a 313-byte shim into the working tree. So `web-serve` — supervised, running, and named after a path under `~/.local/bin` — executes code from the git tree this session has been editing all day. The runtime cannot even reach its own fallback: the launcher resolves "repo script, else the binary on PATH", and `tractor` is not on PATH here. Measured consequences, not imagined ones: a build rewrites what live services execute, a branch switch changes them silently, and `backup plan` carries 32 files of which none is the code — so a restore yields a fully configured node with nothing to run. ISS-123 closed on the node's source of truth being exportable, which is still true; the code was never part of that truth, and now it is clear it has to be. Not an accident of a bad install: running the working tree is the fastest loop, and this repo is the operator's own instrument. The defect is that nothing says the two are the same thing, so a development action and a node action are indistinguishable until one breaks the other. Records both directions with their costs, because the choice is the operator's: install a copy and promote deliberately, or declare the coupling and make the node say what it is running. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
ISS-154, the half that can be resolved now. Measured 2026-08-19 while chasing a node that did not come back after a reboot: systemd unit → ~/.local/bin/refarm → ~/github/refarm/apps/refarm/dist/index.js A 313-byte shim into a git working tree. Every supervised service on this node runs the development repo's build output while naming a path under `~/.local/bin`, so nothing about it looks that way. RUNNING THE WORKING TREE IS NOT THE DEFECT. It is the fastest loop there is, and this repository is the operator's own instrument — editing it and using it in the same breath is the point. The defect was that nothing SAID the two were the same thing, so a development action and a node action were indistinguishable until one broke the other. `readNodeSubstrate` walks up from the executed entrypoint looking for a git tree and returns `installed`, `working-tree` or `unknown`. UNKNOWN, never `installed`, when the process cannot name its own code: `installed` is the reassuring answer and nothing measured it. TWO DESIGN MISTAKES THE PROCESS CAUGHT, both about where advice belongs: - the advisory led `nextAction`. A handoff is what to DO next, and "nothing is broken, know this about your node" is not that — it pushed real recoveries below it. `severity: "info"` already existed and was already skipped by `diagnosticNextActions`; that is what the category is for, and the renewal advisory got the same treatment for the same reason; - and then `info` went mute. `health` printed "All checks passed" while carrying an unread note in `--json` only. Advice that reaches nobody is not advice, so the human view now ends with a "Worth knowing" section, distinct from faults. 9 tests, 99 in @refarm.dev/health, 246 files / 3663 in apps/refarm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
MEASURED 2026-08-19 on the operator's real node: `backup create` carries 32 files
— configuration, credentials-to-re-obtain, databases, peer ids — and not one of
them is code. Restoring on a reformatted machine yields a fully configured node,
with everything it needs to decide and nothing to execute.
That was already true and already invisible. ISS-123 closed on the node's source
of truth being exportable, and it still is; the code was never part of that truth,
and now it is clear that it has to be.
The manifest records `substrate: { kind, executes, repository, included: false }`
— for the same reason `secrets.included` exists. "This bundle is complete" and
"there was nothing else to carry" are different statements, and a restore reading
`working-tree` knows a repository has to be cloned back before any of this
configuration means anything.
`included` is typed as the literal `false`. A bundle that carried the code would
be a different artifact with a different size and a different trust boundary;
making the field settable would let a caller claim it.
REQUIRED, not defaulted, exactly like `secrets` above it: a default would write a
manifest that says nothing about the code, which is the silence this breaks. The
type made every caller answer, which is how the test callers got it too.
Verified on the real node — the manifest now carries `working-tree` and the
repository path.
20 tests in the backup suite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
The operator asked which section or diagram would disambiguate node, workspace and sandbox. The measurement gave the answer: the confusion is spatial — three things that look separate, and the arrows that tie them together. `docs/NODE_SUBSTRATE.md`, with an inline mermaid diagram. Inline rather than a `.mermaid` source under `docs/diagrams/`: those feed the SVG pipeline and are reference figures, while this one is read in context and must not depend on a browser render to be legible. Considered and rejected as homes: - `SANDBOX_NODE.md` ALREADY CARRIES THE FACT — "nothing about the sandbox isolates code" — but frames it as a limit of the test node. Growing a section about the operator's real node inside a document about the isolated one would bury it; - `specs/` is for designs of things to build; this describes what already is; - `ARCHITECTURE.md` maps the software's layers, not the machine's substrate. Cross-referenced both ways: the sandbox bullet now says this is not only a sandbox limit, and points here. The document states what now says it (health, the backup manifest), and what would actually separate them — an installed substrate — with why that is a packaging project rather than a slice: the CLI resolves workspace packages through a runtime loader, so copying `dist/` is not enough. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…first time ISS-154 and NODE_SUBSTRATE.md both claimed separating the node from the working tree was a packaging project, on the grounds that a runtime loader made copying `dist/` insufficient. That was asserted from a quick read. The operator pushed on it, and the measurement says otherwise: the installable tree 45MB of dist + 127 package.json external runtime deps 15, whole workspace pnpm deploy --prod --legacy exit 0, self-contained, @refarm.dev/* populated That tree almost runs. It fails on one precise thing: Cannot find module '…/@refarm.dev/root/dist/fetch-with-timeout.js' `packages/root` declares `files: ["dist/index.js", "dist/index.d.ts"]` and the built CLI deep-imports a path that list does not ship. A deep import bypasses `exports` and depends entirely on `files` — and 26 workspace packages carry a restrictive `files` list, so each is a latent instance of the same failure, invisible for as long as everything resolves through the workspace. It is the rope the 0.1.0 release already names, measured here as shipped-dist rather than built-dist, and now with a reproduction. Fixing those declarations is the first slice of the install path and the release's own blocker at once. The correction matters more than the finding: "it is a project" ends an inquiry, and this one was ended by an assertion rather than by a measurement. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
MEASURED 2026-08-19 while testing whether this node could run an INSTALLED copy of itself instead of the development working tree. `pnpm deploy --prod --legacy` produced a self-contained tree that almost ran, and failed on one line: Cannot find module '…/@refarm.dev/root/dist/fetch-with-timeout.js' `packages/root` declared `files: ["dist/index.js", "dist/index.d.ts"]`, and `dist/index.js` re-exports from `./fetch-with-timeout.js` — a file the list does not ship. The package resolves everywhere it goes through the workspace and breaks everywhere it does not, which is the worst place to find out. SIX PACKAGES CARRIED IT, and the shape was identical in all of them: `files` ENUMERATED individual paths, so every file added later had to be remembered. `root`, `windmill`, `health`, `cli` and `config` now ship their directory. `health` is the one worth naming: `credential-renewal.js`, `node-substrate.js` and `tool-measurement.js` — all written earlier in this same session — would not have shipped. Three fresh instances of the defect, created without knowing. THE GATE walks the transitive closure from every published entry point and fails on anything reachable but unshipped. It reports EVERY package rather than the first, so a mechanical sweep is one run. This is the rope the 0.1.0 release already names, measured as SHIPPED-dist rather than built-dist, with a reproduction — and it is simultaneously the first step of the install path (ISS-154). One fix, both. NOT TOUCHED: `@refarm.dev/plugin-manifest`, whose `exports` names `./src/index.js` while `files` ships `dist`. It is a protected surface under CLAUDE.md §8 and takes the serialized lock/handoff policy, not a drive-by edit. Verified: the deploy's `cli-main.js` now imports cleanly, where it did not before. 246 files / 3663 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QDEbyw5k3JQadaNRqJQWP9
…s options * records.map(recordToVaultNote) broke when recordToVaultNote grew a second parameter (e863bfe): Array.map passes the index there, and TypeScript rightly refuses number for RecordToVaultNoteOptions * explicit arrow: the develop Test & Quality lane was red on this since 2026-08-28
…dows can build * await import(absolutePath) parses as protocol "d:" on Windows and the ESM loader throws ERR_UNSUPPORTED_ESM_URL_SCHEME — Platform compatibility (windows-2025-vs2026) was red on exactly this since 2026-08-28 * pathToFileURL(...).href, verified on POSIX; the other dynamic imports in apps/refarm use module specifiers, not paths
…SS-112) * measured the consumers before choosing: vault-seed and enem both read contract?.TASK_ARTIFACT_MANIFEST_SCHEMA ?? "refarm.task-artifacts.v1" — they already prefer the package's value and keep the literal only as a fallback; arch-engine emits the package's value verbatim * so the package is canonical (ADR-087): the CI constant, four expected fixtures, three POC producers, their tests and READMEs, and the artifact-contract-v1 proofTarget all say sovereign.task-artifacts.v1 * pinned by a test that reads the package source — the permission-vocab idiom for one vocabulary across two stacks; validation POCs 41/41
* artifact-contract-v1, quality-contract-v1, provenance-contract-v1: zero dependencies, proven together by arch-engine, a Python producer that emits the envelopes and validates them against the real tarballs * the same shape as design-system-ready: selected explicitly instead of weakening the complete consumer-ready gate; install smoke 3/3 pack → install → import; plan test pins the topological order * quality-contract-v1 sits in both units on purpose
* a package can belong to two units; the second lane must be a no-op for what the first shipped, or pnpm publish fails mid-unit and leaves the rest unpublished * npm view <name>@<version> decides; an unknown probe result never skips; the probe is injectable so the rule is tested without a registry
* the state as measured: promote-check BLOCKED only by a red origin/develop that two commits on this branch already fix; wouldPublish empty; the lock (RELEASE_AUTOMATION=false) in place * six operator steps, from pushing develop to re-locking after the publish; what changes downstream afterwards; the posture gap (no required checks on main) named rather than assumed
…fest * the wasm under test is the real agent; only the manifest was a fixture, and its restated capability (ai:respond) outlived the vocabulary — the host elects a default responder by integration:respond alone * once the host stopped guessing a responder (7b11ec0) refarm ask found none, and every code-changing run since 2026-08-27 died at this step after the effort round-trip and the stream smoke had passed; the last green smoke was 046eff1 on 2026-08-24 * capabilities and permissions now come from packages/agent/plugin.json, so the fixture cannot drift from the plugin it wraps
…finds the agent * the CLI reads requested[] and defaultResponder since dddc75c (aligned to the Rust host); this host still answered {installed, local, loaded, known}, so refarm ask reported "No agent is loaded" against a daemon that had loaded it — the agent smoke died there on every code run since 08-27 * the same election rule as the Rust host: the first LOADED plugin whose manifest declares integration:respond; nobody elected is null, never "" * requested rows come from the ~/.refarm/plugins scan, which is what this host is handed; grants stays {} because this host computes none * listInstalledPluginManifests exposes the manifests the route needs
…that entered it * 23 → 25: vault-contract-v1 (cc61342, 2026-08-28) and provenance-contract-v1 (0efcfd4) both entered consumer-ready and this suite kept the old list — the ninth place the 2026-08-28 counts commit did not reach, because only the cold clean-room lane runs it; it surfaced on the first develop → main PR (#59) after a month of warm per-push runs
…E and the source * README.md and src had moved to host-* (34c8ab7, 2026-07-22); the template still said agent-* since 2026-07-02, so mdt check on packages/agent drifted in three blocks (tools, config_fields) — invisible to the weekly schedule on main, which predates the rename, and red on PR #59 * the template follows the rendered truth rather than the README being regenerated backwards; the three blocks now compare byte-equal
…that entered it * provenance-contract-v1 (0efcfd4) and vault-contract-v1 (cc61342) were missing; handoff date and facts follow the 2026-08-30 packet: 25 packages, 80 required checks * the test that pins this list to release policy lives in the quality job, which the push run skipped and the PR run cancelled — it never saw the selection change until PR #59's second round
… the quality job * the hardening collector imports each package's built artifact and its test depends only on its own build, so on a cold graph it ran before most of the workspace existed: 18 conformant against a floor of 20 (24 on a built tree) — a scheduling race, red only in this lane * a forced cold build first; then type-check/test/lint without --force so the fresh cache dir reuses those builds instead of rebuilding them
…not for 400 ms * the test settles a question from another device after the attend command has shown it; it guessed that moment with a fixed sleep, and on a cold, saturated clean-room runner the child had not even fetched the question yet — it reported "nada pendente" instead of losing a race it never entered (PR #59, 2026-08-30; warm runs pass 3/3 either way) * runAttend gains waitForOutput(pattern): resolve when the command printed it, fail loudly with both streams if it never does
* 150 ms killed the child before a cold node on a saturated clean-room runner had written the banner the test preserves, so it claimed the banner was dropped when the process had not reached it (PR #59, 2026-08-30, twice — the first time hidden inside a cancelled run) * 2 s budget, child sleeps 30 s: the timeout is still what ends it; 22/22 three times warm
* ok:false alone sent the cold clean-room lane through a whole round with nothing to read (PR #59, 2026-08-30): the envelope now travels with the assertion as its message
…ere no upstream exists * before-push resolves since: "upstream" by default; on a detached HEAD — every CI checkout of a PR — resolution throws and the plan refused to exist (invalid-agent-finish-since-ref), which is how the repo-contract gate test died in the cold clean-room lane (PR #59, 2026-08-30) with nothing to read until the envelope rode the assertion * a DEFAULT is a preference: the selection now validates the dirty tree and carries sinceFallback {requested, reason, validationScope} so the envelope reads as a fallback, not a choice; an explicit --since upstream still refuses, because the operator named a ref * 65/65 in agent.test.ts, including the detached-HEAD lane case
* a PR's merge ref has a different content signature from the branch head, so the result cache the push run hits (1 min) is a miss on the PR and the job runs cold: 30m18s twice on PR #59 (2026-08-30) against a 30-minute ceiling, reported as "canceled" — a timeout that reads like a concurrency cancel until the clock is read * 45 minutes, the ceiling the other heavy jobs already carry
…ys why * GitHub refuses a job matrix above 256 configurations: the matrix-runner job is never created and the workflow reports failure with nothing to read — every PR run today (8/8) on PR #59, whose 154 changed packages built 496 configurations * above the limit the builder now emits an empty matrix, a ::warning naming the count and the limit, and matrix_overflow=true; the cache finalize no longer records that signature as validated * the limit is GitHub's and is pinned by test
* wait_until_listening (100 × 20 ms) and wait_for_audit_lines (200 × 10 ms) both gave up inside two seconds; under the coverage-instrumented, fully parallel Tractor gate on a saturated PR runner that was not enough, and the audit wait gave up in SILENCE — so the assertion after it blamed the trail for lines still being written (PR #59, 2026-08-30; 40/40 warm) * 30 s deadlines, immediate in the good case; a miss now panics with what it saw instead of handing a half-written trail to the next assertion
…nstead of waiting on it * e2e_the_handshake_records_every_attempt: a refusal has a SHAPE (HTTP 401); a reset or a refused connect used to count as a refusal and send the test to wait 30 s for a trail line no refusal ever wrote — now it says what it actually got * one_resolution_is_the_policy: free_port() releases the port before start() binds it, and with 1522 tests in one process another test can take it in between; the server task is now watched next to the socket, a lost bind is retried on a fresh port, any other early exit is printed * neither failure reproduces locally (cargo test, llvm-cov on the module, llvm-cov on the whole lib); the class is filed in the ledger
…tive tests, measured on PR #59
… signal reaches only itself * a_real_sigterm_resolves_the_wait raised SIGTERM at the shared cargo test process, and every server any other test had waiting on shutdown::wait() at that instant shut down — the WS handshake audit test saw its server vanish between two guesses (ConnectionRefused) on the cold Tractor coverage gate of PR #59; locally the schedules never overlapped * the case is #[ignore] and the test binary relaunches itself for exactly that one test; the parent asserts the child ran it and passed * the same doctrine ws_server states for set_var: process-global effects do not belong in a shared test process (ISS-184 updated with the cause)
…se changesets Both packages are pulled by consumers today (coop-vault overrides them; vault-seed resolves them through vault-contract-v1) but had no README a registry page could show and no changeset to carry a first version. The READMEs describe the primitives behind the published contract without naming a product; the changesets declare the 0.1.0 initial release the consumer-ready lane will carry.
…dy; health holds The consumer-ready lane goes from 25 to 27 packages so the first publish serves every consumer in the ecosystem, not only vault-seed: - @refarm.dev/std and @refarm.dev/node-contract-v1 enter as sdk-primitives (boundary-review, consumerPull proofs). coop-vault overrides them today and vault-seed resolves them through vault-contract-v1; both have no dependencies. - @refarm.dev/plugin-manifest enters as the manifest:v1 contract (schema, validators, fixtures). vault-contract-v1 - in the lane since cc61342 - lists it under dependencies, so the lane was never installable from a registry without it. The documented hold (fixtures, negative trust tests, an authoring example) is met by src/fixtures.js, the 23 rejection cases in src/validate.test.js and the README authoring section. The agent-demo release proof therefore stops holding the manifest; terminal-plugin, toolbox and the host-effects component stay held - the runtime is not published. - @refarm.dev/health leaves consumer-ready (held-until-config-boundary-review): it is a proof-only devDependency downstream and returns together with config. Counts and ordered lists follow the fact: RELEASE_POLICY, the v0.1.0 gate, packages/README, DISTRIBUTION_STATUS, the release-check/first-publish/handoff/ status-doc anchors, the release-engine list (now topological from the engine, surfaces core/plugin/shared) and apps/site site-data (27 packages, 88 checks).
* std, node-contract-v1 and plugin-manifest ship for the first time; health leaves the packet until config passes boundary review * ok, ready, no missing/extra/stale
…e esbuild builds The smoke's consumer lives in tmpdir, outside the repo, so the repo .npmrc (public registry over the corporate proxy in ~/.npmrc) never applied: with the proxy unreachable, `pnpm install` sat on the network for half an hour with no store writes. Copy the .npmrc over when it exists. pnpm 11 also hard-errors on an unreviewed build script (ERR_PNPM_IGNORED_BUILDS): ds-astro -> astro -> esbuild carries one, and the root pnpm-workspace.yaml approves it. The consumer now declares the same allowBuilds, which is what a consumer following the same security line does. Proven: all 27 consumer-ready packages pack -> install -> import cleanly.
…arries * recorded while coordinating the joint 0.1.0 release: only source-web and source-contract-v1 of rcdc5's eight refarm packages are in the 27-package consumer-ready lane; the six others are hand-packed from develop * second wave ordered by downstream dependency, each entry following the same lane-entry checklist the 2026-08-30 recomposition used
* vault-seed (develop 94334c3): 15 receipts emitted by its own emit_refarm_proof_receipts.mjs, result pass * coop-vault (main f7ac4f4): six receipts — local-surface, content-projection, vault-contract-v1, records-contract-v1, channel-policy-v1, ds — after the npm -> pnpm 11 migration and the vendor pin to this packet * enem (main f0b1b4d): ds and source-web, the latter for INEP artifact acquisition rather than authenticated capture * arch-engine (main ff1e89f): the three evidence-contract receipts refreshed against the rematerialized packet (quality-contract-v1 bytes changed)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promote develop → main
Promotion to
mainso theFirst Publish Selectionlane can be dispatched there for theevidence-contracts-readyunit (0.1.0). Merging publishes nothing: every selected package isstill 0.1.0, so the first-publish guard in
release-changesets.ymlreportsblockedandchangeset publishdoes not run.RELEASE_AUTOMATIONisfalse(owner lock) besides.Verified on develop before this PR
Test & Qualitygreen ondevelopat4160f021(run 33289677945, all 12 jobs). Three fixes made it green again after a month of masked red:3f3a15f3reqbench-t3 build,0cd0c661Windows ESM file URL,ce7cce8e+4160f021the agent smoke (its fixture restated a dead capability, and the TS host's/pluginsstill answered the pre-dddc75cb shape, sorefarm asknever found the agent — the TS host now answers in the Rust host's shape)pnpm run release:promote:check→SAFE:sourceGreen: true,wouldPublish: [], divergence clean (develop +1193, main +0),RELEASE_AUTOMATION=falserefarm agent finish --lane before-push→ ok (security audit included)release:readiness:test,validation-pocs:test→ green after ISS-112 (46e76097)release-install-smoke.mjs --selection evidence-contracts-ready→ 3/3 pack → install → importrelease:first-publish:check -- --selection consumer-ready→ publish dry-run okWhat this promotion carries for the release
e8ac2f86evidence-contracts-readyunit (artifact, quality, provenance) — proven by arch-engine9068e193first-publish skips a version already on the registry0efcfd4cprovenance-contract-v1 enters the consumer-ready lane;aaa3b6ccvalidateQualityReportRunbook:
docs/2026-08-30-evidence-contracts-first-publish.md.🤖 Generated with Claude Code
https://claude.ai/code/session_012EUeZjc29Wde2Lzcm2Q3Km