Skip to content

Agent hardening: verify-by-retrieval download, truthful agent surfaces, keystore UX#30

Merged
nijoe1 merged 29 commits into
mainfrom
agent-hardening
Jul 23, 2026
Merged

Agent hardening: verify-by-retrieval download, truthful agent surfaces, keystore UX#30
nijoe1 merged 29 commits into
mainfrom
agent-hardening

Conversation

@nijoe1

@nijoe1 nijoe1 commented Jul 21, 2026

Copy link
Copy Markdown
Member

Hardens every surface an agent (or human) touches, driven by a 609-invocation live smoke campaign on Calibration and a keystore field test. Ten commits, grouped by concern.

What's changed

New capability

  • download <pieceCid> — retrieval as verification: the SDK validates received bytes against the piece CID, so a successful download is the storage proof; no separate verify command. Error taxonomy separates what retrying can fix (DOWNLOAD_FAILED, retryable) from what it can't (INTEGRITY_MISMATCH, PROVIDER_NOT_FOUND, WRITE_FAILED). Addresses Add download/verify commands for file retrieval and verification #7 — acceptance decision below.
  • docs --deep — full-sitemap search (~1,800 pages incl. SDK API reference), auto-invoked when the curated index has no matches, failing loudly if the sitemap is unreachable.

Hardening

  • docs is now a docs fetcher, not an HTTP client: --url restricted to docs.filecoin.cloud, redirects refused, .md-mirror normalization, HTML backstop on both fetch paths, attributed User-Agent.
  • Keystore extraction uses execFileSync argv arrays (no shell interpolation) with ~ expansion; wallet init --keystore rejects directories and non-keystore JSON (KEYSTORE_INVALID) instead of "You're all set!".
  • Uploads stream (Readable.toWeb(createReadStream)) — flat memory at any file size; multi-upload keeps its all-or-nothing readability gate.

Fixes

  • wallet costs no longer dies on "No endorsed provider available": costs are computed per existing dataset (one createContext each — the plural API rejects same-provider datasets), falling back to default selection when the wallet has none.
  • wallet balance on a brand-new address returns ADDRESS_NOT_ON_CHAIN with a wallet fund CTA instead of a raw viem multicall dump.
  • Keystore failures decode themselves (ENOENT → install Foundry; Mac Mismatch → wrong password; no tty → use a private-key wallet).

Agent surfaces

  • --schema now tells the truth: every command's declared output includes the processLog/cta envelope real responses carry (previously violated its own additionalProperties: false).
  • Every command carries MCP annotations (title, readOnlyHint/destructiveHint) and consequence-stating descriptions, since MCP consumers can't run -h/--schema.
  • incur bumped to 0.4.19, fixing the doubled group prefix in --llms.
  • Paginated lists gain fetch-all CTAs alongside next-page.

