feat(sdk-wasm): multi-resource create_delegation#48
Merged
Conversation
Extend Session::create_delegation and the createDelegation WASM export to
accept the same multi-resource abilities shape as prepareSession:
HashMap<Service, HashMap<Path, Vec<Ability>>>. A single UCAN now encodes
every (service, path, actions) entry in one signed blob, scoped to a
single target space.
This fixes two concrete gaps in the previous single-resource API:
1. Service was hardcoded to "kv" in the Rust path — delegations for
sql, duckdb, hooks, or capabilities services were silently wrong
(the resource URI was built with the wrong service segment).
2. Callers that needed to delegate multiple (service, path) pairs had
to issue N separate UCANs, which is both wrong semantically (the
user model is one delegation per recipient) and forces N separate
wallet prompts in the legacy wallet-signed fallback path.
The DelegationResult now returns a sorted `resources` vec of
{service, space, path, actions} entries describing every capability
the UCAN grants. Sort order is (service, path) lexicographic so tests
and JS consumers get deterministic output regardless of HashMap
iteration order.
Validation errors (empty abilities map, empty path map for a service,
empty action list under a path) surface as typed DelegationError
variants rather than encoding a useless UCAN.
Tests (11 total in session::test, all green):
- create_delegation_single_resource_backward_compat: one service/path
still round-trips cleanly
- create_delegation_multi_service_same_path: KV + SQL on the same app
path produce one UCAN with two attenuation entries
- create_delegation_multi_service_multi_path: three different services
on three different paths in one UCAN, each getting its own entry
- create_delegation_rejects_empty_abilities
- create_delegation_rejects_empty_actions_under_path
- create_delegation_subset_of_parse_recap: end-to-end invariant that
every delegated resource is derivable from the session's own parsed
recap — the check the JS delegateTo path relies on.
Breaking change: the createDelegation WASM export signature changed
from (session, delegateDID, spaceId, path, actions, expirationSecs,
notBeforeSecs) to (session, delegateDID, spaceId, abilities,
expirationSecs, notBeforeSecs). The only JS caller is
TinyCloudNode.createDelegationWrapper, which is being updated in the
matching js-sdk PR (fix/manifest-signin-multi-delegate) that bumps the
WASM rev to this commit.
skgbafa
added a commit
to TinyCloudLabs/js-sdk
that referenced
this pull request
Apr 10, 2026
…ation Bumps the WASM source dependency to the merge commit of TinyCloudLabs/tinycloud-node#48 (feat(sdk-wasm): multi-resource create_delegation, 6f6fc86). The new WASM exports `createDelegation` with the signature (session, delegateDID, spaceId, abilities, expirationSecs, notBeforeSecs) where `abilities` is the same multi-resource map shape that prepareSession already accepts: Record<short service, Record<path, full-URN actions[]>> A single call now produces ONE signed UCAN whose attenuation carries every (service, path, actions) entry. This unlocks manifest-driven single-prompt sign-in flows where one delegation can cover multiple services on multiple paths to a single backend. Cargo.lock updated via `cargo update -p tinycloud-sdk-rs -p tinycloud-sdk-wasm`. The wrapper packages @tinycloud/node-sdk-wasm and @tinycloud/web-sdk-wasm will pick up the new exports automatically on the next bun run build.
8 tasks
skgbafa
added a commit
to TinyCloudLabs/js-sdk
that referenced
this pull request
Apr 10, 2026
…ta.1 gaps) (#187) * chore(sdk-rs): bump tinycloud-node rev to multi-resource create_delegation Bumps the WASM source dependency to the merge commit of TinyCloudLabs/tinycloud-node#48 (feat(sdk-wasm): multi-resource create_delegation, 6f6fc86). The new WASM exports `createDelegation` with the signature (session, delegateDID, spaceId, abilities, expirationSecs, notBeforeSecs) where `abilities` is the same multi-resource map shape that prepareSession already accepts: Record<short service, Record<path, full-URN actions[]>> A single call now produces ONE signed UCAN whose attenuation carries every (service, path, actions) entry. This unlocks manifest-driven single-prompt sign-in flows where one delegation can cover multiple services on multiple paths to a single backend. Cargo.lock updated via `cargo update -p tinycloud-sdk-rs -p tinycloud-sdk-wasm`. The wrapper packages @tinycloud/node-sdk-wasm and @tinycloud/web-sdk-wasm will pick up the new exports automatically on the next bun run build. * feat(sdk-core): multi-resource WASM types + manifest abilities helpers Update the createDelegation WASM types to match the new multi-resource Rust API: - CreateDelegationWasmParams.abilities replaces flat path/actions — Record<short service, Record<path, action URNs[]>> - CreateDelegationWasmResult.resources replaces flat path/actions — Vec<DelegatedResource> sorted by (service, path) by the Rust side - New DelegatedResource type carries (service, space, path, actions) Add manifest → abilities map bridge helpers in manifest.ts: - AbilitiesMap type alias for the shape both prepareSession and the new createDelegation accept - resourceCapabilitiesToAbilitiesMap converts a flat list of ResourceCapability entries (manifest long-form services, full URN actions) into the AbilitiesMap shape, merging duplicates and rejecting unknown services with a clear error - manifestAbilitiesUnion takes a ResolvedCapabilities (from resolveManifest) and unions the app's own resources with every manifest-declared delegation's permissions, producing the abilities map a session should be signed with so the session key acquires recap coverage for both runtime calls AND downstream sub-delegations These helpers are the bridge that lets the node-sdk's signIn drive its WASM prepareSession call from a manifest, and they also keep sdk-core as the single owner of the manifest semantics — the node-sdk layer just calls them. Update SharingService.ts to use the new abilities-based createDelegation shape (single-entry call, infer short service from action URN namespace), and surface validation errors for empty actions or mixed-service action lists (the WASM call requires the service segment explicitly). Test updates: types.schema.test.ts and wasm-validation.test.ts now exercise both single-resource and multi-resource result shapes. Exports added from sdk-core/src/index.ts: AbilitiesMap, manifestAbilitiesUnion, resourceCapabilitiesToAbilitiesMap, DelegatedResource 532/532 sdk-core tests pass. * feat(node-sdk): manifest-driven signIn + multi-entry delegateTo Two SDK gaps in 2.1.0-beta.1, both fixed here: GAP 1 — signIn ignored config.manifest NodeUserAuthorization.signIn passed `defaultActions` to prepareSession unconditionally, even when a manifest was installed. The result was that manifest-declared permissions and pre-delegations never made it into the session's SIWE recap, defeating the entire manifest flow. Fix: - NodeUserAuthorizationConfig gains an optional `manifest` field - Manifest is stored on the auth handler and exposed via `manifest` getter + `setManifest` setter - New private `resolveSignInAbilities()` returns: * the union (via `manifestAbilitiesUnion` from sdk-core) of the app's own resources + every additionalDelegate's permissions, converted to the WASM abilities map shape * OR `defaultActions` when no manifest is installed (legacy fallback) - Both signIn() and prepareSessionForSigning() now call this helper instead of hardcoding `defaultActions` - TinyCloudNodeConfig also gains `manifest`; setupAuth() forwards it - TinyCloudNode exposes `setManifest()` and `manifest` getter passthroughs so higher layers (TinyCloudWeb) can keep the manifest in sync after construction Result: signIn now produces a SIWE whose recap covers both the app's runtime permissions AND the permissions of every manifest-declared delegation target. This is the precondition that makes the next gap fix work without a wallet prompt. GAP 2 — delegateTo hard-failed on multi-entry input The previous implementation threw "delegateTo currently supports one permission entry per call" — forcing apps that needed multi-resource delegations to either loop (N delegations, N CIDs, N POSTs) or fall back to the legacy wallet path. Both options broke listen-style single-prompt flows. Fix: - delegateTo now accepts any number of PermissionEntry items - Derivability is checked against the union of ALL entries — partial failures throw PermissionNotInManifestError carrying the missing entries (no partial issuance) - createDelegationViaWasmPath converts the entries to one multi-resource abilities map and calls the WASM createDelegation ONCE — producing one signed UCAN whose attenuation covers every (service, path, actions) entry - Returns the existing { delegation, prompted } shape (single PortableDelegation, not an array). The new optional PortableDelegation.resources field carries the full multi-resource breakdown for consumers that need it; the flat path/actions fields mirror the first sorted resource for back-compat - All entries must target the same space (one UCAN = one space); cross-space delegation throws with a clear error - forceWalletSign + multi-entry combination is rejected with a clear error since the legacy wallet path is single-entry only Legacy createDelegation public API: - The legacy compatibility shim no longer gates on entries.length === 1. All N-entry legacy calls now route through delegateTo first, falling back to the wallet path on PermissionNotInManifestError as before. - createDelegationWrapper updated to send the new abilities map shape to WASM and handle the new resources[] return shape. Tests: - TinyCloudNode.delegateTo.test.ts: replaced the "multi-entry throws" test with two new tests: * multi-entry → ONE UCAN with merged abilities map (asserts the abilities map sent to WASM has both services correctly grouped) * multi-entry with one missing cap → PermissionNotInManifestError surfaces ALL missing (no partial issuance) Existing tests updated to return the new resources-array WASM shape - TinyCloudNode.signInManifest.test.ts (new file): four tests covering the manifest-driven signIn path: * no manifest → falls back to defaultActions (legacy) * manifest with own permissions → recap reflects manifest tier * manifest with delegations → recap unions app + delegate caps (the headline listen test) * setManifest() post-construction takes effect on next signIn 28/28 node-sdk tests pass. PortableDelegation gains an optional `resources?: DelegatedResource[]` field. Single-resource delegations still populate the flat path + actions; multi-resource delegations populate `resources` AND mirror the first entry into path + actions. Consumers reading `resources` get the full picture; legacy consumers reading flat fields keep working for single-resource cases. * feat(web-sdk): forward manifest into TinyCloudNode + align WASM ABI Wire the TinyCloudWeb manifest plumbing through to the underlying TinyCloudNode so signIn() can drive its SIWE recap from the manifest: - _init() forwards `this._manifest` into nodeConfig.manifest, which the node-sdk constructor passes through to NodeUserAuthorization - setManifest() now also calls `this._node?.setManifest()` so post-construction manifest updates reach the node before the next signIn (previously only the local _manifest field was updated, so the node still had the construction-time manifest) - requestPermissions's writeManifest callback similarly mirrors the expanded manifest onto the node so the post-escalation signIn picks up the new capability set automatically - Drop the "Phase 4" stale-doc notes from the manifest field, the _manifest private field, and the requestPermissions JSDoc — the Phase 5 refactor is now in place Update BrowserWasmBindings.createDelegation to match the new WASM ABI: it now takes (session, delegateDID, spaceId, abilities, expirationSecs, notBeforeSecs) where `abilities` is the multi-resource map shape `Record<short service, Record<path, action URNs[]>>` — same shape prepareSession already accepts. The previous 7-arg flat-path signature is gone. Full monorepo build is clean with these changes. * chore: changeset for manifest-driven signIn + multi-entry delegateTo Bumps: - @tinycloud/sdk-core minor (new types + helpers, breaking schema change) - @tinycloud/node-sdk minor (manifest config + delegateTo multi-entry) - @tinycloud/web-sdk minor (manifest forwarding to node) - @tinycloud/node-sdk-wasm patch (rev bump) - @tinycloud/web-sdk-wasm patch (rev bump) In pre-mode this rolls forward to the next 2.1.0-beta.N version.
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
Extend
Session::create_delegationand thecreateDelegationWASM export to accept the same multi-resource abilities shape asprepareSession:HashMap<Service, HashMap<Path, Vec<Ability>>>. A single UCAN now encodes every(service, path, actions)entry in one signed blob, scoped to a single target space.What was broken
"kv"in the Rust delegation path — delegations forsql,duckdb,hooks, orcapabilitiesservices were silently wrong (the resource URI was built withkvin the service segment).(service, path)pairs had to issue N separate UCANs. This is wrong semantically (the user model is one delegation per recipient) and, in the legacy wallet-signed fallback, forces N wallet prompts. It also broke the manifest use case in the js-sdk, where an app's manifest can declare multiple permission entries in a singledelegations[].permissionsblock.What's fixed
create_delegationnow takesabilities: HashMap<Service, HashMap<Path, Vec<Ability>>>and walks every(service, path, actions)tuple, accumulating them into oneCapabilitiesobject viawith_actions. One signed UCAN, many resource URIs in itsattenuation.createDelegationWASM export takesabilities: JsValuein the same{ service: { path: [action] } }shape the JS side already uses forprepareSession.DelegationResultnow returnsresources: Vec<DelegatedResource>— a sorted list of{service, space, path, actions}describing every capability the UCAN grants. Sort order is lexicographic on(service, path)so tests and JS consumers get deterministic output regardless of HashMap iteration.EmptyAbilities,EmptyPathsForService,EmptyActionsForPath. Previously an empty actions list under a path would silently sign a useless UCAN.Tests (11 total, all green)
create_delegation_single_resource_backward_compat— one service/path round-trips cleanly through the new shapecreate_delegation_multi_service_same_path— KV + SQL on the same app path produce one UCAN with two attenuation entriescreate_delegation_multi_service_multi_path— three different services on three different paths in one UCAN, each getting its own entrycreate_delegation_rejects_empty_abilitiescreate_delegation_rejects_empty_actions_under_pathcreate_delegation_subset_of_parse_recap— end-to-end invariant that every delegated resource is derivable from the session's own parsed recap (the check the JSdelegateTopath relies on)All existing sdk-wasm tests (
parse_recap_*,create_session_and_invoke,session_with_additional_spaces) still pass.Breaking change
The
createDelegationWASM export signature changed fromto
The only JS caller is
TinyCloudNode.createDelegationWrapper, which is being updated in the matching js-sdk PR (fix/manifest-signin-multi-delegate) that bumps the WASM rev to this commit.Test plan
cargo test -p tinycloud-sdk-wasm --lib— 11/11 pass locallycargo check --workspace— cleanpackages/sdk-rs/Cargo.tomlrev to this merge SHA and updatescreateDelegationWrapperto pass the abilities map