Skip to content

Release v0.1.4 - #164

Open
Kevin-Chen0 wants to merge 63 commits into
mainfrom
release/v0.1.4
Open

Kevin-Chen0 wants to merge 63 commits into
mainfrom
release/v0.1.4

Conversation

@Kevin-Chen0

Copy link
Copy Markdown
Contributor

Release v0.1.4: release/v0.1.4 into main. Every version declaration reads 0.1.4. The cycle opened from the v0.1.3 release commit on main (tag v0.1.3). Pull requests for this cycle target release/v0.1.4, not main. The install pins in the README and docs stay on v0.1.3, the published version, until this cycle's release moves them.

In queue

🤖 Generated with Claude Code

Kevin-Chen0 and others added 30 commits October 13, 2026 00:35
The Fusion liquidity note, the Morpho V2 revert note and the Solana
partial-withdrawal note now ask the operator about the amount instead of
suggesting one (#161), but no test checked that: restoring the old
wording passed every test. Each of the three tests that reaches a note
now asserts its ask-the-operator sentence. Restoring the old wording
fails exactly those three tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
CI asserts the declared version follows the release branch, so the branch
opens with every version declaration at 0.1.4: package.json, the lockfile,
the plugin's package.json and manifests, and the marketplace entry. The
documentation's install pins stay on 0.1.3, the published version, until
this cycle's release pull request moves them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…rding

test: pin the wording of the withdrawal refusals
Opening the 0.1.4 cycle declared version 0.1.4, but llms.txt still named
0.1.3 in its own URL and npx line. It is generated from the package
version, so three guide tests fail on release/v0.1.4 until it is
regenerated. Only those two lines change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
Kamino caps a withdraw leg its reserve cannot pay instead of failing it,
so a simulated withdrawal of every share can succeed while burning only
part of them. Quote, prepare and the connect flow already refuse that
case; earn balance still reported it as "a simulated full withdrawal",
so an exit that would leave shares behind read as a full one.

earn balance now applies the same partial-withdrawal check. When shares
would be left behind, exit.measuredAs is "a capped withdrawal",
exitableNow is what that withdrawal would pay, and the note says a full
withdrawal cannot be prepared now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
The bundles still declared version 0.1.3, so the guide test that checks
the bundle names the llms.txt beside it failed. npm run build changes
that one line in dist/treasury.mjs and its plugin copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
withdrawInstructions gives the last reserve in its order whatever shares
are left, and a vault with no reserve is refused before the loop. So
after the loop nothing is ever left, and the line that pushed the
remainder as an extra leg on the first reserve could not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…it fits

In the capped case, exitableNow was the payout of the full withdrawal,
which prepare refuses, and no withdrawal of that amount had been
simulated, so it could cap again. earn balance now simulates a withdrawal
of just the shares that did burn. It reports that payout as
"a simulated partial withdrawal" only when every one of those shares
burns. Otherwise exitableNow is "unknown" and measuredAs stays
"a capped withdrawal". This matches the EVM path, which reports the
vault's liquid balance only after simulating it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
chore: regenerate llms.txt and the bundles for 0.1.4
fix(solana): earn balance reports a capped exit as capped, not full
refactor(solana): drop the withdraw leg that can no longer be added
Under NODE_ENV=production the Solana client's errors carry a code instead
of a sentence ("Solana error #8100002; Decode this error by running ..."),
so the transport-failure match never saw "HTTP error (429)" and a rate
limit or failing public endpoint reached the operator without the hint
naming TREASURY_RPC_SOLANA.

describeError now writes an HTTP transport error from its code and
context ("HTTP error (<status>): <message>"), which the error carries in
either mode. A 404 still gets no hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
fix(solana): the setup hint reads the error's code, not its sentence
The release sequence ended at verifying both channels and had no step
for opening the next cycle. Bumping the version declarations without
running the build leaves llms.txt and both bundled CLIs naming the old
version, and CI on the new release branch fails from its first commit.

Step 2 now names what the build changes on a version bump, measured on
a bumped tree: plugin/package.json, llms.txt, dist/treasury.mjs and
plugin/dist/treasury.mjs. It also lists the lockfile's version fields,
which the cycle-open commits bump and npm rewrites otherwise. Step 8
cuts the next release branch, bumps, builds and commits it all at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
npm audit reports source-map-js 1.0.0-1.2.1 (GHSA-68fv-2mgg-jv7q, high), a
development dependency of the build tooling. npm audit fix moves the
lockfile to 1.2.2; nothing else changes, and a rebuild leaves the bundled
CLI byte-identical.

esbuild's low-severity advisory (GHSA-g7r4-m6w7-qqqr) remains. It affects
esbuild's development server on Windows, which this project does not run,
and tsup accepts only esbuild 0.27, which has no fixed release. Forcing
0.28 would change the generated bundle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
docs: open the next cycle with the build, not only the bump
chore(deps): source-map-js 1.2.2 in the build tooling
npm audit reports esbuild 0.27.7 under GHSA-g7r4-m6w7-qqqr (low; the
development server on Windows; affected >= 0.27.3, < 0.28.1). tsup, the
latest release of which accepts only esbuild ^0.27.0, and vite (through
vitest) resolve it. No patched 0.27 exists, so an override pins
esbuild@^0.27.0 to 0.27.2, which predates the affected range. tsx keeps
its own esbuild 0.28.2. npm audit reports 0 vulnerabilities.

The bundled CLI changes by two lines: esbuild 0.27.2 does not emit the
`window;` and `needed;` field declarations for WindowTooNarrow's
parameter properties. The constructor assigns both, so an instance has
the same own properties, descriptors and order.

The alternative, overriding to 0.28.2, also clears the audit but changes
the __esm lazy-init helper to cache and re-throw a failed initialiser,
a behaviour change across every lazily initialised module in the CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
… page's lockfile

npm audit on connect-page/ reported 25 findings. Only three packages carry
an advisory of their own: ws 8.x (GHSA-96hv-2xvq-fx4p, GHSA-58qx-3vcg-4xpx),
uuid below 11.1.1 (GHSA-w5hq-g745-h8pq) and decode-uri-component up to
0.4.2 (GHSA-vcc3-ghjq-m6fr). The other 22 are flagged only because they
depend on one of them.

None of the three is in the built page: of the flagged packages, only viem
and x402 code reaches dist/connect-page.js, and neither has an advisory of
its own. Overrides lift the three to ws 8.22.0, uuid 11.1.1 and
decode-uri-component 0.5.0. npm audit now reports 0, and a clean npm ci and
build leave dist/connect-page.js and THIRD_PARTY_NOTICES.connect-page.md
byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
… withdrawals (#155)

`connect plan --legs "deposit <vault> <amount>; withdraw <vault> <amount|all>"` builds and
gate-checks every leg exactly as its own run would, and returns ONE acknowledgement covering
every leg: each leg's amount, chain, vault, receiver and wallet prompts, the totals, each vault
warning once and the disclosures once. Nothing opens. On the operator's yes, the same command
with --ack records it and returns each leg's exact command.

Each leg then runs as `connect deposit|withdraw ... --plan <code> --leg <n>`, through the same
page, gate and receipt checks as a single acknowledgement. A leg is held to the digest it was
acknowledged as (callsDigest / solanaIntentDigest, unchanged), legs run in order, a leg is
marked running before its page opens, and a leg that does not complete ends the plan. A plan
lasts 60 minutes; a reconnect voids it. `withdraw <vault> all` binds the share balance read
when the plan is made. `connect plan --status` reports every leg and its transactions.

The single `--ack` flow is unchanged, and the signing page is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
chore(deps): esbuild 0.27.2 for the build tooling
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…hangelog (#155)

The earn skill now uses `connect plan` when a request moves money in more than
one vault through `connect`: settle and quote every leg, post the plan's one
acknowledgement word for word, record the yes with --ack, then run each leg's
command in order without asking again, and stop at the first leg that does not
complete. Amounts stay the operator's. Without `connect`, the per-vault flow is
unchanged. Three paragraphs are rewrapped to keep the skill under 500 lines.

docs/tools.md documents `connect plan`, its results, the --plan/--leg flags on
`connect deposit` and `connect withdraw`, and the refusals. The command counts
in the README, docs/README.md, docs/blurb.md and docs/install.md now say six
`connect` commands. llms.txt is regenerated, and the v0.1.4 changelog gains an
Added entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
chore(deps): patched ws, uuid and decode-uri-component in the connect page's lockfile
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
… one warning per vault family

- A `withdraw <vault> all` leg is named as the whole position read when the
  plan was made, not with the single acknowledgement's "the amount given
  ... not checked here", which contradicted the plan's own totals line.
- The acknowledgement says the plan lasts 60 minutes, that connecting a
  wallet again cancels it, and how to stop partway.
- With more than one vault, the fee disclosure speaks of these vaults.
- A deposit's and a withdrawal's warning for the same family are one
  warning naming every vault, with the advice on how much to deposit named
  for the deposits only.
- A withdrawal of `all` must be the only leg on its vault so far, and no
  leg on that vault may follow it: an earlier withdrawal from the vault, a
  second `all`, or a later leg on it is refused, as an earlier deposit was.
- Tests for guards that had none: a leg that lands but does not match
  (stopped) ends the plan; a leg whose run throws is not_reported with its
  reason; an unreadable expiry counts as expired; a plan file with a
  malformed leg is no plan; reconnecting the Solana wallet and
  disconnecting void the plan; a Solana leg runs under --plan and is held
  to its computed intent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…leg flags

The bundled CLI's connect surface is pinned exactly. `connect plan`
(--legs, --ack, --status) and the --plan and --leg flags on
`connect deposit` and `connect withdraw` are added to the pin; the check
that no connect command signs, sends or transfers is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
feat(connect): one acknowledgement for a plan of several deposits and withdrawals (#155)
The header documents `--subject name=<install prefix>`, but the parser
only matched `--subject=name=<prefix>`, and it skipped any argument it
did not recognise. A subject given in the documented form was therefore
never run, and the job could pass without checking it.

Both forms are now accepted for --subject and --reference. Any other
argument stops the run with exit code 2 and says which argument it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
… else

The page lock's liveness check, process.kill(pid, 0), sends no signal; it
only asks whether a process exists. Nothing stopped a later change from
using process.kill elsewhere, or with a real signal, to stop a process
this package did not start.

The boundary gate now allows the global process.kill only in
src/connect/session.ts and only with a literal 0 as the signal. It
covers process.kill, process["kill"], and taking kill out of process by
destructuring. A positive control requires that the probe is found, so
a walk that sees nothing cannot pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
Kevin-Chen0 and others added 30 commits October 14, 2026 00:39
…and aliases

The rule added in #176 matched the global process object only when it is
spelled `process`. So `globalThis.process.kill(...)` passed, and so did a
kill through an alias such as `const p = process`. Neither form exists in
the code today.

The rule now recognises process as `process`, `globalThis.process`,
`global.process` or `globalThis["process"]`. A variable that aliases the
global process object is refused, because a kill through it could not be
checked. Other routes to the object, such as passing it to a function or
a reflection call, remain outside the rule; the file header describes why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
After the plan's acknowledgement, `connect plan --run` opens ONE page that
walks every EVM leg in order, instead of one page per leg. The operator
connects once; the wallet still asks for each transaction (approve, then
deposit), with no bundling.

- Each leg is still rebuilt and held to the digest it was acknowledged as,
  admitted by the same gate, and checked from its receipts, one leg at a
  time. A leg that does not complete ends the plan.
- Declined in the wallet and stopped on the page: the leg is stopped,
  nothing sent. A hash the page could not hand over, or a page that ends
  a leg without a decline: not_reported. Stopping between legs ends the
  plan before the next one.
- The page outlives the shell's time limit: near it, no leg starts and the
  run returns in_progress; the next run serves the same page (secret and
  port kept on the plan file), so the operator's open tab carries on.
- Refuses a plan not yet acknowledged, a plan with a Solana leg (those
  legs still run one at a time with --plan/--leg), and a leg that no
  longer rebuilds to what was acknowledged.
- The --ack result now points at --run for an EVM plan.
- runConfirm's per-call accept/verify is extracted as prepareConfirm and
  shared; single confirms behave as before.
- Page: plan view in connect-page/src/plan.tsx, shared helpers in
  shared.tsx; each step shows its contract, token, network and where the
  shares or funds go.

Docs (tools.md, earn skill), CHANGELOG and the CI command pin updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
… that race a receipt read

Page (connect-page/src/plan.tsx):
- A running leg this tab did not start (a reload, or another tab) may
  already have been sent. The page no longer resumes it: it shows the leg
  as "may have been sent" and offers only "End the plan here", which
  needs no wallet and records the leg as not_reported.
- A failure before the wallet is asked (switching chain, estimating,
  losing the agent) is "Couldn't prepare, nothing sent", not a decline.
- The wallet is not asked with under a minute left on the run.
- "Plan expired" never shows while a leg is running.
- One always-mounted status region announces each change; focus moves
  to the way forward after a decline; after a pause between runs the
  page carries on by itself, as it says.
- The calls' chain is checked against the leg's before sending.

Process (src/connect/commands.ts):
- A stop that arrives while a step's receipt is being read no longer
  throws: the step gets a plain refusal, the leg keeps its "may have
  been sent" record, and its transactions take what the read found.
- A leg that was stopped, or started elsewhere, while it was being
  prepared does not start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
Review found that the rule compared each receiver's raw source text with
a list of names, so any wrapper defeated it. (process).kill,
(process as NodeJS.Process).kill, process!.kill and
globalThis?.process.kill all passed.

The rule now works on the syntax tree. It strips wrappers that leave a
value unchanged (parentheses, `as`, `<T>`, `!`, `satisfies`) and reads
`process` either as itself or as a member of globalThis or global, by
`.`, `?.` or `["process"]`. The same check finds the kill member and the
call it feeds, through wrappers around the callee.

The rule is now a function, and a new test runs it on one in-memory
fixture per spelling:
- 17 kill spellings, each refused in another file, refused with signal 9
  in the probe file, and accepted as the probe with signal 0;
- 7 alias and destructuring forms, each refused;
- 3 look-alikes, none refused: a spawned child's kill, and properties
  that only share the name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…heck

fix(solana): a landed withdrawal is checked for what it paid
…liases

test(boundary): the process.kill rule also covers globalThis.process and aliases
Brings in the Solana landed-withdraw check (#154). No conflicts; both
bundles rebuild to the merged bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
… retry

- The status line stays rendered when empty (visually hidden), so the
  first announcement is not missed.
- A running step this tab is not driving is described as "in another
  tab, or reloaded"; ending it says it ends the plan in every tab.
- With under a minute left in the run, the page offers only "Stop the
  plan here" instead of a retry that cannot finish in time.
- The race test pins each transaction's verdict, not only its hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
feat(connect): connect plan --run walks a whole plan on one signing page
The cli-exit job runs on six matrix legs and called the public Base and
Solana endpoints from each, which can be rate-limited. It now reads
CI_RPC_BASE and CI_RPC_SOLANA from repository secrets and passes them to
the CLI as TREASURY_RPC_BASE and TREASURY_RPC_SOLANA. Without them (a
pull request from a fork is not given secrets) it uses the public
endpoints as before. Each URL's token is also masked on its own in the
job log.

Refs #154

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
Fixes from the v0.1.4 release review of `connect plan --run` and the
plan's per-leg runs.

- `--run` keeps its own copy of the plan and records every leg outcome
  there first. The plan file is written only while it still holds that
  plan, so a plan issued or cleared by another command while the page is
  open (`connect plan --legs`, `connect disconnect`, `connect wallet`)
  never takes the old plan's outcome, and the run reports the
  transactions that landed even when the file is gone.
- Once the plan is replaced or cleared, or has expired, a start on the
  page is refused and the run ends at once, with the first pending leg
  recorded as stopped.
- When the page's time limit ends a leg, a receipt still being read can
  no longer overwrite that leg's not_reported.
- A page whose old port is taken moves to another port with a new
  secret, so the old secret is never served by anything else.
- A leg run with `--plan --leg` whose page never opened sent nothing: it
  stays pending and the plan stands, instead of ending as not_reported.
- A leg's transactions keep `alsoMoved`, so an `extra_transfer` names
  where the money went.
- The acknowledgement says how the plan runs: one signing page for EVM
  legs, a page per leg once a Solana leg is in it.
- A leg starts with at least 5 minutes left (was 3): a deposit's chain
  waits alone can take 3.
- The page labels a deposit stopped after its approval landed as
  "Approved · deposit not sent", never "nothing sent".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
- Install pins move to 0.1.4 in README.md, docs/install.md and
  docs/blurb.md, as the release process requires of the release pull
  request; the blurb gets a v0.1.4 section and its release-notes link.
- security-model.md describes a plan: one text for up to 8 legs, its
  60-minute code spent leg by leg, the page secret kept in the plan file
  and served only on its own port, a run that records only on its own
  plan, and how a plan's page leaves each call's confirmation to the
  wallet.
- tools.md: the plan's leg statuses, `args` in `--status`, `alsoMoved`
  on a leg's transactions, the 5-minute leg minimum, what happens when
  another command replaces or clears the plan, a leg that fails before
  its page opens, and Solana's capped `exit` in `earn balance`.
- CHANGELOG: dated; a plan of EVM legs runs on one page and a plan with a
  Solana leg leg by leg; the dependency entries no longer call the built
  bundles unchanged. The plugin changelog gets its v0.1.4 entry.
- install.md: one confirm page per deposit or withdrawal, or one for a
  whole plan.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…val matched

"Approved · deposit not sent" applied to any stopped leg with a
transaction. A deposit whose deposit receipt was never read, or a
withdrawal whose only transaction was never read, then read as nothing
sent when it may have been. A stopped leg whose last transaction is
unverified now reads "May have been sent"; "Approved · deposit not sent"
is kept for a deposit whose last transaction (its approval) matched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
A DNS or connect error from the script's own fetch calls names the host,
and the CLI's redaction already treats a provider's subdomain as part of
the key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
A `--plan --leg` run of the same plan, in another shell, marks its leg
running in the plan file before its page opens. The plan page checked
only its own copy of the plan, so it could start that leg too, and a
stop pressed between legs could overwrite the other run's record. The
page now starts a leg only when it is pending both in its copy and in
the file, and a stop leaves a leg another run holds to that run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
ci(cli-exit): use the repository's own RPC endpoints when provided
fix(connect): a plan's page records only on its own plan
docs: v0.1.4 install pins, and corrections from the release review
- A `--plan --leg` run that fails after its page opened is not_reported,
  never put back to pending: the reset is only for a page that never
  opened.
- By default no leg starts with less than 5 minutes of the run left.
- A plan replaced while a leg's chain reads are under way refuses the
  start, and the leg never runs.

Each test fails when its guard is removed or loosened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
test(connect): pin three plan guards that had no test
Two follow-ups from #154 on the page lock (src/connect/session.ts).

Takeover was check-then-remove, so two runs that judged the same
leftover lock stale at the same instant could both remove it and both
open a page. A run now renames the file aside (atomic) and keeps the
removal only if what it moved is the content it judged stale; anything
else is put back with a hard link, which never replaces a lock another
run has made in between.

Liveness was by process ID alone, so a leftover lock whose ID had been
reused read as live until it expired. The holder now touches its file
every 5 s. A lock marked as beating and untouched for 30 s is given one
more beat to show it is touched (a laptop that slept) and is otherwise
replaced. Locks from earlier versions, which do not beat, are judged by
process ID as before.

claimPageLock is now async (it may wait that one beat).

Refs #154

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
A rename refused by Windows (a handle open without share-delete) is
treated as a held lock rather than surfacing as an error. A put-back
that cannot hard-link (a filesystem without hard links) renames the lock
back instead of deleting it. A suspect lock gets two beats to show it is
touched, since an interval timer can land a beat late after a wake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
fix(connect): replace a crashed run's page lock safely
…ame page (#184)

A plan with a Solana leg used to be refused by --run and run one page per
leg. Now every leg runs on the one plan page.

- Server: prepareConfirmSolana is the Solana confirm core without its page
  (runConfirmSolana unchanged in behaviour). A Solana leg is held to its
  acknowledged digest and gate-checked when it starts, and its transaction
  is built again at the operator's press and verified from what landed.
- Each chain family's connection is required only when the plan has a leg
  on it.
- Page: an EVM-only plan renders as before. A Solana-only plan signs in as a
  single Solana confirmation does. A mixed plan keeps the EVM sign-in and
  connects the same wallet's Solana account at Start, checked against the
  connected Solana account before any leg runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
… an EVM vault (#184)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
- docs/tools.md, docs/security-model.md, the earn skill and both changelogs
  describe Solana legs on the one plan page.
- llms.txt regenerated; dist/ and the plugin copy rebuilt.
- The page-source pin now lists the two Solana imports the plan page adds,
  and pins the only Wallet Standard features it may call: standard:connect
  and solana:signAndSendTransaction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…d-plan wallet check says what already went

- A Solana signature already on record for an earlier leg of the plan is refused as a mismatch, and the plan
  ends. Two identical legs built on the same blockhash produce the same transaction, so the blockhash binding
  alone could let leg 1's transaction complete leg 2 and the plan record twice what moved.
- The page's Solana wallet check runs before every leg while a Solana leg is still to run, so after a reload it
  can fail once earlier legs have sent transactions. The page and the recorded reason now name the leg it
  stopped before and say nothing more was sent, instead of "before the plan started ... nothing was sent".
- The plan tests' stub chain hands out a new blockhash on every read, and two tests pin that a transaction
  built for an earlier leg, or the signature already on it, does not complete a later leg.
- The page-source check on a Solana wallet's features is an allowlist: every `features` in the page's code
  must be one of the two literal lookups, so `features?.[...]`, destructuring and aliasing fail it. Comments
  are not held to it.
- Bundles rebuilt; plugin copy synced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
…nature too; parse comments out of the page check

- `connect deposit|withdraw --plan <code> --leg <n>` on Solana now refuses, as a mismatch, a signature already on
  record for another leg of the same plan, as `connect plan --run` does. A test runs two identical Solana legs leg
  by leg on one blockhash.
- The page-source check strips comments with the TypeScript parser instead of a pattern, so a `//` inside a string
  no longer hides the code after it.
- The plan page's comment on its Solana wallet check says it runs before every leg.
- Bundles rebuilt; plugin copy synced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kevin Chen <13785749+Kevin-Chen0@users.noreply.github.com>
feat(connect): connect plan --run walks a plan's Solana legs on the same page
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