Conversation
…landing page + restores main⊆dev) RELEASE.md step 3. gudnuf-authorized (release-sequence).
…ring (#260) `main` rejects direct pushes, so the dev -> main cut is a PR. Recorded with the merge mode it requires: --squash gives `main` a commit that exists nowhere in `dev` history and --rebase does the same for every commit it moves, so either one breaks the ancestor relation the following step exists to maintain. Also states that tagging still works as a direct push -- the ruleset targets branches, not tags -- because a reader who has just been told `main` rejects pushes has every reason to doubt the tag step. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…m dev (#236) `workflow_dispatch` is only offered for a workflow present on the default branch, and a `v*` tag runs the copy of the workflow in the tagged commit. A tag cut from a `main` that predates `release.yml` therefore starts no run at all — no output, no failure, nothing in the Actions tab. That is the failure mode worth naming: an absent run is indistinguishable from one nobody noticed. Both cases have the same fix, which the numbered steps already prescribe; the trap is only reachable by tagging or dispatching without merging `dev` into `main` first, most easily by cutting a release from a `main` merged minutes before a workflow change landed on `dev`. Records the one-line check that distinguishes "the trigger misfired" from "the workflow was not in that tree". Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…#204) * crossmint: a smoke script that runs, and states what it cannot prove The smoke script's command surface had never been executed. Against the real binary every invocation returns rc=1: `budget`, `config` and `job` are not commands at all; `--home` is a post-subcommand flag on `wallet` alone; `wallet balances` is `balance` and has no `--json`; `buyer restart` does not exist; and the binary is not at ./target (CARGO_TARGET_DIR is redirected). A script in that state does not fail at step 0 — it fails partway, and on the real-money path that means after the melt. The script now targets the surface the binary has, and is staged so the external unknowns are measured before our own code is involved: --self-test parsers against fixtures. The `status=needs_payment` branch only ever appears on a non-test mint, so fixtures are the only coverage available before real sats. --dry-run testnut: gates, home discipline, env config, balance math, and that a melt which cannot route returns non-zero — the property every `|| die` guarding a real melt depends on. --stage-a the hop by hand in three wallet commands. Answers what no hermetic test can: does LN route source -> target, and what does it cost. Ends in a fee-fit verdict against the cap. --stage-b refuses, naming what it still needs, rather than pretending. Safety is structural rather than procedural. Config is supplied entirely by environment, and env overrides are never persisted: a home booted with MOBEE_ALLOW_REAL_MINTS=true still reads `allow_real_mints = false` on disk, so the real-mint fence is process-scoped and cannot be left open. Bootstrap does write into MOBEE_HOME, and an unset MOBEE_HOME resolves to ~/.mobee, so every invocation is funnelled through one wrapper that refuses any directory this run did not itself create and mark. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * crossmint: fund the probe wallet, and shape-check what gets melted Stage A created a fresh empty home and then asserted a source balance, so the first real run would have died at "buyer holds 0 sats" — the funding step was missing entirely. Paying the invoice happens out of band and by a human, so funding is two invocations (`--fund` prints the bolt11, `--fund-complete` issues once it is paid) sharing one throwaway wallet. That means a home has to be reusable across invocations: `ensure_home` reuses one carrying our marker and still refuses any directory this tooling did not create. The authorization token now names the TOTAL FUNDED EXPOSURE rather than each step's amount — one human decision about how many real sats are at risk, which is the shape the spend was approved in. A mint that auto-funds prints `minted_sats=...` where a real mint prints a bolt11. Both are non-empty, so an emptiness check would feed that line straight into `wallet melt`; `is_bolt11` checks the shape instead, and the self-test pins both directions. Verified: self-test 11/11, dry-run rc=0, and --fund / --fund-complete / --stage-a / --stage-b each rc=1 without matching auth, creating no home before the gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * crossmint: stop truncating quote ids at the underscore Live Stage A hit this twice. `parse_quote_id` matched [A-Za-z0-9-], which has no underscore, so a real quote id truncated at the first one: lcLc0JwHHCIG_UIwQ8cvPB-LXgiVfSxE6ZO6Tq1b became lcLc0JwHHCIG. The result is still shaped like an id — it is a PREFIX of the real one — so nothing failed at the parse. It failed two steps later, at mint-complete, with "no stored amount". Both times the full id recovered it; no sats lost. Match to whitespace instead. Quote ids are opaque: testnut issues UUIDs, the real mints issue base64url. Never enumerate the characters of an identifier you do not define. The self-test is why this shipped. Its fixture was a UUID — copied from testnut, the mint the dry run CAN reach — so a fixture standing in for the unreachable real format inherited the format of the reachable one, and proved the parser against a character set that could not fail. Both shapes are pinned now, the base64url one verbatim from the live run, and the old class is confirmed red against it (12-char prefix of a 40-char id). `--amount` is now passed on both mint-complete calls: a quote raised by a different process leaves no stored amount in this home, which is the error the live recovery actually hit. The strand message prints the exact recovery command with the full id and the env fence, and says to copy the id from that line rather than from earlier output. Verified: self-test 13/13, dry-run rc=0, refusal gates rc=1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * crossmint: name the real cause of "no stored amount" The previous commit explained the live failure with a mechanism I made up: that a quote raised by a different process leaves no stored amount in this home. The run data refutes it — leg-1 recovery used the full id with NO --amount and succeeded, so the quote was stored all along under its full id. The source agrees. `complete_mint_by_id` only reaches "has no stored amount; pass --amount" when the quote lookup returns None. The message means THE ID WAS NOT FOUND; the code then guesses you need --amount, having no way to know the id is a truncated prefix. An error's suggested remedy is a hypothesis, not a diagnosis — and I took the remedy as the cause, which is inference wearing the clothes of a record. The character class is the whole cure. --amount stays as a cross-check, which is what it actually is: when the quote IS found, mint-complete refuses a completion whose amount differs from the stored one, so passing it pins that we are completing the quote we raised. Comments at both call sites now say that rather than claiming to fix anything. Verified: self-test 13/13, dry-run rc=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: worker:mobee-crossmint <orveth@agi.cash> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* rename: the application ships as `maxplayer`, buyers are racers
The binary, the npm packages, the release assets and the CLI's own output all
carry the product name; the crates, the state directory and the internal
identifiers do not.
`[[bin]] name = "maxplayer"` is what separates the two. Without it Cargo derives
the executable name from the package, which is why the application rename looked
like it forced a crate rename. It does not, and the crates stay `mobee`
deliberately: nothing publishes to crates.io, so those names are invisible to
users while renaming them would touch most of the workspace.
npm goes to the ruled layout -- the launcher unscoped as `maxplayer`, payloads as
`@maxplayerai/linux-*`. Directories under npm/ keep their paths: seven sites
hardcode `npm/mobee`, and nothing derives from the directory name, so moving it
would be churn with a failure mode.
package-release-asset.sh takes the artifact name as an argument rather than
hardcoding the new one. The release matrix is about to ship a second binary from
the same script, and hardcoding here would mean un-hardcoding it there.
Two renames are deliberately NOT made, both because they would change behaviour
rather than naming:
* the `buyer` and `sell` subcommands keep their tokens. `sell` cannot become
`run` because `run` already exists as a different command, and `buyer` pairs
with a self-spawn argument in daemon.rs by string -- a mismatch compiles
cleanly and fails at runtime, in the money-path daemon.
* MOBEE_HOME and ~/.mobee are untouched. That directory holds the key, the
wallet and the spend ledger; pointing it somewhere new is a migration with
its own proof obligations, not a rename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* rename: follow the binary rename into the nix and artifact-verifier paths
The `[[bin]]` change renames the built executable, so every path that names it
had to follow. Six were missed, and `nix run` was the shipped regression: the
flake's app pointed at `/bin/mobee`, which no longer exists.
flake.nix meta.mainProgram, apps.default.program
scripts/verify-static-artifact.sh default binary path, staged name, container invocation
scripts/verify-arm64-artifact.sh the same three, plus the `^mobee <semver>` assertion
scripts/verify-release-version.sh usage example
docs/PACKAGING-SCOPE.md invocation example
docs/SELLER-QUICKSTART.md MOBEE_BIN path
.gitignore comment naming the default
`-p mobee` in cargoBuildFlags stays: that selects the package, not the binary.
The two artifact verifiers had a second coupling behind the first. Each stages
the binary under a fixed name and then invokes that name inside a container, so
renaming the staged file without the invocation would have failed at runtime
rather than at build. The arm64 verifier also greps the version output for
`^mobee`, which the renamed binary no longer prints.
Evaluated rather than assumed: `nix eval` on apps.default.program now returns
`.../bin/maxplayer`, and meta.mainProgram returns `maxplayer`. The store path
stays `mobee-0.1.0` because that is the package name, which is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* rename: the last `npx mobee` — a track heading naming an install path that will not exist
Track B in the packaging scope is the npm install path, and its heading still named the command
`npx mobee`. That package was never published, and after this PR the launcher is `maxplayer`, so the
heading named an install path that cannot work in either direction.
Found by re-running the acceptance predicate after the rebase rather than by re-reading it: the
`npx mobee` clause came back 1, not 0, against a PR body asserting the docs no longer advertise it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…efusals (#269) A Send(TokenCreated) saga with no confirmed outgoing tx wedges the wallet permanently, and the refusal is wallet-wide rather than job-scoped: one stuck saga blocks every subsequent outbound payment. The Swap family already resolved this shape on mint truth; the Send family had no equivalent. resolve_one_send_saga resolves exactly one case, the send that demonstrably completed: reserved inputs all SPENT at the mint means the send executed, so record the inputs Spent and drop the saga. Everything else refuses fail-closed. all-UNSPENT refuses here where the Swap path rolls back, because a Send has a counterparty. All-unspent proves only that the token is unclaimed, which is indistinguishable from delivered-and-awaiting-redemption, and the token is bearer so it names no payee. Rolling back would queue a payee's money for re-spend. Same mint answer, weaker strength; classify_reserved_inputs is shared, the interpretation is per-family. The Send completion path deliberately does not call Wallet::restore. A Send's outputs are the token, and restore re-derives from this wallet's own seed, so restoring would pull an outstanding token's proofs back into our balance and silently reclaim money already handed over. Both resolver call sites propagated refusals with `?`, so a single unresolvable saga aborted the whole pass and left resolvable ones stuck, with progress depending on saga iteration order. Refusals are now recorded per-saga in RetireReport.unresolved and the pass continues. Applied at both call sites, since the Swap one carries the same defect. Fail-closed is unchanged and asserted: recover_unmapped_sagas still refuses over survivors, so the pay path stays blocked. Clearing an already-stranded row is out of scope. Deleting a proof-less saga touches no proof, so it is safe with respect to money and not with respect to the record: the row is the only store of SendOperationData.token, so an automatic drop would destroy a possibly-claimable bearer token with no chance to capture it first. The NUT-07 test fake returned every configured state regardless of which ys were requested. Since classify_reserved_inputs requires the response Y-set to equal the requested set, that refuses any wallet holding more than one saga, so a per-saga isolation test would have reported green with neither saga resolving. It now filters by requested ys. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The v1 cut. Six commits from
dev, headlined by the surface rename.maxplayer, buyers are racers #262 — the application ships asmaxplayer, buyers are racers, sellers are runners. Crates staymobee(package name, correctly unchanged); the binary is a[[bin]], and the flake'smainProgram/apps.*.programfollow.--mergeis load-bearing #260 — release docs: both triggers read the workflow from a commit rather than fromdev, and the cut is a pull request with--mergeload-bearing.main ⊆ dev.Merge with
--merge, never squashgh pr merge --merge. Squashing this collapses six reviewed commits into one and breaksmain ⊆ devby content as well as ancestry — the next back-merge then re-proposes changes that are already
present. This is the process documented in #260.
What this does NOT do
Nothing publishes on this merge, and I re-proved it against
dev's ownrelease.ymlrather thaninheriting the earlier proof. Two independent legs:
on:ispush: tags: ['v*']plusworkflow_dispatch— there is no branch trigger, so a pushto
maindoes not start the release workflow at all.Create the release, the npm publish) are gatedif: github.event_name == 'push' && github.ref_type == 'tag'.The gate is push and tag. Merging this is not a release.
Ordering after this lands
The
v0.1.0tag is not next and is gated on #271 — the acp cancel currently takesTest mobee-core (money-path suite)down as skipped, so the money-path gate is absent repo-widerather than failing, and a skipped row looks exactly like a passing one from a distance. Tagging
before it lands would ship money code on a tree whose money-path suite never ran.
Sequence: this cut → dry-run #5 (rename only) → runner artifact +
verify-runner-surface(kind-awareasset names; the verifier must invoke the binary, never read
--help) → dry-run #5b → #125 curlinstaller against the runner artifact → #249 darwin-arm64 →
v0.1.0plain, no-rc(
release.ymltreats any hyphen as a prerelease and would publish under--tag rconly, leavinglatestunresolvable for a first release) → clean-machine verify of both install paths → siteupdate last, so the site never advertises an install that 404s.
Back-merge owed
maincarries exactly one commitdevlacks —c94ece6(#270, mobile responsive). A back-mergeafter this cut restores
main ⊆ dev. Verified clean: a trial 3-way merge ofdevintomainreportsno conflicts, because #270 touches the header nav hunks and
dev'sindex.htmlchanges are themobee→maxplayeraiURL and command renames elsewhere in the file.Standing CI note
Build (acp) + money-path testsis red ondevandmainboth, independent of any PR: builds pass,then
Test mobee-core (acp)is cancelled at the ~2s mark and money-path is skipped. That is#271, not this cut. Judge this by the other rows.