Skip to content

Promote develop → main: evidence-contracts-ready unit, green CI, ISS-112 closed - #59

Open
aretw0 wants to merge 1213 commits into
mainfrom
develop
Open

Promote develop → main: evidence-contracts-ready unit, green CI, ISS-112 closed#59
aretw0 wants to merge 1213 commits into
mainfrom
develop

Conversation

@aretw0

@aretw0 aretw0 commented Aug 30, 2026

Copy link
Copy Markdown
Owner

Promote develop → main

Promotion to main so the First Publish Selection lane can be dispatched there for the
evidence-contracts-ready unit (0.1.0). Merging publishes nothing: every selected package is
still 0.1.0, so the first-publish guard in release-changesets.yml reports blocked and
changeset publish does not run. RELEASE_AUTOMATION is false (owner lock) besides.

Verified on develop before this PR

  • Test & Quality green on develop at 4160f021 (run 33289677945, all 12 jobs). Three fixes made it green again after a month of masked red: 3f3a15f3 reqbench-t3 build, 0cd0c661 Windows ESM file URL, ce7cce8e + 4160f021 the agent smoke (its fixture restated a dead capability, and the TS host's /plugins still answered the pre-dddc75cb shape, so refarm ask never found the agent — the TS host now answers in the Rust host's shape)
  • pnpm run release:promote:checkSAFE: sourceGreen: true, wouldPublish: [], divergence clean (develop +1193, main +0), RELEASE_AUTOMATION=false
  • refarm 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 → import
  • release:first-publish:check -- --selection consumer-ready → publish dry-run ok

What this promotion carries for the release

  • e8ac2f86 evidence-contracts-ready unit (artifact, quality, provenance) — proven by arch-engine
  • 9068e193 first-publish skips a version already on the registry
  • 0efcfd4c provenance-contract-v1 enters the consumer-ready lane; aaa3b6cc validateQualityReport
  • design-system-ready unit and dependency-closure smoke (parallel slice, same day)

Runbook: docs/2026-08-30-evidence-contracts-first-publish.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_012EUeZjc29Wde2Lzcm2Q3Km

Arthur Silva and others added 30 commits August 18, 2026 12:17
… 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
aretw0 added 30 commits August 29, 2026 22:10
…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
… 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant