Skip to content

chore: align dev with current staging (gRPC migration + docs merged into staging's security-delete work)#483

Draft
harrymove-ctrl wants to merge 29 commits into
devfrom
chore/staging-baseline
Draft

chore: align dev with current staging (gRPC migration + docs merged into staging's security-delete work)#483
harrymove-ctrl wants to merge 29 commits into
devfrom
chore/staging-baseline

Conversation

@harrymove-ctrl

@harrymove-ctrl harrymove-ctrl commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Status: Draft — review only, do not merge yet. Per team guidance, this should be reviewed now but held until the ongoing V1→V2 migration work lands.

Summary

This PR is the new dev baseline built on staging as the standard: base is staging, plus the valid dev-only parts. It introduces no new gRPC — the gRPC in this diff is what staging already ships (690888b9) reconciled with dev's #355, and wherever the two branches disagreed, conflicts were resolved in staging's favor.

Context: dev and staging had diverged substantially (112 files, +18.7k/−1k lines) — the gRPC migration from #355 landed on dev but never reached staging, while staging independently rebuilt the delete-old-memories feature into the security_delete subsystem and hardened the relayer.

What dev adds on top of staging (19 files)

Documentation (10):

File Change
docs/guides/delete-memories-programmatically.md New guide — deleting memories via API/SDK
docs/sdk/headless-setup.md New guide (#482 agent-memory set)
docs/sdk/codebase-memory.md New guide (#482 agent-memory set)
docs/fundamentals/concepts/verifiable-memory.md New concept doc (#482)
docs/relayer/overview.md, docs/relayer/self-hosting.md SUI_GRPC_URL documentation (#481)
docs/reference/environment-variables.md, docs/reference/configuration.md Env/config reference updates (#481)
docs/docs.json Navigation entries
docs/guides/delete-old-memories.md Minor fix

Smart contract (2):

  • services/contract/sources/account.move + tests/account_tests.move — the seal_approve authorization-scoping fix with its regression test (test_seal_approve_delegate_rejects_unrelated_id).

Supporting changes (7):

  • Comment-only (2): services/server/src/types.rs (documents that sui_rpc::Client must not be rebuilt per request), apps/app/src/utils/suiClientCompat.ts (architecture note updated).
  • Merge glue (3): ConnectMcp.tsx and SetupWizard.tsx now use the shared suiClientCompat helpers instead of the deleted suiFields.ts — same behavior (JSON-RPC), but the pages no longer assume one transport shape.
  • Fixes worth keeping (2): sidecar/retry/rpc.ts adds gRPC-aware retryable status codes (UNAVAILABLE, RESOURCE_EXHAUSTED, DEADLINE_EXCEEDED, …) — without it, the gRPC write path staging already uses silently stops retrying; useSponsoredTransaction.ts adds retry/backoff on the /sponsor call and settles on the always-sponsored flow.

Note: the GitHub "Files changed" tab diffs against the base (dev), so it shows a different, larger set — the list above is the delta against staging, which is the meaningful one under the "staging is standard" model.

Conflict resolutions

Area Decision Rationale
Frontend transport Kept staging's architecture Shared SuiClientProvider stays JSON-RPC; gRPC remains scoped to the security-delete subsystem's own client. dev's unconditional createClientForNetwork swap was removed.
Backend validation Kept staging's hardening validate_memwal_account_type rejects objects that aren't actually a MemWalAccount before trusting their fields.
Sidecar testnet policy Kept staging's stricter stance gRPC mandatory on testnet: boot-time validateSuiTransportPolicy guard, suiJsonRpcClient forced to null.
Cargo.toml / Cargo.lock Kept staging's exact pins Required by the native sui/ client module.

The full file-by-file resolution list is in the merge commit message (03d1f72).

Verification

Deployed environment — the Railway dev environment runs this exact branch on both app (dev.memwal.ai) and relayer (relayer.dev.memwal.ai):

  • Relayer logs show delegate key verified onchain (gRPC) on /api/remember; no failed phases
  • Delegate account flow works end-to-end (dashboard, account resolution, delegate keys)

Test suites

  • Server: cargo check clean; cargo test 273/278 (the 5 failures are pre-existing local DB-pool timeouts, unrelated)
  • Sidecar: npm test 89/89
  • App: tsc -b, vite build + verify-dist-assets, vitest 66/66
  • Move contract: 48/48

ducnmm and others added 28 commits June 5, 2026 15:59
…protocol (#281)

The release workflows used `npm publish`, which ships package.json verbatim and
cannot rewrite the `workspace:` protocol. As a result oc-memwal was published
with `"@mysten-incubation/memwal": "workspace:*"`, which fails to install
outside the monorepo (`Unsupported URL Type 'workspace:'`).

Switch release-oc-memwal, release-mcp, and release-sdk to `pnpm publish`
(--no-git-checks), which rewrites `workspace:*` to the concrete dependency
version at pack time. Bump oc-memwal to 0.0.5 so a working version is published;
0.0.4 on npm remains broken and should be deprecated.
…ce (#351)

Mainnet writes were failing 100% at the register_sponsor phase with an Enoki
dry_run_failed -> MoveAbort in 0x2::coin::destroy_zero (ENonZero). Root cause is
not Enoki: @mysten/walrus '#withWal' pre-funds an *exact* WAL payment computed
from the client's cached systemState price, then asserts the coin is empty via
coin::destroy_zero. When mainnet storage/write price drifts down between the
cached read and execution, the contract deducts less WAL than we split off and
the leftover trips destroy_zero. Verified on prod: on-chain price moved
71464->70922 within ~15 min, and a fresh relayer boot served 6 writes before the
same client flipped to 100% destroy_zero at the next price move.

Two gaps let this fail hard instead of self-healing:
- The sidecar's auto-refresh only fired on isMoveAbortBalanceSplit ('balance'
  + 'split'); this message says destroy_zero, so refreshWalrusClient never ran.
- The Rust worker swept it into the MoveAbort -> Permanent catch, so Apalis
  Dead-marked every write with no retry.

Fix:
- enoki.ts: add isMoveAbortWalDestroyZero detector.
- walrus-upload.ts: refreshWalrusClient() on the destroy_zero abort so the retry
  rebuilds against the live price.
- jobs.rs: classify the register destroy_zero abort as Transient (retryable),
  disjoint from the balance::split gas-budget path.
- config.ts: drop WALRUS_CLIENT_MAX_AGE_MS default 30m -> 60s so the cached
  price tracks a live-drifting mainnet price (still env-overridable).

Tests: new TS detector suite (7) + Rust classification test using the verbatim
prod error. Full suites green (85 TS, 40 jobs).
The sidecar hardcoded getJsonRpcFullnodeUrl(mainnet) and ignored the SUI_RPC_URL
env, so it could not be pointed at a dedicated RPC when the public fullnode pool
degrades. Observed today: the public pool served stale reads (a blob certified
at 00:49:14 read back as 'does not exist' at 00:49:49, 35s later), failing
uploads at get_blob/certify/metadata. Honor an explicit SUI_RPC_URL override so
ops can switch to a healthy RPC via env; fall back to the network default.
* docs: add guide for deleting old memories

* docs: hide delete-old-memories guide from navigation

Remove the Guides tab (which only contained the delete-old-memories
guide) from the docs navigation so the page is no longer surfaced in
the site. The guide's markdown file is kept in place so the content is
preserved and can be re-enabled by restoring the navigation entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4ARRUn69UZpxyvaFFUfuQ

* docs: remove Seal encryption claims from Walrus Memory overview

---------

Co-authored-by: Reem Sabawi <reem.sabawi@mystenlabs.com>
Co-authored-by: Claude <noreply@anthropic.com>
…ries

Adds a subsystem for permanently deleting the memory blobs written by the
pre-upgrade encryption scheme. The server resolves which blobs a wallet owns,
builds and sponsors the delete transaction, and the user signs it; the blob
object is destroyed on chain and its data is no longer retrievable from Walrus.
Users pay no gas.

Server
- `/api/security-delete-auth/*` — wallet-signature auth minting a short-lived
  token. The address is derived from the signature, never taken from the request.
- `/api/security-deletable-blobs` — the wallet's deletable blobs, resolved from
  chain rather than from the database, so a row can never claim ownership the
  chain does not agree with.
- `/api/security-deletions` — prepare / submit / cancel. The delete PTB is built
  entirely server-side against a hard-coded `system::delete_blob` target; the
  client only ever returns a signature over bytes it did not choose.
- Background reconciler and object resolver to settle in-flight batches and keep
  tracking state in step with the chain.
- Deletion is tracked in a separate legacy database (`LEGACY_DB_URL`); the main
  database is untouched.

App
- A "Delete old memories" card listing the wallet's remaining old memories, with
  an in-browser decrypt preview so a user can read a memory before destroying it,
  single and bulk delete, and progress/history.

Two chain-facing details are worth calling out, because both are invisible on a
freshly-published local network and destroy data on a live one:

- `Blob.storage.end_epoch` is a WALRUS epoch and must never be compared against
  the Sui ledger epoch. The two clocks are independent and diverge without limit,
  so the comparison marks live blobs expired — and expired is terminal, so the
  user's data would stay on Walrus with no way left to remove it.
- The Walrus package id must be read from the System object at runtime. Walrus
  upgrades its package; a pinned id makes every delete abort on chain. The id in
  config is a fallback and a drift signal only. It cannot simply be repointed at
  the new package: the same value also types the owned-object filter, and Move
  types keep naming the package they were published in.

`SuiEpoch`, `WalrusEpoch` and `WalrusCallPackage` are distinct types precisely so
that neither mistake can be expressed. Both were previously bare primitives.

Everything is off by default (`ENABLE_MEMORY_DELETION`, `ENABLE_SECURITY_DELETE`,
`VITE_SECURITY_DELETE_ENABLED`), behind a boot guard that refuses a partial
configuration. The subsystem talks to Sui over gRPC; on mainnet the existing
JSON-RPC paths are untouched.

410 tests (381 unit, 29 Postgres-backed). Verified end to end against live Sui
and Walrus testnet: prepare, sponsor, sign, execute, and the blob object absent
from chain afterwards.

Co-authored-by: Daniel Lam <daniellamhoang@gmail.com>
Co-authored-by: hungtranphamminh <tranphamminhhung65@gmail.com>
Brings the Walrus register `destroy_zero` stale-WAL-price recovery and the
explicit `SUI_RPC_URL` override, plus the accompanying docs and CI.

One conflict, in `services/server/scripts/sidecar/config.ts`: both sides keep
`SUI_RPC_URL` with the same fallback, and this branch adds `SUI_GRPC_URL` and a
validating `parseSuiNetwork` on top. Kept that structure and took the incoming
comment, which documents why the override exists — the public fullnode has served
stale reads that fail uploads at get_blob / certify.

`jobs.rs` and the sidecar's walrus-upload route auto-merged; the `destroy_zero`
reclassification is not duplicated.
…ndpoint

The public fullnode.mainnet.sui.io pool serves stale/slow reads under
load (read-after-write lag measured at 40-70s), which breaks flows that
read state their own transaction just wrote. Point the relayer's
SUI_RPC_URL at rpc-mainnet.suiscan.xyz and give the app a matching
VITE_SUI_RPC_URL override (active network only; other networks keep the
public default).
Defaults mainnet to the dedicated JSON-RPC endpoint rather than the public
fullnode, whose pool serves stale reads under load. `VITE_SUI_RPC_URL` now
overrides the endpoint for the active network, mirroring the relayer's
`SUI_RPC_URL`.

Merged clean. Also corrects two stale comments in `config.ts` that still
described the app's shared client as gRPC-only.
feat(security-delete): let users permanently delete their old V1 memo…
fix(api): return 503 for paused uploads
fix(app): update delete memory tabs
fix(app): distinguish delete tabs
the evolved security-delete feature and relayer hardening

Both branches had diverged substantially (112 files, +18.7k/-1k lines)
since dev's PR #355 gRPC migration landed without ever reaching staging,
while staging independently rebuilt the delete-old-memories feature into
"security_delete" (native Sui gRPC client in services/server/src/sui/,
dedicated auth/error/store modules) and hardened the relayer (destroy_zero
recovery, dedicated JSON-RPC RPC provider). This merge keeps both lines of
work; base = staging (dev has no unique commits it kept since #389 was
closed unmerged).

Conflict resolutions (15 files):
- App.tsx / config.ts / suiClientCompat.ts: kept staging's deliberate
  architecture — the shared SuiClientProvider stays on JSON-RPC (documented
  reason: every JSON-RPC-shaped consumer would need dual-transport compat
  otherwise), gRPC is scoped to the security-delete subsystem. Removed
  dev's now-dead top-level createClientForNetwork/gRPC swap and its unused
  imports.
- Dockerfile: kept both VITE_SUI_RPC_URL (staging) and VITE_SUI_GRPC_URL
  (dev) ARG/ENV pairs.
- vite.config.ts: kept staging's dev-only E2E wallet injector addition
  (dev didn't touch this file).
- docs/guides/delete-old-memories.md: took dev's later wording fix
  ("Stored" status label) since it matches what SecurityDeleteSection.tsx
  actually renders; staging's "At risk" wording was stale.
- services/server/{auth,main,storage/sui}.rs, types.rs: kept staging's
  side throughout — it added a real fix (validate_memwal_account_type,
  rejecting objects that aren't actually a MemWalAccount before trusting
  their fields) that dev's simpler verify_delegate_key_onchain lacks.
  types.rs keeps both doc comments (merged, not a pick).
- Cargo.toml/.lock: kept staging's exact-pinned crate set (needed by the
  new sui/ native client module); lockfile regenerated via cargo build.
- sidecar clients.ts/config.ts/routes/walrus-query.ts: kept staging's
  hardened testnet policy (SUI_GRPC_URL mandatory on testnet, boot-time
  validateSuiTransportPolicy guard, suiJsonRpcClient forced null on
  testnet) over dev's opt-in/lenient version.

IMPORTANT — found during review, not introduced by this merge: staging's
account.move currently lacks security fix 97754fc ("scope seal_approve
authorization to the account owner", dev-only, marked "code-only; not
deployed" at the time). Without it, a registered delegate's seal_approve
authorization does not bind the requested key `id` to the account they're
a delegate on, so `is_delegate_address` alone can satisfy the assert
regardless of whose data the id belongs to. This merge's Move source now
has dev's fix (git auto-merged it cleanly since staging never touched
this function), plus its regression test
(test_seal_approve_delegate_rejects_unrelated_id, previously absent from
staging). Needs a decision on whether/where this contract change has
already been published on-chain.

Verified after resolution: cargo check clean; cargo test 273/278 (5
failures are pre-existing local DB-pool timeouts, unrelated); sidecar
89/89; app tsc + vite build + 66/66 vitest; Move contract 48/48.
const promise = createSession(opts.suiClient, opts.address, opts.signPersonalMessage)
sessions.set(key, promise)
try { return await promise } catch (error) {
if (sessions.get(key) === promise) sessions.delete(key)
@railway-app
railway-app Bot temporarily deployed to Walrus Memory / dev July 22, 2026 03:55 Inactive
@harrymove-ctrl
harrymove-ctrl force-pushed the chore/staging-baseline branch from fe91f78 to cab99be Compare July 22, 2026 04:27
…C_URL relayer docs, #482 agent-memory developer guides)
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.

5 participants