fix(web): polyfill Buffer for Privy transfers - #126
Merged
Merged
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
oneshot | dcc7307 | Sep 13 2026, 06:59 AM |
kapustazh
commented
Sep 13, 2026
kapustazh
left a comment
Collaborator
Author
There was a problem hiding this comment.
Review: PR #126 — fix(web): polyfill Buffer for Privy transfers
Verdict: Approve — correct, minimal, well-scoped. All 5 checks green (ESLint/TS, browser acceptance, Markdown, Workers build, repo policy).
What's good
- Right fix, right place.
buffer@6.0.3as a direct dep +globalThis.Buffer ??= Bufferinmain.tsxis the standard remedy for Privy's transaction path referencing Node'sBufferin the browser. The assignment executes at entry-point evaluation, before Privy's lazy wallet chunk loads, and the error occurred at transaction time (after approval) — so ordering is sound. ??=is the right operator. It won't clobber a Buffer if a bundler or another polyfill already provided one. Defensive and correct.- Regression guard.
p5.spec.tsnow assertstypeof globalThis.Buffer === 'function'in real Chromium, so a future refactor that drops the polyfill fails CI instead of failing a user's transfer. - Repo hygiene. Exact-pinned version matches the lockfile style of the repo; lockfile updated consistently; scope matches the PR description; security invariants untouched (frontend-only, no auth/settlement change).
Minor notes (non-blocking)
- Test coupling — the Buffer assertion lives inside the p5 test named "keeps the public landing separate from the authenticated cabinet". A one-line dedicated test (or a rename) would be cleaner, but it works.
- Expect possible follow-ups. Only
Bufferis polyfilled. This dependency chain (Privy/WalletConnect/viem) sometimes trips next onprocess is not definedorglobal. If the deployed testnet run surfaces one of those, the same entry-point pattern applies — or adoptvite-plugin-node-polyfillsif it becomes more than one global. Worth watching, not blocking. - Typing fragility —
globalThis.Buffer ??= Buffertypechecks today (CI confirms), but it depends onBufferbeing visible onglobalThisvia transitive@types/node. If that ever disappears from the graph, adeclare globalshim inapps/web/srcmakes it explicit. - Residual risk is honestly declared in the PR: the real confirmation is one user-approved Privy testnet transfer against the deployed Worker. Do that before merge per the repo's own handoff note.
The .agent/context/ session doc follows the repo's established convention (repository-policy check enforces it), and the disclosed Worker version ID / Privy app ID are not secrets.
Suggested merge checklist: ✅ CI green → ⬜ one live approved testnet transfer succeeds → ⬜ human owner merges (per repo rule: agents don't merge).
9 tasks done
kapustazh
marked this pull request as ready for review
September 13, 2026 07:31
This was referenced Sep 13, 2026
kapustazh
added a commit
that referenced
this pull request
Sep 13, 2026
* fix(subgraph): remove trailing empty line in schema.graphql * docs: plan Arc Circle qualification path * feat(web): compose P5 frontend acceptance * feat(web): merge P5 frontend acceptance implementations * feat(worker): add production runtime * test(worker): wire integration ledger dependencies * feat: package gate p6 demo * fix: use pnpm version * feat(web): define the operator session port * feat(web): gate the console behind an operator sign-in surface * feat(api): verify Privy access tokens against an operator allowlist * feat(api): route credentials to one authenticator by token shape * feat(api): load and validate Privy operator auth configuration * feat(api): compose Privy and service-bearer authenticators at runtime * feat(web): require an operator session before the console renders * style(web): format operator session wiring * feat(web): mount the Privy provider lazily behind the session port * docs: describe the Privy operator sign-in boundary * feat(web): Behance-grade minimalist dark glassmorphism landing and console * feat(auth): open wallet authentication with wildcard allowlist and UI polish * fix(web): buffer proxy request body, enforce CORS headers, and default production Privy App ID * fix(worker): preserve production runtime dependencies * fix(worker): sign and broadcast on custom Arc chain without relayer * fix(recovery): verify hashless graph candidates on Arc * fix(worker): persist provider request identity before submit * fix(recovery): preserve verified receipt metadata * fix(worker): require production subgraph mcp admission * fix(api): harden production operator authentication * feat(storage): persist operational metric events * docs: document recovery hardening and fix integration fixtures * test(worker): reset operational metrics between integration cases * fix: harden settlement safety boundaries * fix(recovery): query Arc subgraph via Studio * fix(recovery): allow delivered reconciliation retries * docs(web): design for brand frontend repaint and wallet picker The brand direction settled on the design canvas lives nowhere in the repository, and the palette is restated independently in three stylesheets. Record the approved design for putting it in code: a shared token and asset package, light and dark boards switched by data-theme, the commit-ring mark and derived hero geometry, and a searchable wallet picker built on Privy's headless SIWE flow. Privy's own login modal has no search hook and login() cannot be narrowed to a single wallet, so the picker is ours; useLoginWithSiwe is the supported headless path it uses. * docs(web): add the brand frontend implementation plan * docs(web): correct four defects found in the brand frontend plan preflight * docs(web): give the lime field its own fixed ink token * feat(brand): add the brand token package with a two-theme contrast audit * docs(plan): graph studio agent recovery plan and durable handoff context * feat(brand): add the commit-ring mark with its reduced cut * fix: harden Arc Testnet settlement * docs(web): correct the hero vertex count to four per shape * feat(brand): derive the diagonal hero cut from the box size * docs(web): restore globals before clearing storage in the theme teardown * feat(web): add theme selection with a pre-paint guard * docs(web): wrap the brand alias to the repo print width * feat: implement Graph Studio agent recovery * feat(settlement-ui): remove allowlist display from PolicySummaryPanel * feat(worker): remove recipient allowlist restriction and support wildcard * fix(worker,ui): wait for transaction receipt on submission and improve authorization status inference * style: format settlement-ui contract and cloudbuild config * fix: add shared API rate limiting and complete worker deploy config * fix: harden Privy fallback and integration cleanup * docs: reconcile production runtime and qualification status * test: avoid migration rollback fixture collision * feat(web): repaint the console shell onto the brand tokens - Replace the navy/cyan literal palette in apps/web/src/styles.css with @oneshot/brand's --os-* tokens throughout; zero hex/rgb literals remain, enforced by a new guard test (test/styles.test.ts). - Apply the task-5 mapping table rule by rule: page ink on page/surface context, panel ink on panel/card/console context, signal for accents, the three ledger-state tokens for success/warning/error, os-line for borders and dividers. Delete glows, translucent-navy washes, and gradients rather than inventing replacement tokens. - Flatten every control to font-weight 300 and either a 999px pill (buttons, badges, inputs, chips) or var(--os-radius-lg) (panels, cards, the console container, the top nav bar). - Replace the button, tab-strip, and state-card rules verbatim per the brief, and append the new .theme-toggle, .hero-cut/-figure/-panel/ -copy/-plain, and .wallet-* classes for tasks 6, 7, and 10. - Fix the 101-char @oneshot/brand alias line in vite.config.ts and vitest.config.ts (prettier printWidth 100), carried over from task 4. * fix(web): correct ink/panel-ink contrast bugs in the stylesheet Review found three Critical contrast bugs in the Task 5 token repaint, caused by --os-ink/--os-panel (and their muted/panel-ink counterparts) colliding in the light theme while the dark theme masks the mistake: - button.secondary took --os-ink but both real usages (IntentStatusView's Refresh, IntentForm's New obligation ID) sit inside .panel containers, making the label invisible in light theme. Switched to color: inherit so it always takes whatever ink its container sets. - .machine-token used panel-ink unconditionally, correct for its .login-gate usage but wrong for its second usage inside .operator-identity (a page-surface container). Added .operator-identity-scoped overrides using the page ink tokens, ordered so the summary's :hover state still wins in both contexts. - .surface-empty (page-ink) and .panel (panel-ink) tie in specificity and both apply to FrontendSurfaces' <section className="panel surface-empty">, reached by opening Settlement/Recovery before selecting an intent. Repointed .surface-empty at --os-panel-ink-muted. Also, three smaller findings: - Added a .state-failed_safe strong rule (--os-state-failed) — FAILED_SAFE is a real terminal state the state-card block had no color for. - Added missing .muted and .field-label rules (both previously unstyled), scoped to the panel ink tokens matching their current usages in IntentStatusView and IntentForm. - Tightened styles.test.ts's fonts/tokens import-order assertion to a single anchored regex so a rule inserted between the two @imports (silently disabling the whole token layer) now fails the test, instead of slipping past the old indexOf/toContain pair. Verified: pnpm --filter @oneshot/web test (11 files, 58 tests, all pass), pnpm typecheck, pnpm --filter @oneshot/web lint, and npx prettier --check on both changed files all clean. No hex/rgb literal in styles.css. * feat(web): put the commit-ring mark and the theme toggle in the shell Adds afterEach cleanup() to app-brand.test.tsx (missing from the brief's verbatim test code); without it, jsdom's document persists across the file's three render(<App />) calls (no globals:true/setupFiles wires up RTL's auto-cleanup in this repo), and the third test's getByRole('button', { name: /theme/iu }) matches multiple stacked toggles. Every other multi-render test file in apps/web already calls cleanup() for the same reason. * feat(web): cut the diagonal hero from derived geometry * docs: reshape plan around resumable paid tools * feat(ui): read the slice palettes from the brand tokens * fix(brand): guard the contrast audit and fix panel border contrast Two review findings against the slice-palette work: - packages/settlement-ui/test/contrast.test.ts: the WCAG AA loop skipped any token resolveColour couldn't resolve instead of failing, so a future rename/typo in packages/brand/src/tokens.css would silently drop a channel from the audit while the test stayed green. Assert the value resolved before continuing, so an unresolvable token fails loudly. Verified with a deliberate break (renamed a var() target) that now fails the test, then reverted it. - packages/brand/src/tokens.css: --os-panel is identical in both themes but --os-line flips with the theme, so a border painted with --os-line on a panel reads correctly in dark and disappears in light. Add --os-panel-line (light ink at low opacity, theme-invariant like the surface it borders) and repoint every border/divider drawn directly on --os-panel in apps/web, settlement-ui, and recovery-ui stylesheets onto it, tracing each declaration to its actual container. Borders on the page ground or --os-surface keep --os-line unchanged. * feat: implement plan gap analysis * test: align CI coverage with job cabinet * test: isolate worker postgres integration suites * feat(web): separate tools and jobs workspace * feat: add Arc transfer quote and approval demo * feat(web): discover installed wallets over EIP-6963 * fix(web): harden EIP-6963 announcement validation and its test coverage - Finding 1: icon must be a data:image/ URI or it is dropped (icon now optional); the wallet itself is still kept. Fixes a zero-click IP/UA beacon leak via <img src>. - Finding 2: replaced the vacuous short-circuited malformed-announcement test with targeted cases for missing/blank rdns, absent provider, non-callable provider.request, and the https-icon-dropped behaviour. - Finding 3: the repeat-announcement test now re-announces the same uuid with a different name and provider object, and asserts the store keeps the first one by value and by reference. - Finding 4: cap uuid/name/rdns at 256 chars and icon at 256 KiB; oversized fields are rejected the same way malformed ones are. - Finding 5: documented that detectWallets() registers a permanent window listener and must be called once per app session. No behaviour change outside parseAnnouncement's validation and the new JSDoc; no .tsx files touched; no console/log statements added. * feat(web): add a searchable wallet picker * feat: add Circle x402 demo rail * feat(web): sign in through Privy's headless SIWE flow - OperatorSession gains an optional signInWithWallet(wallet) -> Promise<void>, present only on the configured Privy session. LoginGate renders the searchable WalletPicker when it is offered and falls back to the plain Privy button (and unconfiguredOperatorSession) when it is not. - usePrivyOperatorSession implements signInWithWallet via Privy's headless useLoginWithSiwe(): eth_requestAccounts -> generateSiweMessage (bound to Arc Testnet eip155:5042002) -> personal_sign -> loginWithSiwe. Both wallet RPC responses are validated (non-array/empty accounts, non-string signature) and rejected with a detail-free error before use; nothing is ever logged. - Brand Privy's fallback modal via its appearance config (theme, accentColor, walletList) — the only literal colours in this change, Privy's own hex API. - Fix an EIP-6963 listener leak: detectWallets() registers a window listener with no removal path, and WalletPicker called it on every mount. Add a lazy module-level singleton getWalletStore() and switch WalletPicker to it so remounting across sign-out/sign-in cycles never re-registers a listener. detectWallets() itself is unchanged and still directly tested. * fix(web): measure the hero before paint to stop the load flash Hero.tsx measured its box in a useEffect, which React runs after the browser paints. This app renders purely client-side (main.tsx uses createRoot, no SSR), so every load at desktop width painted .hero-plain for one frame before flipping to .hero-cut — a visible flash on the brand's signature element on every load. Switch to useLayoutEffect so the initial measurement runs synchronously before paint. The ResizeObserver wiring for subsequent resizes is unchanged. useLayoutEffect warns when it runs during SSR, but this app has none; the existing hero tests (jsdom, a real DOM) ran clean with no such warning. * fix(web): time out unresponsive wallet requests during sign-in signInWithWallet called wallet.provider.request(...) twice (eth_requestAccounts, then personal_sign) with no timeout. A provider that never resolves — a crashed or backgrounded extension — left WalletPicker stuck in its busy state permanently, with no way for the operator to retry or pick another wallet short of reloading. Wrap each request in withWalletTimeout, a 2-minute race against a timer. Two minutes is generous by design: the operator is interacting with their own wallet UI for both calls (approving a connection, reading and signing a SIWE message), so the timeout must not cut off a slow but honest human, only a provider that will never answer at all. The timeout's rejection carries a fixed, generic message with no wallet data (no address, message, or signature) — WalletPicker already replaces every thrown error with a sanitized line, and this keeps that contract intact even if the raw error is ever read elsewhere. Add test/privy-session.test.tsx, which mocks @privy-io/react-auth and uses fake timers to prove a never-resolving provider rejects at exactly the timeout instead of hanging, and that the rejection message contains none of the wallet's identifying fields. * docs: record x402 review evidence * docs: fix context markdown lint * docs(web): document the brand package and the theme guard * docs: fix markdown lint * docs: record final gate evidence * feat: add job-aware activity audit * docs: record activity audit checks * docs: record final activity state * docs: bind activity audit context * docs: bind activity audit context * docs: bind activity audit context * feat: add r4 response-loss drill * docs: add r5 release packet * feat(web): make report payment inputs configurable * feat: integrate paid API settlement into site * fix: clear paid API CI failures * test: include paid API records in postgres cleanup * feat: add Circle x402 seller service * fix(web): polish workspace loading state * fix(web): allow seller proxy redirects safely * fix(web): preserve wallet SIWE login * docs: record deployment gate evidence * fix(web): canonicalize wallet address for SIWE * fix: persist seller worker origin * fix(web): use Privy native login modal * fix: bound Privy reference IDs * fix(x402): reconcile Circle transfer UUIDs Persist Circle transfer identity before batch confirmation and verify Gateway submitBatch receipts. Add provider_transfer_id migration for safe recovery of paid API results. * docs: record Circle x402 PR * test(storage): cover transfer migration * feat(web): simplify payment workspace UX * fix(web): restore contrast and add rescue plan * docs: record rescue draft PR handoff * docs: fix handoff link formatting * fix(x402): bind approval to quoted payment * docs: record Arc rescue handoff * docs: refresh rescue handoff state * fix(web): restore workspace hero and action states * fix(web): preserve selected tab contrast * feat(web): redesign payment proof and recovery Merged after Gate A and Gate B PASS with all required CI checks green. * fix: harden Privy settlement response handling * feat: support user-funded wallet payments * fix: preserve legacy workspace composition * test: align postgres fixtures with merged migrations * fix: show verified user wallet payment proof * fix(web): make the operator console readable in both themes (#96) * fix(web): make the operator console readable in both themes The console shipped unreadable text in both themes from one mistake made in two directions. --os-panel and --os-field are the same colour in light and dark and carry their own fixed inks; --os-ground and --os-surface flip with the theme. Pairing one family's ink with the other family's surface is invisible in exactly one theme, so each instance survived review done in the other one: the tab strip used panel ink on the page ground, and the quote and paid-API summaries used panel ink on the lime field. Pair each surface with the ink family that owns it, and add a structural guard that resolves every rule's nearest painting ancestor and fails on any fixed ink over a flipping surface. Verified against the previous stylesheet: it reports all six pairings that existed there. Also in the cabinet: - Remove the Wallet & permissions and Developer access sections. Both were read-only restatements of facts the other sections already show, and neither had a control behind it. - Give .panel-heading a rule. It had none, so every panel that pairs a title with an action stacked them flush. - Let the hero copy sit in normal flow and set its own height, measured and fed to the clip paths, instead of overflowing a fixed 268px box by an amount that varied with the viewport. Drop the 820px cap so the hero spans the shell like the nav and tabs around it. - Lay the x402 and recovery controls out on a grid so label, field and help text each get a row, and style a.secondary, which was only ever styled for button. - Scale type once at the root rather than per component, so the console is not set at laptop sizes on a large display. - Fade surfaces on theme change and panels on tab switch, under prefers-reduced-motion: no-preference. Adds --os-on-field-muted and --os-field-line: a surface that does not flip needs a muted ink and a border line that do not flip either. * fix(web): reconcile the readability work with the workspace redesign Merging develop left the branch uncompilable: Hero carried both a `height` prop and a `height` state, and the cabinet kept this branch's narrowed section union while still rendering develop's panels, so four section names had no type to belong to. Reconcile rather than pick a side: - Hero measures itself by default, which is what stops the workspace copy clipping at widths where the headline wraps. The `height` prop stays as an explicit override for the landing page, which sizes its hero to a layout rather than to its copy. Only a pinned hero gets an inline height; feeding the measured value back would rebuild the fixed box the measurement exists to avoid. - Take develop's cabinet vocabulary, then apply this branch's request to it. The two sections asked to be removed were not gone, they were renamed: Spending rules was Wallet & permissions, Team & access was Developer access. Both are read-only restatements with no control behind them, so the cabinet is Overview, API services, Requests and Payment proof, and the two panels behind them are deleted rather than left unreferenced. - Drop this branch's enumerated ink overrides. Develop solved the same problem more generally by rebinding --os-panel-ink and --os-accent-ink at each surface boundary, so descendants inherit the right ink instead of every descendant being listed; its version also gets `.quote-panel` right, which moved onto --os-surface in the redesign. Only the --os-field-line border, which develop lacks, is kept. The structural guard survives and is what caught the regressions above. One of its assertions was wrong rather than the stylesheet: `.console-container .tabs button` legitimately takes panel ink because that container paints --os-panel, and an unanchored regex was matching it as a substring. * test(web): audit contrast on the settled page The browser contrast checks run immediately after navigation. With motion enabled, axe samples while the cabinet panel is still fading in and reads every colour composited against what is behind it: panel ink measured as #bdd1c2 on #4d6b64 rather than #a9c4b2 on #0b332c, and page ink as #728876 on #f4f7f1 rather than #3f5c46 on #edf1e9. Ninety-eight reported violations, none of them real. All motion in styles.css sits behind prefers-reduced-motion: no-preference, so emulating a reduced-motion viewer settles the page before the audit instead of suppressing a genuine failure. * test(web): walk the four cabinet tabs the workspace now has The browser acceptance spec iterated a six-label list and clicked each one. Spending rules and Team & access are gone, so the click timed out on a tab that no longer renders. Note the markdownlint findings under .superpowers/ are pre-existing and outside this branch. * fix(web): render the class the paid-API grid rules depend on Gate A caught this: taking develop's redesigned JobWorkspace.tsx wholesale also took back `<section className="panel">`, dropping the `paid-api-panel` class. The grid rules stayed in the stylesheet with nothing wearing them, so the x402 request key rendered inline with the text before it and its help text ran on after it — criterion 7 undone, while every check stayed green. Nothing caught it because the style tests assert stylesheet text and the browser specs never look at that field. So add the structural guard: every class this stylesheet declares must be rendered by some component. It resolves names built by interpolation (`state-${...}`) and ignores classes the read-only slices declare. Verified against the defect — dropping the class again fails with exactly ['paid-api-panel']. Also from the same review: - Remove the .recovery-panel rules. They targeted a cabinet section develop replaced with PaymentProtectionPanel, which lays its own controls out; the markup they addressed no longer exists. - Bind --os-on-field-muted. It was defined and audited for contrast but had no consumer once the enumerated overrides gave way to token rebinding, so the lime field had no muted ink. .paid-api-status now rebinds to it. Four classes dead before this branch (brand-logo, fixture-toolbar, meta-code, workspace-fact-grid) are recorded in the guard rather than removed here, so it ratchets instead of pulling unrelated rules into this change. * feat: add user-funded Circle x402 payments * test: authorize paid API fixture before claim * test: select claimed paid API attempt deterministically * fix(web): clarify payment service labels (#102) * Update copyright holder in LICENSE file * fix(api): accept empty activity refresh body (#103) * fix(web): restore the settlement link and fade late panel content The ArcScan link in a request's settlement box was invisible in the light theme. `.job-list li` rebinds --os-accent-ink to the page ink for the card around it, but the settlement box inside repaints --os-panel, so the link rendered forest ink on the forest box. It read correctly in dark, which is how it shipped. The box now rebinds the ink tokens its own descendants inherit, the same way `.paid-api-status` does for the lime field, and the anchor is underlined so it still reads as a link. Tab panels appeared instantly while the strip above them faded, for two separate reasons. The console panel was never wrapped in `.tab-fade` and had no key at all. The cabinet panel was wrapped, but the request list arrives from the API after mount, so the 0.24s fade ran on the "Checking requests…" placeholder and was over before any row existed. The console panel is now keyed on its tab, and the list body is keyed on its loading state, so the fade runs on the content the operator actually waited for. * fix(circle): allow normal user wallet approval delay * fix: recover user-wallet payments safely * feat(web): add user-funded Circle Gateway deposits * fix(web): bind payments to Privy wallet (#108) Ignore external active wallets when a Privy embedded wallet is available, and fail closed when it is not. * fix: widen Circle user-wallet authorization window * docs: record Circle validity handoff * docs: record merged Circle validity candidate * fix(web): restore Privy wallet picker (#109) Restore the Privy-controlled payment selection path removed by the previous payer binding fix. Scope remembered picker wallets to the signed-in Privy user so account changes cannot reuse a payer. * docs: record latest develop sync * fix(web): update website logo (#111) * fix(web): pay with the actively selected wallet (#113) Payments bind to the active wallet instead of silently preferring the Privy embedded one; the picker (detected wallets + WalletConnect) opens only when nothing is active. Raw eth_signTypedData_v4 payloads declare EIP712Domain, which external wallets such as MetaMask require. * fix(web): keep the dedicated Privy payer (#114) * fix(web): pay with the actively selected wallet Payments bind to the active wallet instead of silently preferring the Privy embedded one; the picker (detected wallets + WalletConnect) opens only when nothing is active. Raw eth_signTypedData_v4 payloads declare EIP712Domain, which external wallets such as MetaMask require. * fix(web): keep the dedicated Privy payer MetaMask and other detected wallets authenticate through the picker, while the Privy embedded wallet stays the dedicated payer for x402 signing and transfers; payment requests intentionally never reach MetaMask. Restores the selection logic superseded by the previous iteration and keeps its EIP712Domain signing fix. * feat: persist paid API requests in request list * fix(web): route payment approval by wallet type (#116) The Privy embedded wallet signs the legacy EIP-712 payload its signer accepts, while external wallets receive the EIP712Domain declaration they validate. An actively selected browser wallet stays the payer behind an explicit confirmation; otherwise payments fall back to the automatic embedded payer without silent wallet switching. * fix(settlement): remove recipient allowlist (#117) * fix(web): pay console jobs through the server wallet (#118) Privy login authenticates the operator only; the browser wallet is no longer wired into the console. Report and paid-API approvals route to the server-privy path, so the execution wallet settles without browser confirmation or 2FA. Restores the pre-user-wallet payment behavior. * remove: retire Circle paid API product * test: restore postgres cleanup dependencies * test: assert direct arc provider identity * fix(web): restore user-wallet payment preparation * feat: add Arc payment MCP endpoint (#120) * plan: personal MCP and Privy agent payments (#112) * docs: plan personal MCP payments * docs: restructure the MCP arc_payment plan One remote MCP tool first (arc_payment) on the existing policy-bound execution wallet and Arc settlement worker; personal wallets stay a separately gated milestone. Tasks carry dependencies, concrete work, and exit conditions. * feat(api): add the arc_payment MCP tool One bearer-authenticated /mcp endpoint exposes a single arc_payment tool bound to one workspace, request key, and a 1 USDC atomic cap. Payments create or replay a durable intent settled by the worker through the policy-bound Privy execution wallet; the Cloudflare worker proxies the streamable HTTP transport. * feat(web): add the MCP docs page A static /docs/mcp page documents the MCP configuration, the arc_payment tool, the 1 USDC cap, replay semantics, and ArcScan verification. The hero links to it, and local Vite proxies /mcp to the API. The page stores no credentials and sends no payments. * docs: add the downloadable arc_payment agent skill * docs: link the PR instead of a bare URL * fix(web): resolve Privy wallet payment selection * feat: add personal MCP credentials (#122) * feat: add personal MCP credentials * test: advance migration expectations * docs: finalize MCP access rollout * docs: repair the MCP access context record * fix(web): clarify direct payment copy (#125) * feat: capture Graph evidence for every settlement (#124) * feat: capture graph evidence for every settlement * docs: record graph evidence PR handoff * test: align migration expectations with graph evidence 012 * test: capture valid graph evidence in the production runtime fixture --------- Co-authored-by: Matvii Nesterenko <51422901+kapustazh@users.noreply.github.com> * fix(web): polyfill Buffer for Privy transfers (#126) * fix(web): polyfill Buffer for Privy transfers * docs(context): record Privy buffer rollout * feat(web): add MCP profile shortcuts (land #127 on develop) (#129) * feat(web): add MCP profile shortcuts * docs(context): record MCP profile rollout * fix(mcp): let agent generate request key (#130) The deployment-wide ONESHOT_MCP_REQUEST_KEY allowlist forced manual key copying and rejected agent-generated keys. Callers now generate and retain the request key (report-<purpose-slug>-<8 hex>) while the field stays required, so replay identity and conflict detection are intact. The obsolete one-intent demo quota is removed; worker settlement caps and Privy policy remain authoritative. * fix(web): auto-verify user wallet payments * feat(recovery): backfill Graph evidence and brand proof UI (#131) Historical committed settlements never received a Graph capture job, so their evidence card read as a flat NOT REPORTED. A new append-only migration 013 enqueues one idempotent capture_graph_evidence job per settlement that has neither Graph evidence nor a pending capture job. Missing Graph copy now distinguishes CAPTURE PENDING (committed) from NOT YET REPORTED (non-terminal) instead of masking absent data, and both proof surfaces adopt the shared Rubik/mono typography, brand radii, pill actions, and lime summary tokens with light/dark awareness. * feat: route MCP payments through user wallets * fix: normalize MCP payload conflicts * test: align MCP conflict assertion * fix(web): resume supplier results and copy wallet address * fix(web): avoid repeated resume result polling * fix(web): wait for resumed supplier result * feat: add MCP wallet signing handoff link * fix(web): remove automatic resume polling * fix: surface Graph evidence for site payment outcomes * docs: refresh README and add front-end derived banner README had drifted 23 commits behind develop. Bring it back to the shipped surface and give it the product's own nav panel as a banner. Documentation corrections: - Add packages/brand to the repository layout and describe apps/api's MCP endpoint and personal-credential role. - Replace the retired four-tab console and "Tools" section with the five cabinet sections that exist today, and document the /docs/mcp route. - Add the user-wallet, MCP, and profile-credential routes to the API table, and note that the transport and operator routes sit outside the frozen v1 contract pack. - Describe the non-custodial MCP payment flow and digest-stored personal bearers in a new Agent access section. - Record that a browser caller's workspace is derived from its verified Privy subject, and that every committed settlement captures Graph evidence through a durable outbox job with a backfill for older settlements. - Repair a duplicated sentence fragment in Project status and add the user-wallet and MCP rows to the status table. The banner is not hand-drawn. scripts/render-nav-panel.mjs reads the palette from packages/brand/src/tokens.css, the mark geometry from CommitRing.tsx, and the nav labels from apps/web/src/App.tsx, then emits one SVG per theme. GitHub strips CSS from Markdown, so the README selects between them with a <picture> element. tokens.css remains the only source of a brand colour. * fix(web): re-read a pending delivery until the worker reports the result Supplier delivery finishes in the worker, after the browser has already read the request list, so a request whose delivery was still PENDING at read time kept saying "Retrieving result" until someone pressed refresh — long after the result was durably available. Whether it looked right depended only on whether the worker beat the page load, which is why it worked intermittently. The list now re-reads itself while any delivery is PENDING: every four seconds, fifteen attempts, then it stops and leaves the manual refresh as the way to look again. The reads are GET /v1/jobs only. They never call the resume endpoint and never submit a payment, so at-most-once settlement is unaffected, and they do not raise the loading flag, because the spinner, the disabled button, and the tab fade belong to a read the operator asked for. This partially reverses a2903a1, which removed automatic polling after a report of unwanted traffic. The bound and the pending-only condition keep both reports satisfied; the resume control that commit removed stays removed. The browser spec asserted an exact list-read count, which a timed re-read makes timing-dependent; it now asserts a lower bound and still asserts that no resume request is sent. * fix(jobs): recover a delivery stranded in PENDING A committed job whose delivery reached PENDING could be left with nothing able to move it. The worker no-ops a payload whose delivery_attempt no longer matches and then marks the outbox row DELIVERED, and resumeDelivery re-queued only NOT_REQUESTED or RETRIEVAL_FAILED, so the job kept a committed payment, an unretrieved result, and no path forward. PENDING alone is still not treated as resumable. The deciding evidence is whether a fulfill_supplier_order row is still queued for that job: a row a worker currently holds is status PENDING and so counts as queued, which means an in-flight retrieval is never duplicated. Only a job with no such row left is re-queued, under a fresh delivery_attempt that fences the old retrieval. The claim is a compare-and-set against the state read under the job's FOR UPDATE lock, so two concurrent resumes cannot both take one delivery. No payment work is created on this path; the committed settlement is untouched. * docs: reduce the README banner to the brand lock-up The full-width nav strip carried the network badge, token badge, theme control and workspace link into a README where none of them mean anything, and at README width the whole row rendered small. Keep only the brand lock-up — the commit-ring mark, the wordmark, and its SETTLEMENT ENGINE tag — on the same rounded panel, and scale every length by one factor so the proportions stay exactly those of `.top-nav`. The banner is still generated, not drawn: the palette comes from tokens.css, the mark geometry from CommitRing.tsx, and both labels from App.tsx. The canvas is 381x112 and sized to its own content, so the tag clears the panel edge with the fallback fonts GitHub renders. * Add centered picture element to README Updated the README to include a centered picture element for the brand lock-up. * fix(activity): scope indexed transfers to the workspace The Graph activity query is sent with the shared server wallet alongside the workspace's user wallets, so a stored observation also carries transfers made for other workspaces. Settlement and uncertain counts were SQL-scoped to the workspace while the unmatched transfer count was taken from the whole observation, so Payment proof reported one workspace's 11 settlements next to 40 unmatched transfers drawn from every workspace. Filter the observed transfers to the ones this workspace can own before they are matched, listed or counted: the sender is a workspace payer wallet, or the transaction hash is already recorded on a workspace settlement, job or attempt. The attempt hash keeps a stranded server payment visible as unmatched evidence. * docs: require min-instances and no-cpu-throttling for the worker The worker drains the outbox on its own timer rather than per request, so it only works while its process is alive and holding CPU. The documented deploy omitted both flags, and a deployment without them fails quietly: payments still settle through the API, but supplier deliveries sit in PENDING and requests stay on "Retrieving result". The failure is intermittent, which makes it easy to misdiagnose as a UI or state-machine fault. Any request that reaches the service boots an instance, and startup drains the backlog before the timer takes over, so the queue appears to clear itself and then stalls again. --------- Co-authored-by: SuPuHe <mizin.a.s.2006@gmail.com> Co-authored-by: Artem <47925608+selezenart@users.noreply.github.com> Co-authored-by: selezenart <artvs14@gmail.com>
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.
Summary
Privy transaction approval reached a browser SDK path that referenced Node's
Buffer, so the transfer failed after approval withBuffer is not defined.The web entry now installs the existing
bufferpolyfill before Privy's lazywallet module loads.
Scope and acceptance criteria
globalThis.Bufferbefore wallet code runs.Product and security invariants
Invariant notes: this frontend-only compatibility fix does not change Business
Intent identity, settlement ownership, retry behavior, network, token, amount,
recipient validation, or Privy authorization.
Validation
Local Node 22.23.2 differs from the repository's requested Node 24.19.0; CI
validates the pinned runtime.
Independent review evidence
Gate A — exact candidate tree before push
5e1c9e9210ef22416ee8b62713e9a3e597bb457748cf89bfb0071076eacd742bb8c5ab3c820f75d44721961d7e55047d120e2d4e51dc3a40b4d520bbGate B — exact remote PR head
Risk and rollback
4721961d7e55047d120e2d4e51dc3a40b4d520bb.Human merge