From e8843a7073e5f5449a7339598b8924dbaad208ff Mon Sep 17 00:00:00 2001 From: James Waugh Date: Wed, 4 Nov 2026 22:03:11 -0500 Subject: [PATCH] SDK 0.5.1: platform pin 2223d11, app-access audit --- .github/workflows/ci.yml | 4 ++-- README.md | 16 ++++++++-------- apps/server/package.json | 2 +- docs/adr/0002-owner-ui-app-access.md | 20 ++++++++++---------- pnpm-lock.yaml | 14 +++++++------- pnpm-workspace.yaml | 6 +++--- scripts/stack.ts | 6 +++--- web/PLATFORM-SESSION.md | 4 ++-- web/package.json | 2 +- web/scripts/app-access-audit.ts | 6 ++++-- web/test/app-access-audit.test.ts | 14 ++++++++++---- web/test/stack.e2e.ts | 4 ++-- 12 files changed, 53 insertions(+), 45 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8876984..4775a16 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,8 +47,8 @@ jobs: with: persist-credentials: false repository: MultiAgency/platform - # The commit @multiagency/sdk 0.5.0 was published from (tag sdk-v0.5.0); startStack checks it. - ref: 6ba0831f18ca84b2093187750f1a5f0cc8ed14d9 + # The commit @multiagency/sdk 0.5.1 was published from (tag sdk-v0.5.1); startStack checks it. + ref: 2223d11995042ee62d9006ff2e6520988b4e0ea5 path: platform - uses: pnpm/action-setup@73e46992e05b19abd31f2f3c20e0e7370d762115 # v6.1.1 - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 diff --git a/README.md b/README.md index e296bc4..92bca0a 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ # treasury -The Treasury app's owner UI, built on [`@multiagency/sdk`](https://www.npmjs.com/package/@multiagency/sdk) 0.5.0: the owner's Treasury pages, replacing the prototype's, in the session the owner signs in to with their wallet (`/a//`, `ownerClient` same-origin). It lives in `web/` ([below](#the-owner-ui)). Beside it, `apps/server/` is the app's own server on the app route ([below](#the-app-server), #20): the only way to reach Treasury proposals. +The Treasury app's owner UI, built on [`@multiagency/sdk`](https://www.npmjs.com/package/@multiagency/sdk) 0.5.1: the owner's Treasury pages, replacing the prototype's, in the session the owner signs in to with their wallet (`/a//`, `ownerClient` same-origin). It lives in `web/` ([below](#the-owner-ui)). Beside it, `apps/server/` is the app's own server on the app route ([below](#the-app-server), #20): the only way to reach Treasury proposals. -The owner UI moves to the app's own routes on the platform (`/apps/v1`): the owner session gets no production host ([MultiAgency/platform#14](https://github.com/MultiAgency/platform/issues/14)). Most of its calls move by changing its one platform module, `web/src/platform.ts`; the rest are served only by the owner session, and which of them reach app access is [MultiAgency/platform#17](https://github.com/MultiAgency/platform/issues/17). SDK 0.5.0 adds app access for DAO-owned agents (consent, setup and owner actions by the DAO's vote); it isn't on any host yet, and this repository doesn't use it yet ([MultiAgency/platform#13](https://github.com/MultiAgency/platform/issues/13)). +The owner UI moves to the app's own routes on the platform (`/apps/v1`): the owner session gets no production host ([MultiAgency/platform#14](https://github.com/MultiAgency/platform/issues/14)). Most of its calls move by changing its one platform module, `web/src/platform.ts`. SDK 0.5.1 answers which of the rest reach app access ([MultiAgency/platform#17](https://github.com/MultiAgency/platform/issues/17)); [ADR 2](docs/adr/0002-owner-ui-app-access.md) gives the replacement for each one that doesn't. The hosts serve them once the platform deploys that code. -This repository had a CLI on Treasury's machine route (`/machine/v1`). It was removed in favour of the app route: the SDK's [getting-started](https://github.com/MultiAgency/platform/blob/sdk-v0.5.0/src/contract/skills/getting-started/SKILL.md) skill says the machine route isn't extended and is retired once Treasury moves to app access, and that a new app uses `appClient`. `watch` (#2) comes back as the app server's proposer (#20). +This repository had a CLI on Treasury's machine route (`/machine/v1`). It was removed in favour of the app route: the SDK's [getting-started](https://github.com/MultiAgency/platform/blob/sdk-v0.5.1/src/contract/skills/getting-started/SKILL.md) skill says the machine route isn't extended and is retired once Treasury moves to app access, and that a new app uses `appClient`. `watch` (#2) comes back as the app server's proposer (#20). -Both run against the platform's simulated stack at [`6ba0831`](https://github.com/MultiAgency/platform/commit/6ba0831) (tag `sdk-v0.5.0`, the commit SDK 0.5.0 was published from). Its provider, chain, signatures and settlement are simulated. Pointing at a hosted platform is a separate decision. Requires Node ≥ 24.22 and pnpm 12.16, and the platform checked out beside this repository: +Both run against the platform's simulated stack at [`2223d11`](https://github.com/MultiAgency/platform/commit/2223d11) (tag `sdk-v0.5.1`, the commit SDK 0.5.1 was published from). Its provider, chain, signatures and settlement are simulated. Pointing at a hosted platform is a separate decision. Requires Node ≥ 24.22 and pnpm 12.16, and the platform checked out beside this repository: ```sh -git clone https://github.com/MultiAgency/platform.git ../platform && git -C ../platform checkout 6ba0831 +git clone https://github.com/MultiAgency/platform.git ../platform && git -C ../platform checkout 2223d11 (cd ../platform && pnpm install --frozen-lockfile) pnpm install ``` @@ -20,7 +20,7 @@ SDK and platform feedback goes to [MultiAgency/platform](https://github.com/Mult ## The app server -`apps/server/` is its own package in the pnpm workspace: Node running TypeScript directly, `node:http`, `@multiagency/sdk` 0.5.0 and `near-kit` 0.20.2. It holds the deployment's `tap_` credential and calls the platform with `appClient`. Its design, rules and plan are in #20. The first version so far: +`apps/server/` is its own package in the pnpm workspace: Node running TypeScript directly, `node:http`, `@multiagency/sdk` 0.5.1 and `near-kit` 0.20.2. It holds the deployment's `tap_` credential and calls the platform with `appClient`. Its design, rules and plan are in #20. The first version so far: - **Owner sign-in:** a NEP-413 challenge addressed to this server, verified with `near-kit` before any chain call, then a full-access key of the signing account on chain (FastNEAR's RPC, configured as the platform's `src/chain.ts`). Challenges are stateless (the nonce carries its issue time and an HMAC under a per-process key), so issuing one stores nothing and a flood can't crowd out an owner's; each is single-use once validly signed (only its canonical base64 text is accepted) and expires in 5 minutes. Challenges and sign-in attempts are each rate-limited per client (an IPv4 address, or an IPv6 /64). - **Sessions:** a `Secure`, httpOnly, `SameSite=Strict` cookie, ended after 30 minutes idle, 12 hours or sign-out, and swept when abandoned; in memory, so a restart signs everyone out. Signing in again ends the previous session, and sign-out always clears the cookie, with or without a body. Every write checks `Origin` and requires JSON. @@ -48,7 +48,7 @@ To run it, set `TREASURY_PLATFORM_ORIGIN`, `TREASURY_DEPLOYMENT`, `TREASURY_CRED The app-access migration's target architecture, contract gaps and acceptance steps are in [ADR 2](docs/adr/0002-owner-ui-app-access.md). Run `pnpm --filter @treasury/web audit:app-access` to compare current owner UI calls with the installed SDK's consent scopes. The owner-session build below remains the simulated-stack reference until that migration is complete. -`web/` is its own package in the pnpm workspace: React 19, TanStack Router and Query, rsbuild, Tailwind 4 and shadcn, on `@multiagency/sdk` 0.5.0 only. Nothing is imported from the platform's or the prototype's source. +`web/` is its own package in the pnpm workspace: React 19, TanStack Router and Query, rsbuild, Tailwind 4 and shadcn, on `@multiagency/sdk` 0.5.1 only. Nothing is imported from the platform's or the prototype's source. - **One platform module.** `web/src/platform.ts` makes every call: the gateway's session routes, and each agent's reads, actions and streams. Reads are cached and may retry (`reads`). An action or stream returns its result, decided by `kind`, and is never sent again. Screens import only from it. - **Features port one at a time** from MultiAgency/treasury-prototype's `web/src`, each in its own issue. A page not ported yet shows a placeholder where its feature will land. @@ -65,4 +65,4 @@ pnpm --filter @treasury/web test:stack # builds, then drives the owner UI on a The stack test starts the platform's stack with its browser, whose owner page starts signed in, and attaches to it over CDP. It covers sign-in and out across tabs, the redirects, an outage of the agent's provider (setup unavailable, then recovered), the keyboard paths, and an axe scan (WCAG A and AA) of every page it opens. -CI (`.github/workflows/ci.yml`) runs the app server's typecheck and unit tests and the owner UI's typecheck, unit tests and build on every pull request and push to `main`, then both stack tests on a stack from the platform at 6ba0831. Locally, the stack tests need `../platform` (or `TREASURY_PLATFORM`) at 6ba0831 with its dependencies installed. +CI (`.github/workflows/ci.yml`) runs the app server's typecheck and unit tests and the owner UI's typecheck, unit tests and build on every pull request and push to `main`, then both stack tests on a stack from the platform at 2223d11. Locally, the stack tests need `../platform` (or `TREASURY_PLATFORM`) at 2223d11 with its dependencies installed. diff --git a/apps/server/package.json b/apps/server/package.json index 33ebb4f..2073502 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -14,7 +14,7 @@ "test:sandbox": "node --test test/sandbox.e2e.ts" }, "dependencies": { - "@multiagency/sdk": "0.5.0", + "@multiagency/sdk": "0.5.1", "near-kit": "0.20.2", "zod": "^4.6.6" }, diff --git a/docs/adr/0002-owner-ui-app-access.md b/docs/adr/0002-owner-ui-app-access.md index 153a74c..d97469d 100644 --- a/docs/adr/0002-owner-ui-app-access.md +++ b/docs/adr/0002-owner-ui-app-access.md @@ -1,6 +1,6 @@ # 2. Move owner pages behind the Treasury app server -**Status:** accepted, 2026-11-05. The first steps landed in #118 (owner reads and a read-only portfolio through the app server). SDK 0.5.1 settles platform#17: the table below is the 0.5.0 inventory, and the upgrade rewrites it. +**Status:** accepted, 2026-11-05. The first steps landed in #118 (owner reads and a read-only portfolio through the app server). SDK 0.5.1 answers platform#17; the gaps below are as of 0.5.1. Replaces the production destination in [ADR 1](0001-owner-session-and-app-server.md). Keep the existing owner-session build as the simulated-stack reference until its replacement passes equivalent workflows. @@ -8,7 +8,7 @@ Replaces the production destination in [ADR 1](0001-owner-session-and-app-server The Treasury app server serves owner pages and proposals from one origin. Its existing wallet sign-in, session, per-agent ownership checks and DAO membership checks gate every API route. Only the server calls `/apps/v1`; deployment credentials never reach browser code. The browser uses the existing server session rather than the platform's `/gw/*` session. -Add explicit, schema-checked endpoints per supported workflow, each checking the required member permission and platform consent. Connecting an agent must not silently add consent scopes. The existing proposals connection requests only `read` and `treasury`; wallet owner actions need an explicit consent change for `owner`, and deposit needs `payout`. DAO consent cannot include those scopes in SDK 0.5.0. SDK 0.5.1 puts `deposit` in the `treasury` scope, so funding needs no `payout` consent, for wallet and DAO owners alike. +Add explicit, schema-checked endpoints per supported workflow, each checking the required member permission and platform consent. Connecting an agent must not silently add consent scopes. The existing proposals connection requests only `read` and `treasury`, which covers funding (`deposit`) for wallet and DAO owners alike. Wallet owner actions need an explicit consent change for `owner`, which a DAO's consent can't include. Wallet NEP-366 actions open the platform's confirm page and follow their status. DAO owner actions use the SDK's DAO preparation/vote flow. Neither uses the owner-session submit flow as a generic replacement. Actions and streams remain single-attempt; follow an interrupted action with observations rather than replaying it. @@ -16,22 +16,22 @@ Wallet NEP-366 actions open the platform's confirm page and follow their status. Run `pnpm --filter @treasury/web audit:app-access` to inventory current literal `.read`, `.act` and `.stream` call sites against the installed SDK's scope rules. This is a source inventory, not proof that every flow works; dynamic calls and gateway routes require separate review. A unit test tracks the unsupported procedure set so an SDK or UI change triggers a migration review. -At SDK 0.5.0, seven procedures used by the owner UI are absent from every app scope: +At SDK 0.5.1, `reconcileBooks` is under `read`. Three procedures the owner UI uses are absent from every app scope, and a DAO's consent is refused three more: | Procedure | Required treatment | | --- | --- | -| `setup` | Replace with identity, instance and overview after the missing agent wallet and executor-held grant ID have a supported observation. Never infer the held grant from the grants list. Grant adoption is also a platform concern. | -| `recover` | Replace the manual trigger in the app-access UI with reads of `unresolved` while the platform reconciles automatically, as decided in platform#14. | -| `reconcileBooks` | Await the platform's read-scope decision; ledger records cannot substitute for independent reconciliation evidence. | -| `treasury/link`, `treasury/unlink`, `treasury/setConfidential`, `treasury/fund` | Await explicit scope and DAO authorization decisions. | +| `setup` | Read `account` (`read`): the agent's wallet, the grant its executor holds and any staged grant, matched by the token, never inferred from the grants list. Owner and signer come from `GET /apps/v1/agents/`. It adopts nothing: a staged grant takes effect once its owner action settles. | +| `recover` | Remove the manual trigger and follow `unresolved`, which the platform reconciles on its own (platform#14). After a lost reply, read the action's own record; never resend while it's unknown. | +| `treasury/fund` | `deposit` (`treasury`) for the address, then the SDK's `fundingTransaction()`: the owner's wallet sends it, or a DAO member files the same calls as one proposal. | +| `treasury/link`, `treasury/unlink`, `treasury/setConfidential` | Under `treasury` for wallet owners. Refused under a DAO's consent: a DAO agent's link is the DAO, fixed at setup, so its page hides the controls. | -The planned Runs/manual-rebalance/steward work additionally needs decisions for `autopilot/tick` and `steward/propose`, and explicit `treasury-execute` consent for execution without per-swap signatures. Creation uses app `setup` or `daoSetups`, followed by wallet confirmation or a DAO vote, rather than `/gw/agents`. +Run now is `autopilot/tick` under `treasury`; offer it only when the held grant binds the saved strategy, or the tick swaps nothing. Suggested swaps come from Treasury's own agent through `proposals/create`; `steward/propose` stays off app access. Execution without per-swap signatures needs explicit `treasury-execute` consent. Creation uses app `setup` or `daoSetups`, followed by wallet confirmation or a DAO vote, rather than `/gw/agents`. -Sources checked on 2026-11-04: [platform#14 decision](https://github.com/MultiAgency/platform/issues/14), [platform#17 open contract decisions](https://github.com/MultiAgency/platform/issues/17), and the installed SDK's exported scope definitions. The SDK is the executable contract. +Sources checked on 2026-11-05: [platform#14 decision](https://github.com/MultiAgency/platform/issues/14), [platform#17](https://github.com/MultiAgency/platform/issues/17) as answered by SDK 0.5.1 (platform `2223d11`), and the installed SDK's exported scope definitions. The SDK is the executable contract. ## Implementation order and acceptance -1. Resolve frame observations and scope decisions in the platform; publish the SDK and update this repository's SDK/stack pin together. +1. Platform decisions, the SDK and this repository's SDK/stack pin: done with 0.5.1. The hosts answer these procedures only once the platform deploys that code. 2. Add explicit owner observation endpoints. Test ownership transfer, DAO roles, cross-organization isolation, missing consent and malformed upstream responses. Consolidate page reads to respect the deployment's shared 1,200-call hourly allowance. 3. Move browser discovery and owner reads to the server session. Preserve retained-record recovery and clear private caches when account or access changes. 4. Port owner actions, strategy and funding with consent review and wallet-confirm/DAO-vote outcomes. Required workflows must be supported before declaring the migration complete. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f691b5f..5fdefe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,8 +169,8 @@ importers: apps/server: dependencies: '@multiagency/sdk': - specifier: 0.5.0 - version: 0.5.0(zod@4.6.7) + specifier: 0.5.1 + version: 0.5.1(zod@4.6.7) near-kit: specifier: 0.20.2 version: 0.20.2 @@ -209,8 +209,8 @@ importers: specifier: ^5.3.0 version: 5.3.0 '@multiagency/sdk': - specifier: 0.5.0 - version: 0.5.0(zod@4.6.7) + specifier: 0.5.1 + version: 0.5.1(zod@4.6.7) '@phosphor-icons/react': specifier: ^2.1.10 version: 2.1.10(react-dom@19.3.1(react@19.3.1))(react@19.3.1) @@ -496,8 +496,8 @@ packages: resolution: {integrity: sha512-rgR9yxSaAJ+bcsi/XroT/S2AfPiIWloXAnrdfYSCms9ivVju0ishu/wW0J21Ho6+ZOR1r7RyeRbjiRuF87ix5Q==} engines: {node: '>=20'} - '@multiagency/sdk@0.5.0': - resolution: {integrity: sha512-vfCpI9ZhCIrKgPsvPhO4bzbiidHC6TuwvcENxwX2Vs5rMGQnK7q22ec33U80vkS50qvlKZL8cTSANRNkipCldg==} + '@multiagency/sdk@0.5.1': + resolution: {integrity: sha512-5XjmtGYGW3HDWT2US3lVc5EPTfwrFrWTNx7ZCNui59+mRVbC8ksH1sDkh1UXAifqkrk4QSivg2DZxzvC86SMhA==} hasBin: true peerDependencies: zod: ^4.6.5 @@ -3063,7 +3063,7 @@ snapshots: '@modelcontextprotocol/core': 2.4.0 zod: 4.6.7 - '@multiagency/sdk@0.5.0(zod@4.6.7)': + '@multiagency/sdk@0.5.1(zod@4.6.7)': dependencies: '@ag-ui/core': 1.0.3(zod@4.6.7) '@orpc/contract': 2.0.0-beta.47 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0cf9b2b..6ae29d5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,10 +2,10 @@ packages: - web - apps/* - packages/* -# Published 2026-11-04T19:20:52Z, inside pnpm's minimum release age (24 hours here). -# Remove after 2026-11-05T19:21Z, once 0.5.0 is past it. +# Published 2026-11-05T02:44:20Z, inside pnpm's minimum release age (24 hours here). +# Remove after 2026-11-06T02:45Z, once 0.5.1 is past it. minimumReleaseAgeExclude: - - '@multiagency/sdk@0.5.0' + - '@multiagency/sdk@0.5.1' permissions: '@multiagency/sdk': skills: true diff --git a/scripts/stack.ts b/scripts/stack.ts index 816214d..60f971b 100644 --- a/scripts/stack.ts +++ b/scripts/stack.ts @@ -1,4 +1,4 @@ -// The platform's simulated stack (`pnpm test:e2e:stack --no-browser --treasury` at 6ba0831), through its state file and +// The platform's simulated stack (`pnpm test:e2e:stack --no-browser --treasury` at 2223d11), through its state file and // control files only: never through its internals. import { execFileSync, spawn } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, writeFileSync } from "node:fs"; @@ -6,8 +6,8 @@ import { request } from "node:https"; import { join } from "node:path"; import { setTimeout as delay } from "node:timers/promises"; -/** The platform commit the stack runs from: the one `@multiagency/sdk` 0.5.0 was published from (tag `sdk-v0.5.0`). */ -export const PLATFORM_COMMIT = "6ba0831"; +/** The platform commit the stack runs from: the one `@multiagency/sdk` 0.5.1 was published from (tag `sdk-v0.5.1`). */ +export const PLATFORM_COMMIT = "2223d11"; export const USDC = "nep141:17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1"; export const WNEAR = "nep141:wrap.near"; diff --git a/web/PLATFORM-SESSION.md b/web/PLATFORM-SESSION.md index ad6f971..252001a 100644 --- a/web/PLATFORM-SESSION.md +++ b/web/PLATFORM-SESSION.md @@ -234,7 +234,7 @@ A second build (`src/proposals/`, built to `dist-proposals/`), served only by th How it departs from the brief, or decides what the brief leaves open: - **The agent comes from the address** (`/agents/`). `/` lists the signed-in account's own treasuries (`GET /api/setup`, the agents its consents name under its Organization), and still opens an agent by name, for one a DAO owns. -- **Setup (#12) is the app server's**, through `/apps/v1/setup` and `/apps/v1/dao-setups`, not the owner session's `/gw/agents`. An account with no Treasury signs the server's setup text; the agent's page then offers its creation (`agent_create` under the setup consent), signed on the platform's confirm page, or for a DAO filed for its vote. The setup consent lasts an hour; after that, creating asks for the setup again. The stack sets up Payables agents only (`hostedAreas: ['payables']` at `6ba0831`), so setup and creation are covered by the server's unit tests, not a stack test. +- **Setup (#12) is the app server's**, through `/apps/v1/setup` and `/apps/v1/dao-setups`, not the owner session's `/gw/agents`. An account with no Treasury signs the server's setup text; the agent's page then offers its creation (`agent_create` under the setup consent), signed on the platform's confirm page, or for a DAO filed for its vote. The setup consent lasts an hour; after that, creating asks for the setup again. The stack sets up Payables agents only (`hostedAreas: ['payables']` at `2223d11`), so setup and creation are covered by the server's unit tests, not a stack test. - **The portfolio is read-only (#7 on `/apps/v1`).** It shows the owner reads (#61) once the agent is connected: holdings with exact amounts, what needs attention, the latest 20 activity rows and 10 autopilot runs. Rebalancing, plans, the steward and the inbox aren't here: they need `treasury-execute` (which this app never asks for) or procedures no app scope covers yet (platform#17). Each read is a call from the deployment's shared 1,200 an hour, so the page reads them once and again only on "Check again". - **Signed in or not** is read from `GET /api/agents/:agent` on an agent's pages, and from `GET /api/setup` or `GET /api/setup/dao/:dao` on the others: the server has no session route. `not_signed_in` there shows Sign in; another read answering `not_signed_in` later reads the identity again, so the page returns to Sign in. - **The wallet's account is checked before relaying.** A consent or verdict signed by another account than the agent's owner is not sent: "Your wallet signed as ``, but this agent's owner is ``. Nothing was sent." The server would refuse it anyway (`not_owner`, `signature_invalid`). @@ -267,7 +267,7 @@ Driving the page on the stack (`test/proposals.e2e.ts`, its own stack started wi - A page that connection creates (`context.newPage()`) doesn't share the owner's session, and `context.pages()[0]` isn't reliably the owner's page. Find the owner's page by its URL (`/a/payout/`). - A tab opened from the owner's page (`window.open`) shares the session cookie but not the stack's test wallet. Wallet steps run in the owner's page itself. -What the stack (platform 6ba0831) can't show for `/policy`, so `web/test/stack.e2e.ts` stops short: +What the stack (platform 2223d11) can't show for `/policy`, so `web/test/stack.e2e.ts` stops short: - **Revoking a grant.** The simulated provider (`test/e2e/provider.mjs`) has no `grant_revoke`: `owner/prepare` answers 500, an unknown outcome to the SDK. The test reviews the revoke in its dialog, prepares it once, and checks the unknown outcome is shown and the grants are unchanged. A signed revoke needs a `grant_revoke` branch in the simulator. - **Issuing a grant.** `portfolio` accepts only the strategy-bound grant (`label: "treasury"` with the saved strategy's digest), which the Strategy page signs (#7 PR 2). The test issues it through the `authorizeStrategy` control and checks `/policy` lists it under its label as the current grant. - **A NEP-366 policy edit.** `onChainPolicyEdits` is off for `portfolio` (`test/e2e/environment.mjs` starts its provider without it), so every policy edit there is NEP-413. The test drafts, reviews, signs and applies one; the NEP-366 path and the confirm page are covered by `test/owner.test.ts` only. diff --git a/web/package.json b/web/package.json index d06c3c2..eaff2a4 100644 --- a/web/package.json +++ b/web/package.json @@ -16,7 +16,7 @@ "@fontsource-variable/geist": "^5.3.0", "@fontsource-variable/geist-mono": "^5.3.0", "@fontsource-variable/inter": "^5.3.0", - "@multiagency/sdk": "0.5.0", + "@multiagency/sdk": "0.5.1", "@phosphor-icons/react": "^2.1.10", "@tanstack/react-query": "5.105.3", "@tanstack/react-router": "1.170.46", diff --git a/web/scripts/app-access-audit.ts b/web/scripts/app-access-audit.ts index 110b705..15eb387 100644 --- a/web/scripts/app-access-audit.ts +++ b/web/scripts/app-access-audit.ts @@ -2,7 +2,7 @@ import { readdirSync, readFileSync } from "node:fs"; import { join, relative } from "node:path"; import { pathToFileURL } from "node:url"; import { APP_READS, APP_OWNER, APP_PAYOUT, APP_TREASURY, APP_TREASURY_EXECUTE, - appScopesCover, DAO_CONSENT_SCOPES, type AppScope } from "@multiagency/sdk"; + appScopesCover, DAO_CONSENT_SCOPES, daoScopesCover, type AppScope } from "@multiagency/sdk"; const SCOPES: AppScope[] = ["read", "owner", "payout", "treasury", "treasury-execute"]; const procedures = [...new Set([...APP_READS, ...APP_OWNER, ...APP_PAYOUT, ...APP_TREASURY, ...APP_TREASURY_EXECUTE])]; @@ -11,6 +11,8 @@ export function scopesFor(procedure: string): AppScope[] { ? !appScopesCover(["treasury"], procedure) && appScopesCover(["treasury", scope], procedure) : appScopesCover([scope], procedure)); } +/** Whether a DAO's consent reaches the procedure: its scopes, less what the platform refuses DAO consents. */ +export const daoCovers = (procedure: string): boolean => daoScopesCover(DAO_CONSENT_SCOPES, procedure); /** Literal owner call inventory, using the same convention as contract.test.ts. */ export function callsIn(source: string): string[] { @@ -46,7 +48,7 @@ export function report(root: string): string { "", "| Procedure | Wallet consent scopes | DAO consent covers | Call sites |", "| --- | --- | --- | --- |", - ...[...calls].map(([path, sites]) => `| ${path} | ${scopesFor(path).join(", ") || "Unavailable"} | ${appScopesCover(DAO_CONSENT_SCOPES, path) ? "Yes" : "No"} | ${sites.join(", ")} |`), + ...[...calls].map(([path, sites]) => `| ${path} | ${scopesFor(path).join(", ") || "Unavailable"} | ${daoCovers(path) ? "Yes" : "No"} | ${sites.join(", ")} |`), "", `Missing from all app scopes (${missing.length}): ${missing.join(", ") || "none"}.`, "", diff --git a/web/test/app-access-audit.test.ts b/web/test/app-access-audit.test.ts index 00c4cee..0f953b7 100644 --- a/web/test/app-access-audit.test.ts +++ b/web/test/app-access-audit.test.ts @@ -1,7 +1,7 @@ import { test } from "node:test"; import assert from "node:assert/strict"; import { join } from "node:path"; -import { callsIn, inventory, scopesFor } from "../scripts/app-access-audit.ts"; +import { callsIn, daoCovers, inventory, scopesFor } from "../scripts/app-access-audit.ts"; test("the inventory handles multiline literal calls and deduplicates procedures", () => { assert.deepEqual(callsIn(`api.read(\n "overview", undefined); @@ -10,16 +10,22 @@ test("the inventory handles multiline literal calls and deduplicates procedures" api.read("overview", undefined);`), ["overview", "sweep", "treasury/fund"]); }); -test("execution needs both Treasury scopes and deposit needs payout consent", () => { +test("execution needs both Treasury scopes, and deposit is under payout or treasury", () => { assert.deepEqual(scopesFor("executePlan"), ["treasury-execute"]); - assert.deepEqual(scopesFor("deposit"), ["payout"]); + assert.deepEqual(scopesFor("deposit"), ["payout", "treasury"]); assert.deepEqual(scopesFor("setup"), []); }); +test("a DAO's consent reaches deposit and reconcileBooks, but not the treasury account's link or confidentiality", () => { + assert.ok(daoCovers("deposit") && daoCovers("reconcileBooks") && daoCovers("autopilot/stop")); + for (const procedure of ["treasury/link", "treasury/unlink", "treasury/setConfidential", "owner/prepare"]) + assert.ok(!daoCovers(procedure), procedure); +}); + test("owner UI migration gaps are explicit; SDK or call-site changes require reviewing this list", () => { const calls = inventory(join(import.meta.dirname, "..", "src")); assert.ok(calls.has("overview") && calls.has("owner/prepare") && calls.has("sweep")); assert.deepEqual([...calls.keys()].filter(path => scopesFor(path).length === 0).sort(), [ - "reconcileBooks", "recover", "setup", "treasury/fund", "treasury/link", "treasury/setConfidential", "treasury/unlink", + "recover", "setup", "treasury/fund", ]); }); diff --git a/web/test/stack.e2e.ts b/web/test/stack.e2e.ts index b75f965..042fd24 100644 --- a/web/test/stack.e2e.ts +++ b/web/test/stack.e2e.ts @@ -1,4 +1,4 @@ -// The owner UI on a fresh simulated stack (the platform at 6ba0831) that serves this build: `pnpm test:stack`. +// The owner UI on a fresh simulated stack (the platform at 2223d11) that serves this build: `pnpm test:stack`. // The stack's browser starts the owner signed in; these tests attach to it over CDP, as the platform's specs' clients do. import { after, before, describe, test } from "node:test"; import assert from "node:assert/strict"; @@ -336,7 +336,7 @@ test("a strategy-bound grant is listed under its label, as the current grant", a }); test("revoking the current grant: reviewed in the dialog, prepared once; the stack's provider fails it and the grant stays live", async () => { - // The stack's simulated provider has no grant_revoke (platform 6ba0831, test/e2e/provider.mjs): owner/prepare answers + // The stack's simulated provider has no grant_revoke (platform 2223d11, test/e2e/provider.mjs): owner/prepare answers // 500 there, so this follows the revoke up to that failure. A signed revoke can't be tested on this stack. await home.goto(`${dashboard}/a/portfolio/policy`); const current = home.getByRole("list", { name: "Live grants" }).getByRole("listitem").filter({ hasText: "Current" });