Docs & skills (addresses #1 #2 #3 #4 #5 — acceptance decision below)

  • Both SKILL.md files open with the self-discovery rule (<cmd> -h + <cmd> --schema --format json before first use) and document flag syntax truthfully (camelCase/kebab both parse; booleans are presence-only switches; --flag=false is the explicit form).
  • Keystore documented as interactive-CLI-only everywhere it matters; MCP requires a private-key wallet.
  • Three new references: keystore-setup (with creation recipe), mainnet-funding, troubleshooting (full error-code catalog with retry semantics).
  • New skills-consistency test pins skill frontmatter version/license and every foc-cli@x.y.z doc pin to cli/package.json.
  • README rewritten: badges, one-table command map, quick start ending in a verified download round-trip.

Removed (breaking)

  • dataset uploadupload already auto-creates datasets; the low-level duplicate had a worse interface.

Commit → issue traceability

Every commit references the issue it addresses; merging auto-closes only #23#29 (evidence-based bug reports fully fixed here). #1#5 and #7 are docs-redirect deliveries — closed manually only if the owners accept that scope (see pre-merge task 5).

Commit Closes
chore(deps) bump synapse-sdk 1.1.0, incur 0.4.19 #23 (--llms prefix doubling)
refactor(upload) streaming + drop dataset upload #24 (memory buffering + duplicate command)
feat(download) verify-by-retrieval #7 (download/verify commands)
feat(docs) host allowlist + deep search #25 (SSRF surface, HTML soup, unsearchable API ref)
fix(costs) per-dataset contexts #26 (endorsed-provider failure, undercounting)
fix(wallet) keystore validation + first-run UX #27 (directory accepted, raw internals)
feat(agent) truthful schemas + MCP annotations #28 (schema contradiction, missing annotations)
test: coverage for all of the above — (rides the feature issues)
docs(skills) self-discovery, keystore/funding guides #1 #2 #3 #4 #5 (dApp/agent guidance set)
docs(readme) compact rewrite #29 (README drift)

Deliberately not addressed here: #6 (create-app scaffold).

Verification

bun test 78/78 (5 new suites' worth of cases), bun run lint (tsc + biome) and bun run build clean at head. Smoke evidence in the (local-only) docs/smoke-report-2026-07-21.md.

Note: commits are grouped by concern for reviewability; intermediate commits are not individually test-green (tests land together in the test: commit). Merge with a merge commit or squash — don't cherry-pick individual commits.


Tasks before merging

  1. Version bump decision. dataset upload removal is a breaking CLI-surface change → bump 0.1.10.2.0 (pre-1.0 semver). Must change together (the skills-consistency test enforces it): cli/package.json, both SKILL.md version: frontmatter, and the foc-cli@0.1.1 pin examples in README + skills.
  2. ClawHub: deferred by decision (resolved). Publishing to ClawHub is deferred indefinitely; the skills stay ClawHub-compatible (openclaw frontmatter + npm keywords), but the README no longer advertises a ClawHub install. No review needed on this front.
  3. incur upstream issues (optional, public action — needs owner sign-off). Still open in 0.4.19: example rendering emits --flag true for booleans (misleading — value parses as a positional), acronym kebab-casing (--with-c-d-n), --schema prints TOON unless --format json. Worth filing on wevm/incur; local docs work around all three.
  4. CI green on the PR (same three gates as run locally).
  5. Issue closures. Nothing auto-closes for No SDK Integration Guidance #1Include cost estimation in recommended upload workflow #5 and Add download/verify commands for file retrieval and verification #7 — they are docs-redirect deliveries: skim each and close manually only if the delivered docs/features satisfy the ask (Add create-app scaffold command for dApp projects #6 create-app scaffold intentionally not addressed). --llms on command groups doubles the group prefix #23README drifted from the CLI surface #29 are fully fixed here and are closed at merge.
  6. Optional: run /code-review ultra on the branch for a multi-agent review pass before requesting human review.

What needs review (focus areas)

  • Security: cli/src/commands/docs.ts fetch surface — resolveDocsUrl/normalizeDocsPath, redirect refusal, traversal rejection (three SSRF variants were smoke-tested, but this is the code that holds the line). cli/src/client.ts keystore exec path.
  • Money paths: wallet/costs.ts per-dataset context building (live-verified: 1 GiB/mo → 0.1322 USDFC on Calibration, up from the under-counting 0.1058); upload prepare() now sizes from stat instead of the read buffer.
  • Breaking change sign-off: dataset upload removal (and whether any downstream scripts depend on it).
  • Schema contract: commandOutput() widens every command's declared output with optional processLog/cta — anyone validating responses against the old schemas sees new optional fields.
  • Known accepted gap: multi-upload has a benign check-then-stream TOCTOU (file deleted mid-batch fails later with a less specific error).
  • External-facing prose: skills ship to skills.sh — the two SKILL.md files and three references deserve a native-speaker read.

nijoe1 added 10 commits July 21, 2026 17:43
incur 0.4.19 fixes the doubled group prefix in --llms output
("foc-cli piece piece list").

Closes #23.
Files now stream to providers via Readable.toWeb(createReadStream())
instead of being buffered whole; only stat sizes are read up front, so
peak memory stays flat for any file size. multi-upload keeps its
all-or-nothing gate by checking readability before streaming.

BREAKING: dataset upload is removed — upload already creates a dataset
automatically, and the low-level path duplicated it with a worse
interface.

Closes #24.
storage.download() validates the received bytes against the piece CID,
so a successful download is itself the retrievability + integrity proof
— no separate verify command needed. Error taxonomy separates what
retrying can fix: INTEGRITY_MISMATCH (source served wrong bytes, never
retryable), PROVIDER_NOT_FOUND, WRITE_FAILED (local, retrieval
succeeded) vs DOWNLOAD_FAILED (transient, flagged retryable).

Closes #7.
--url now only accepts docs.filecoin.cloud pages (full URL or bare docs
path); anything else fails INVALID_DOCS_URL before any request, and
redirects are refused so the restriction holds end-to-end. Pretty paths
are rewritten to their .md mirror and an HTML backstop refuses to hand
agents sidebar markup on both the --url and auto-fetch paths. --deep
searches the full ~1,800-page sitemap (SDK API reference, changelogs),
also used automatically when the curated index has no matches — a
failed sitemap fetch fails loudly instead of masquerading as "no
matches". Requests carry an identifying User-Agent with the configured
source tag. Includes the MCP read-only annotation for the tool.

Closes #25.
prepare() without a context falls back to smart provider selection,
which pings endorsed providers and failed with "No endorsed provider
available". Build contexts from the user's own live, managed,
non-terminating datasets instead — one createContext() call per
dataset, because the plural createContexts() rejects datasets sharing a
provider and each dataset has its own rail and lockup to cost. Empty
dataset list falls back to default selection. Also carries the
command's MCP read-only annotation and truthful output schema.

Closes #26.
Keystore extraction now uses execFileSync with an argv array (the path
is data, never shell syntax) and expands ~ itself so MCP/config paths
work without a shell. wallet init --keystore validates it points at an
encrypted keystore file — directories and non-keystore JSON fail as
KEYSTORE_INVALID at init instead of surfacing later as a raw decrypt
error. Use-time failures now decode themselves: ENOENT means cast is
not installed, "Mac Mismatch" means wrong password, and no tty means
keystore mode cannot run under MCP/CI. wallet balance on a brand-new
address returns ADDRESS_NOT_ON_CHAIN with a wallet fund CTA instead of
the raw viem multicall dump — it is the first command a new user runs.
Includes the two commands' MCP annotations and truthful output schemas.

Closes #27.
Every command's declared output now goes through commandOutput(), which
adds the processLog step trail and optional cta block that agent-mode
responses actually carry — a bare z.object emits
additionalProperties:false, which every real response violated, so
--schema lied to anyone validating against it.

MCP consumers cannot run -h or --schema, so the tool definitions now
compensate: every command carries mcp.annotations (human title,
readOnlyHint on reads, explicit destructiveHint on dataset
terminate / piece remove) and descriptions state consequences —
uploads commit USDFC onchain, terminate is irreversible. The
download, docs, costs, balance, and wallet init annotations ride
their own commits; this one completes the set.

Paginated lists (piece list, dataset details) also gain a fetch-all CTA
alongside next-page, and the interactive spinner no longer blanks step
labels or leaks orphan glyphs when info/success interleave with steps.

Closes #28.
New coverage: download success/taxonomy (integrity mismatch, provider
not found, local write failure, default output path), docs URL
restriction + .md normalization + HTML backstop + deep sitemap search +
User-Agent attribution, per-dataset costs contexts, keystore init
validation, and the new-address balance error. skills-consistency pins
both skills' frontmatter version/license and every foc-cli@x.y.z doc
pin to cli/package.json so releases cannot drift from the skills.
Both skills now open command guidance with the self-discovery rule (run
<cmd> -h and <cmd> --schema --format json once before first use) and
document flag syntax truthfully: camelCase and kebab spellings both
parse, booleans are presence-only switches (--flag true leaks the value
into positionals; --flag=false is the explicit form).

Keystore mode is documented as interactive-CLI-only everywhere it
matters — the password prompt reads the tty at use time, so MCP/CI need
a private-key wallet; no password-in-config option will exist. Three
new references: keystore-setup (creation recipe included — cast wallet
new needs the directory to exist), mainnet-funding, and a
troubleshooting catalog of every error code with retry semantics.
Frontmatter gains version/license (CI-pinned to package.json) and
openclaw install metadata.

Closes #1. Closes #2. Closes #3. Closes #4. Closes #5.
Badges, one-table command map, quick start that walks
init->fund->costs->deposit->upload->download (download doubles as the
storage proof), wallet/key-safety and chain/funding sections that link
the skill references. ClawHub install lines assume the skills are
published there — publish first or drop that line before release.

Closes #29.
@nijoe1
nijoe1 requested a review from snissn July 21, 2026 17:40
nijoe1 added 2 commits July 21, 2026 20:45
Keep a Changelog format, reconstructed from git history and npm publish
records: 0.0.4 (initial), 0.1.0 (SDK upgrade + CI), 0.1.1 (Synapse v1
migration + CLI hardening — published from the hardening branch tip,
verified via the npm gitHead), and the Unreleased agent-hardening set
with its breaking dataset-upload removal flagged for a 0.2.0 bump.
The npm description now names the whole surface — CLI, MCP server, and
agent skills — and the CLI root description reads it from package.json
so the two can never drift. Keywords gain
ai-agent/agent-skills/openclaw/clawhub/cli — the skills already ship
openclaw install metadata in their frontmatter, so ClawHub
compatibility is a present fact even before registry publication.
@snissn

snissn commented Jul 21, 2026

Copy link
Copy Markdown
Member

Preliminary findings before a deep review pass:

My read: PR #30 needs a risk-first, contract-first review. It combines security hardening, money-path changes, filesystem behavior, agent/MCP contracts, breaking CLI changes, and substantial documentation.

Current state:

  • Exact head: eec371df6c469991fd935e7721d65f1aef7713f0
  • 36 files, +1,975/−468
  • GitHub reports it mergeable; exact-head CI passes
  • No human or bot reviews yet
  • No repository-specific review policy was found
  • I made no changes and posted no review comments

Recommended review lanes

  1. Issue acceptance and scope

    Confirm the implementation actually satisfies each issue it claims to close. The biggest questions are No SDK Integration Guidance #1, Document chain configuration and environment setup for dApp projects #3, and Include cost estimation in recommended upload workflow #5: those issues request concrete dApp integration, environment, wallet, and SDK cost guidance, while the PR often redirects readers to live docs instead. Also confirm that retrieval-by-CID sufficiently resolves Add download/verify commands for file retrieval and verification #7’s requested verification experience without a separate dataset-level verify command.

  2. Release and compatibility

    dataset upload is removed, but the package remains 0.1.1; the PR itself says this requires 0.2.0. We should also check downstream command compatibility, npm packaging, changelog accuracy, version-pinned skills, and the currently unpublished ClawHub installation instructions.

  3. Security and local-data safety

    Review every URL provenance path in docs.ts, not only user-provided --url: curated-index links, sitemap-index shards, sitemap pages, redirects, protocols, ports, and HTML fallback behavior.

    Separately inspect keystore execution and secret handling, plus download path behavior. The download command explicitly overwrites existing files but advertises destructiveHint: false, which deserves immediate scrutiny.

  4. Storage and money-path correctness

    The streaming conversion needs review around regular-file validation, stream errors, file changes between stat and reading, and whether transactions can execute before discovering an unreadable directory or special file. The same applies to multi-upload’s partial-store and partial-commit states.

    For wallet costs, verify SDK context semantics, active-dataset filtering, same-provider datasets, runway arithmetic, and empty-wallet fallback. For balance handling, check whether substring matching "actor not found" can misclassify another RPC failure and whether its CTA is correct on mainnet.

  5. Agent/MCP contracts

    Verify generated schemas against actual success and failure envelopes, not just their TypeScript declarations. Inventory all MCP annotations and challenge readOnlyHint, destructiveHint, and idempotentHint command by command. Error codes, retryability, CTAs, command names, and option shapes should agree at runtime.

  6. Tests and evidence

    CI is green, but most coverage is mocked. The deep pass should include:

    • exact-head unit/lint/build checks in an isolated worktree;
    • CLI help, --schema --format json, --llms, and MCP manifest smoke tests;
    • adversarial filesystem cases: directories, missing parents, overwrite, permissions, stream failure;
    • URL allowlist and redirect matrices covering every fetch path;
    • schema-versus-runtime validation;
    • npm package inspection;
    • a bounded memory/RSS check for the “flat memory at any file size” claim.

    The stated 609-invocation smoke report is local-only, so its evidence is presently unauditable from the PR.

  7. Claim and prose accuracy

    “Successful retrieval verifies bytes against a CID” is defensible. Calling it cryptographic “proof the file is stored” may overstate what a point-in-time retrieval establishes compared with PDP’s ongoing proof semantics. Funding, wallet, retry, and destructive-operation language also warrants a native-user and agent-safety read.

Preliminary pressure points

These are leads, not yet formal findings:

I recommend beginning the deep pass with lanes 1–4, then validating agent contracts and packaging. Any live-chain work should remain read-only; uploads, deposits, funding, or other transactions would require separate authorization.

@snissn snissn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is green and the change set is well organized, but I found several correctness and safety issues that should be fixed before merge:

  1. wallet costs prices extraBytes against every active dataset rather than the contexts/copy count the next upload will use, so the estimate depends on historical dataset count and is wrong for common 1- or 3+-dataset wallets.
  2. The docs-host boundary is bypassed by URLs parsed from llms.txt; the auto-fetch path does not revalidate them.
  3. Both streaming upload paths accept non-regular files during preflight and can execute preparation/funding before a directory, FIFO, or device fails or blocks during streaming.
  4. wallet init --auto does not reliably replace the configured wallet, and MCP can still configure an interactive-only keystore.
  5. download can overwrite arbitrary existing files while advertising destructiveHint: false.
  6. The new actor-not-found CTA directs mainnet users to the Calibration-only faucet.

Please also complete the pre-merge gates already called out in the PR body: make the breaking dataset upload removal a consistent 0.2.0 release, and resolve the ClawHub publication/install-row gate.

The new happy-path and error-envelope tests are useful; the inline notes identify the missing regression cases.

Comment thread cli/src/commands/wallet/costs.ts
Comment thread cli/src/commands/docs.ts
Comment thread cli/src/commands/upload.ts
Comment thread cli/src/commands/multi-upload.ts
Comment thread cli/src/commands/wallet/init.ts
Comment thread cli/src/commands/download.ts
Comment thread cli/src/commands/wallet/balance.ts Outdated

@snissn snissn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up deep-pass findings beyond the seven blockers already submitted:

  1. The high-level upload's explicit --withCDN path violates the pinned SDK option contract and fails before storage.
  2. wallet init is the only executable command without an output schema, so both CLI schema discovery and MCP tool details omit its result contract.
  3. Keystore validation still checks only for directories, allowing special files such as FIFOs to reach a synchronous read and potentially block the CLI/MCP process.

These were verified against exact head eec371df6c469991fd935e7721d65f1aef7713f0. The inline comments include the required regression coverage.

Comment thread cli/src/commands/upload.ts
Comment thread cli/src/commands/wallet/init.ts
Comment thread cli/src/commands/wallet/init.ts Outdated
nijoe1 added 14 commits July 23, 2026 15:04
Publishing the skills to ClawHub is deferred by decision, so the README
no longer advertises a ClawHub install or links the registry. The
skills remain ClawHub-compatible — openclaw frontmatter and the
openclaw/clawhub npm keywords stay.
stat() succeeds for directories, FIFOs, sockets, and devices, so both
upload paths could execute the funding transaction before the stream
failed or blocked in store(). Gate on Stats.isFile() before provider
selection, and open the upload stream only right before it is consumed.
The pinned SDK rejects upload options that contexts were already built
from — upload --withCDN failed after the funding transaction had run.
CDN preference rides in via createContexts alone; the upload mock now
mirrors the SDK guard so every upload test enforces the contract.
Only --url went through resolveDocsUrl; entries parsed out of llms.txt
were auto-fetched verbatim, so a planted external link bypassed the
boundary. One validator now gates every URL that arrives inside fetched
content (index entries, sitemap locs, shards): https on the docs host
or dropped at parse time.
--auto ran after the existing-key shortcut, so it reported
already_configured instead of replacing; with a keystore configured it
set a key the keystore silently outranked. Explicit methods now run
first and clear the alternate credential. Agent mode rejects --keystore
(unusable there: cast prompts on the terminal at use time) and its
guidance no longer offers it. Tests pin isAgent to the context flag —
the runner's non-TTY stdout made every context agent mode.
Only directories were screened, so a FIFO or device node reached the
synchronous readFileSync and could block the CLI/MCP process
indefinitely. Gate on isFile() and require the keystore's crypto field
to be an object, not merely present.
init was the only executable command without an output declaration, so
--schema and MCP get_tool_details omitted its result contract. Declare
both envelopes (configured / already_configured) and add a discovery
test that walks src/commands so the next schema-less command fails CI.
The actor-not-found guidance suggested wallet fund on every chain, but
the faucet is Calibration-only — an agent on chain 314 would fund the
wrong network. The CTA now appears only on 314159; mainnet gets prose
directing FIL/USDFC to the address.
download overwrote any existing --out path while advertising
destructiveHint: false to MCP clients. Default to exclusive creation
(EEXIST -> FILE_EXISTS with a --force CTA), add --force for explicit
overwrite, and declare destructiveHint: true since forced overwrite
remains possible.
costs built one context per active dataset, and prepare() applies
dataSize once per context — so the quote scaled with historical dataset
count (1 dataset priced 1 copy, 3 priced 3) instead of the requested
upload. Price exactly --copies contexts (default 2, like upload):
existing datasets first, new-dataset placeholders for the rest, which
also keeps the empty-wallet path away from provider selection.
New codes (NOT_A_FILE, FILE_EXISTS, KEYSTORE_INTERACTIVE_ONLY) join the
troubleshooting catalog, agent init guidance drops the keystore method,
and the Unreleased changelog records the PR #30 review round: costs
pricing by copies, download overwrite protection, init replacement
semantics, the docs index allowlist, and the withCDN contract fix.
Live smoke (2026-07-23) showed Glif Calibration now fails a never-funded
address's eth_call with 'failed to apply on state with gas' instead of
'actor not found', so the ADDRESS_NOT_ON_CHAIN humanization silently
regressed to the raw multicall dump. Match both wordings.
The success CTA recommended 'piece upload', a command removed from the
surface — agents following it hit an unknown-command error. Suggest
upload (provider selection reuses the new dataset) and dataset details
instead.
summary's timeRemaining concatenated the same duration in five units
('17468h 727d 103w 25m 2y'), live-observed reading as nonsense. Pick
the largest unit that fits ('~2y'), with 'mo' for months so it cannot
be misread as minutes.
@nijoe1

nijoe1 commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Responding to both CHANGES_REQUESTED reviews: all nine blockers (plus the multi-upload half of the streaming finding) verified against source and confirmed — nothing rebutted. Each is fixed in its own commit, now pushed; per-finding details are in the inline threads. The suite grew 78 → 98 tests, and every commit in the fix range is individually test/lint/build-green.

Finding Commit
wallet costs prices historical datasets, not the upload ee951a0
docs allowlist bypassed by index-derived URLs 360d135
non-regular files pass preflight before funding (upload + multi-upload) 9a0fc9c
wallet init --auto doesn't replace / keystore outranks / MCP keystore 900cfe4
download clobbers while declaring destructiveHint: false 2840b99
mainnet users sent to the Calibration faucet 883bc61
upload --withCDN violates the pinned SDK option contract 826c47b
wallet init missing output schema 83ade70
keystore validation admits FIFOs/devices to a blocking read 03d4d46

Pre-merge gates from the review:

  • 0.2.0: agreed and queued as the release step — cli/package.json, both skill frontmatters, and every doc pin are CI-pinned together (skills-consistency test), so the bump lands as one atomic change with the CHANGELOG retitle.
  • ClawHub: publishing is deferred indefinitely; the README install row was removed (d2a5a83) while the skills stay ClawHub-compatible (frontmatter + keywords kept), so there is no dangling uninstallable instruction.

On "evidence presently unauditable from the PR": after the fixes, a full live re-smoke ran on Calibration against the funded test wallet — all 18 leaf commands plus integrations (completions, skills add/list, mcp add/doctor, raw --mcp stdio including search_tools/call_read_tool execution). Highlights tied to the blockers:

  • upload --withCDN completes end-to-end (2 copies, zero failed attempts) — impossible pre-fix;
  • wallet costs on a wallet with 8 active datasets prices 1 GiB at 0.0264/0.0529/0.0793 USDFC/month for --copies 1/2/3 — linear in copies, independent of dataset count;
  • download round-trip is byte-identical, repeat → FILE_EXISTS, --force overwrites;
  • balance --chain 314 returns prose-only guidance, no faucet CTA.

The re-smoke also surfaced and fixed three further bugs now in the branch: the fresh-address humanization had regressed against current Glif RPC wording (failed to apply on state with gas) — 5657bff; dataset create's CTA recommended the removed piece upload command — 6c23831; and wallet summary rendered the runway as five concatenated units — 833f7a3. Upstream (incur) rendering issues observed along the way (boolean CTA placeholders like --force <force>, --filter-output nested paths) are catalogued for separate filing, plus one SDK-level report: isNewDataSet: false for datasets created by the same command via createContexts.

@nijoe1
nijoe1 requested a review from snissn July 23, 2026 14:56

snissn commented Jul 23, 2026

Copy link
Copy Markdown
Member

Follow-up review at 45b05c59a9656431aef7d89fc45aa7d6896dea2f:

Exact-head CI is green. I rechecked the prior review findings against the implementation rather than relying only on thread replies. The original concrete blockers are substantially addressed: index-derived docs URLs are allowlisted, non-regular upload inputs fail before preparation/funding, the SDK contexts + withCDN conflict is fixed, wallet replacement/keystore handling is corrected, wallet init has an output schema, download no longer clobbers files by default, the mainnet faucet CTA is gone, and the unpublished ClawHub install row was removed.

Merge assessment

I do not see a massive architectural, data-loss, or unauthorized-spend blocker remaining. I do see two bounded issues that should be handled before merge—or explicitly narrowed so the implementation work can defer.

1. Merge blocker, but small/mechanical: preserve chain in every CTA

The balance-specific mainnet CTA was fixed, but generated follow-up commands still routinely omit chain. A command run with --chain 314 can therefore hand an agent a CTA that silently defaults back to Calibration (314159). Examples include:

  • dataset details next-page/fetch-all plus piece remove / dataset terminate
  • piece list pagination and follow-ups
  • dataset createupload / dataset details
  • wallet depositwallet balance / upload
  • download → piece/dataset lists

This reaches destructive and fund-moving workflows, so I would not defer it in an agent-hardening PR. The fix is compact: every chain-aware CTA should include options.chain = c.options.chain (preferably through one helper), with a regression test that runs commands on chain 314 and rejects CTAs that lose the chain.

2. Conditional blocker: wallet costs is still not the exact upload quote it claims to be

The previous “price every historical dataset” bug is fixed, but the replacement still chooses the first copies active datasets and pads with objects cast to StorageContext. Actual upload first selects reachable, unique providers and then resolves datasets matching provider, source, and CDN metadata. The estimate can therefore reuse:

  • two datasets on the same provider, even though a multi-copy upload requires unique providers;
  • a dataset with different CDN/source metadata that upload would not reuse;
  • arbitrary historical datasets rather than the providers the next upload will select.

That can change dataset-creation fees, CDN lockups, effective monthly rate, and depositNeeded. The plain-object placeholders also depend on current SDK internals despite the package allowing compatible minor upgrades.

This is not a catastrophic spend bug—the real upload performs its own prepare()—but it is a money-path correctness problem while README/skills call wallet costs the “source of truth.” Two acceptable paths:

  1. Align it with upload’s real provider/context selection before merge; or
  2. Narrow the contract now: describe it as an approximate requested-copy estimate, stop calling it the source of truth/exact next-upload quote, document the limitations, and open a tracked issue. With that contract change, the full alignment can defer.

Safe to defer

These do not justify holding the merge if tracked:

  • 0.2.0 version bump: release blocker, not necessarily merge blocker. There is no publish-on-merge workflow and this repository previously used a separate release-metadata PR. Hard requirement: no npm/skill publication until package version, both skill frontmatters, pins, and changelog are consistently 0.2.0.
  • Check-then-stream TOCTOU: a file can change/disappear after stat and after preparation. Opening/fstat-ing a file descriptor before preparation would close it, but the current failure is bounded and already acknowledged.
  • Fresh-address RPC matching: failed to apply on state is broad and may misclassify another RPC failure as ADDRESS_NOT_ON_CHAIN. Tighten later or confirm the actor state explicitly.
  • Existing-keystore no-op path: wallet init without an explicit method only detects an existing private key, not an existing keystore; --source can also mutate before a later init failure. Bounded UX/atomicity issue.
  • Docs hardening cleanup: centralize validation inside docsFetch and optionally reject non-default ports. Current external-host bypass is fixed.
  • Retrieval wording: successful retrieval proves returned bytes match the CID at that moment; it is worth distinguishing that from ongoing PDP proof status before release.

Merge hygiene

Issues #1, #3, and #5 ask for concrete dApp SDK code/environment/cost-display guidance. This PR mostly routes those needs to live docs. That is a reasonable scope choice, but remove their automatic closure references or create explicit follow-ups unless the owners agree the redirected-docs approach fully satisfies them. The same acceptance decision should be explicit for #7’s requested dataset-level verify command.

Verdict: after chain propagation is fixed and the wallet costs contract is either corrected or honestly downgraded, I would consider this ready to merge. The remaining concerns can be deferred. Existing CHANGES_REQUESTED reviews may still need to be superseded or dismissed if branch protection enforces them.

nijoe1 added 2 commits July 23, 2026 18:44
A command run with --chain 314 could hand an agent a CTA that silently
defaulted back to Calibration — reaching destructive and fund-moving
workflows (terminate, remove, deposit, pagination). One chainCta()
helper stamps options.chain onto every chain-aware CTA (docs and wallet
init have no --chain and are exempt); a mainnet sweep test rejects any
suggested command that loses the chain.
upload selects reachable unique providers and matches source/CDN
metadata, so it may not reuse the datasets costs estimates against —
creation fees, CDN lockups, and depositNeeded can differ. Say so in the
command description, README, and skill instead of calling the estimate
the source of truth; the upload re-quotes via its own prepare() before
spending. Full provider-selection alignment is tracked separately.
@nijoe1

nijoe1 commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

Both pre-merge items are addressed at the new head:

  1. Chain propagation — fixed in 941a872. A single chainCta() helper stamps options.chain onto every chain-aware CTA — 15 sites across 11 commands, including the ones you listed: dataset details next-page/fetch-all, piece list pagination, piece remove / dataset terminate follow-ups, dataset create → upload / dataset details, wallet deposit → balance / upload, and download → piece/dataset lists. docs and wallet init CTAs are exempt (no --chain option to carry). A mainnet sweep test runs the CTA-emitting commands with --chain 314 and rejects any suggested command that loses the chain; the pagination assertions now pin the chain too.

  2. wallet costs — contract narrowed per your option 2, in 173cc53. The command description, README, and skill no longer call it the source of truth: it is documented as an approximate requested-copies estimate that reuses existing datasets, with the divergences you identified (unique-provider selection, source/CDN metadata matching → different creation fees, CDN lockups, depositNeeded) called out in code comments and docs, plus the note that upload re-quotes via its own prepare() before spending. Full alignment with upload's provider/context selection is tracked in #ISSUE_COSTS.

Deferred items are tracked in #ISSUE_DEFERRED (TOCTOU, fresh-address RPC-match tightening, existing-keystore no-op + --source atomicity, docsFetch centralization/ports, retrieval-vs-PDP wording).

Merge hygiene: the PR body's closure references for #1#5 and #7 are downgraded to "addresses", with auto-close kept only for the evidence-based reports (#23#29); #1/#3/#5/#7 acceptance stays an explicit owner decision. On the 0.2.0 gate we'll follow the release-metadata-PR pattern — no npm/skill publication until version, frontmatters, pins, and changelog are consistently 0.2.0.

@nijoe1

nijoe1 commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

cc: @snissn

@snissn snissn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review at 173cc53f65d3ea94b0413918b7aa40b237f3d65e: approved.

I rechecked the two commits pushed after the last merge assessment:

  • chainCta() preserves an explicitly supplied chain and is applied only where every suggested command accepts --chain. The mainnet regression sweep covers pagination, destructive/fund-moving follow-ups, download success/error paths, and wallet flows. This resolves the cross-network CTA blocker.
  • wallet costs now describes the result as an approximation against existing datasets, explicitly identifies the provider/source/CDN mismatch risk, and tells callers that upload-time prepare() is the final quote before spending. This is an acceptable deferral of full provider-selection alignment rather than a misleading exact-pricing contract.
  • The prior docs-host allowlist thread is verified and resolved.
  • Exact-head CI is green for tests, formatting/lint, typecheck, and build.

I found no new blocking correctness, security, local-data, or spend-safety issue in the pushed changes. The remaining items are safe follow-ups: full cost/provider-selection alignment, the documented check-then-stream TOCTOU, broader RPC error classification, and attaching reproducible smoke evidence.

Two non-blocking release/PR-description cleanups remain:

  1. Keep the breaking dataset upload removal out of a published 0.1.1; land the atomic 0.2.0 package/skill/doc-pin bump before publishing.
  2. The PR body now correctly says only #23#29 auto-close, but task 5 still says merging auto-closes #1#5 and #7. Please reconcile that stale line so the acceptance decision is unambiguous.

From a code-review perspective, this can merge.

@nijoe1
nijoe1 merged commit 5ad5e00 into main Jul 23, 2026
1 check passed
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.

2 participants