Repository navigation
Open
Milestone
Description
Parent: #91. This issue is the entrypoint to resume this work.
Goal
Bring the protected-content feature from stack #203→#206 onto the current develop as five reviewable layers. The layers give dKMS and the Runtime clear seams, so each side can change independently and later syncs with develop stay small.
Starting point (re-checked 2026-10-21 on develop@3beb8164)
- Source of truth: feat(protected-content): buy from any live offer #206 head
44b11207. The stack base isdevelop@829a24ad. - Target:
develop@3beb8164(0.8.0-alpha.24), which is 1,539 commits ahead of the stack base. Earlier checks were atac7d018bandeeda0aa5. - A simulated merge of
44b11207intodevelopgives 68 conflicted files (54 content, 13 modify/delete). 142 files changed on both sides. The commits sinceac7d018bresolve none of the collisions below. The phone Home work added collision 7. - The 121 commits after
eeda0aa5add two conflicts:inbox.js(Community presence, Community should work without Discovery: a new Home sees nobody and no earlier posts #410) and the Librarymodel.test.mjs(Home cannot start, update or show Recovery with a file over 16 MiB (or 64 MiB total) in Library folders #444). They also add two rules: Library shows an encrypted file without this Home's label as Blocked (Home cannot start, update or show Recovery with a file over 16 MiB (or 64 MiB total) in Library folders #444), and apps open web links only through Home's link sheet, with no pop-ups (Chat and app web links require Home confirmation of the exact destination (0.8.0 F1 gate) #430). developchanged its own protected-content code only a little. Most conflicts come from infrastructure thatdeveloprewrote: Carrier, auth, setup, content, provider bridge and registry, packaging, and CI.
Collisions, most critical first
- Release gate.
developkeeps protected content off by default through theprotected_contenthost setting. The feature routes (/api/creator/*,/api/apps/marketplace/*, object buy and publish operations) need the same gate. - Provider wire versions.
developadmits providers by schema ID. The feature changes the protect, decrypt, PSSH and payload shapes but keeps the/v1IDs. Each changed shape gets a/v2ID, and the Runtime adopts the schema-ID admission fromdevelop. - Provider retirement. The feature removes the key, decrypt, DRM and rights providers. On
develop,check-product-data.mjsandpublish-release.shstill require them, and they are still optional components. The cutover updates both scripts and gives installed Homes a clear update path (update-hop job). - Coupling. Protected-content logic uses
ProviderRegistryandGatewayStatedirectly, and library and gateway call each other. Business logic sits ingateway_provider_proxy.rs, the chain wire is untyped JSON, and the server depends on thecustody-providercapsule crate. Everydevelopchange in these areas becomes our conflict. - Carrier limits.
developlimits whole-object replies, bounds peer reads, binds trusted routes to their peer, and closes idle relays. Custody spread over 3 nodes and the 360 s provider timeout need proof under these rules. - Supply chain. An installed Home runs only signed-release capsules.
develophas onehomeinstall profile and adds per-capsule signed records andwindow_policy.creatorandelacity-readermust enter the release lists. The L5 decrypt node is a new deployment role, like the custody host, and needs its own install path. - Phone Home contract (new on
develop). Home now has a phone stage (docs/HOME_MOBILE.md). Each first-party capsule loads the shared theme to get the form factor. A sidebar becomes the shared push drawer (elastos-drawer.js), and every control is at least 44 px.home-phone-layout-smoke.mjskeeps small targets at 0 for each measured app, including Marketplace, Library and Elacity Player. The feature's Marketplace screens (buy sheet, listing, Explore, Shops, My listings) and the Creator and Reader capsules predate this contract. The 3 new conflicts come from this phone work:home-gui.js,marketplace-product-layout-smoke.mjs, andshell-connector-sheet.js, which the feature deletes anddevelopupdated for the phone. - Off-topic scope in feat(protected-content): one authority, the Runtime, and no 10% disk floor (ELACITY-2299) #203. The "remove 42 unreferenced public items" commit and the disk-floor edits do not belong to this stack.
developalready handles the disk rule. - Process.
state.md,TASKS.md,check-wci-alignment.shandhome-entropy-check.mjsno longer exist ondevelop. Their guards move tocheck-product-data.mjsandtest-behaviour. Dated plan files underdocs/audits/move into issues. - The word "offer". In the Runtime, "offer" means a seller listing. In elacity-web and the v3 contracts, it means a buyer's escrowed bid (
createAccessOffer). The Runtime term changes to "listing" before buy offers are added. - Missing scope. Buy offers, listing management and earnings do not exist in the Runtime yet.
Method
- Rebuild each layer; do not merge or cherry-pick. The 11 source commits each cross several layers.
- Each layer starts from the layer below it, and the first layer starts from
develop. - Each layer ports its own paths from
44b11207. It keepsdevelop's infrastructure and three-way merges shared files against base829a24ad. - Each layer then extracts its seams from the ported code, with the ported tests as the safety net.
- Each layer starts from the layer below it, and the first layer starts from
- Sync by forward merge.
developmerges into L1, then each layer merges into the next. Published branches are never rebased. - Small fixes go straight to
developas separate PRs. Examples are the provider-bridge shutdown idempotence andSLOW_PROVIDER_REQUEST_MS. - feat(protected-content): one authority, the Runtime, and no 10% disk floor (ELACITY-2299) #203–feat(protected-content): buy from any live offer #206 close after the new stack opens, with the owner's agreement. Each one links to its successor layer.
Stack
- L1
feat/pc-bridge→develop: contracts and seams. Behavior does not change, and the gate stays off.- Port the changes from feat(protected-content): buy from any live offer #206 to the five
elastos-protected-content-*crates and the protect and decrypt provider capsules. They already exist ondevelop; the change is 30 files, about +7.9k lines. - Proposed, under review: federation v2 (Slice 1 in the federation comment) replaces the v1 pool document in the same schema change, so that no second bump is needed.
- Give each changed wire shape a
/v2schema ID, and use schema-ID admission. - Keep the Rights, Custody and Decrypt provider traits, and add a Protect provider trait (protect and media). Add no single-implementation traits.
- Add typed chain request and answer types in
provider-contracts, shared by chain-provider and the server. - Move the trusted-issuer and state-root helpers into the custody crate. The server then stops depending on the capsule crate.
- Add the server module
protected_content/with one route-mount function. The gate loads once at startup, and a test shows that every route refuses while the gate is off. - The Runtime owns the capsule operation allowlist. The catalog audit checks each capsule against it.
- Add
capsules/_shared/protected-content-client.js, vendored with its--checkguard, and only to the capsules that use it, in the same way aselastos-drawer.js. JSON golden fixtures are shared by the Rust and JS tests.
- Port the changes from feat(protected-content): buy from any live offer #206 to the five
- L2
feat/pc-dkms-custody→ L1: dKMS and node provisioning.- Port custody composition, the custody node store, the
provision-custody-nodeCLI, peer roles, operator control, and the custody host deployment. - Port the custody parts of Carrier, the replica changes, and chunked content identity.
- First, add a characterization test for 3-node custody calls under the new Carrier limits.
- Do the provider retirement as one commit. It updates
check-product-data.mjs,publish-release.shandcomponents.json, and it has an update-hop proof from the current release pin (now0.8.0-alpha.23). - Proposed, under review: one custody node serves several Runtimes (Slice 2). The same work lets a node release to a decrypt provider on another Runtime for a wallet-only reader, limited to a governance-signed decrypt list (Q2).
- Port custody composition, the custody node store, the
- L3
feat/pc-mint-read→ L2: mint and read.- Port the Creator, Elacity Reader, Elacity Player and Library capsules, with
window_policy, release entries and signed records. - Make each capsule fit the phone stage: shared theme, push drawer for sidebars, 44 px targets and 16 px inputs.
- Port creator channels and Elacity metadata.
- Break the library↔gateway cycle through the
protected_content/module. - Write each protected object through the Library principal-root path, and test that Library never shows it as Blocked (Home cannot start, update or show Recovery with a file over 16 MiB (or 64 MiB total) in Library folders #444).
- Port the Creator, Elacity Reader, Elacity Player and Library capsules, with
- L4a
feat/pc-market-port→ L3: buy and Explore.- First, rename "offer" to "listing", including operation names, fixtures and schema IDs.
- Port the marketplace directory, listing, buy and on-chain directory modules, the market adoption code, the marketplace capsule and the typed chain-provider operations.
- Move the buy orchestration out of
gateway_provider_proxy.rs. - Carry
44b11207(Inbox approval fix). - Rebuild the feature's Marketplace screens on the phone layout from
develop: push drawer and 44 px row actions.
- L4b
feat/pc-market-trade→ L4a: offers, listings and earnings (new), modelled on elacity-web.- Buy offers:
createAccessOffer,acceptAccessOfferandcancelOffer, read throughofferersOf, with USDC approval. - Listing management:
sellAccessandwithdrawListing. - Earnings: sales, unclaimed totals, and
withdrawRewardswith a simulated "withdraw all". - Each chain call is a typed chain-provider operation with a payment-class wallet approval.
- Transaction and explorer links open through Home's link sheet (Chat and app web links require Home confirmation of the exact destination (0.8.0 F1 gate) #430).
- Buy offers:
- L5 → L4b: open Runtime-minted content in external apps such as base.ela.city (native decrypt, agreed in After 0.8.0: Open Runtime-minted protected content in external apps such as base.ela.city (L5 of #388, options) #400).
- Admission for an external-viewer session: wallet only, one asset,
https://base.ela.cityonly. - A decrypt node with the native decrypt provider runs on its own host and OS account, never on the seed (Seed: run the seed service under its own account without sudo or Docker #174). The CEK stays in that provider for one session and is then wiped. No CEK goes to the browser.
- A DASH adapter gives
media-playerthe clear fMP4 parts through session-bound,no-storesegment reads under/api/. The fallback iselacity-player. - Video first. Documents follow, with their own acceptance step.
- First, a one-video prototype on the feat(protected-content): buy from any live offer #206 code (see After 0.8.0: Open Runtime-minted protected content in external apps such as base.ela.city (L5 of #388, options) #400). L5 depends only on L2 and L3, so it can move earlier if needed.
- Also tracked here: ERC-1271 contract wallets and an "access pending" state during Base finality.
- Admission for an external-viewer session: wallet only, one asset,
Open decisions
- Confirm that the deployed Base
AuthorityGatewayincludes the v3 access-offer module. The legacy drm-contracts gateway has no access offers. - Open child issues for L4b (offers, listing management, earnings) when L4a starts.
- L5: name the owner and time limit of the one-video prototype (After 0.8.0: Open Runtime-minted protected content in external apps such as base.ela.city (L5 of #388, options) #400).
- Federation Slices 1 and 2, the Q2 decrypt list, and whether each buyer's Home needs admission (see comments).
Approvals needed (#173)
Until S2 is done, each item below must have an approval recorded in this issue before its PR opens. Every route stays behind the protected_content gate.
- Integration branch, before L1 (added 2026-10-28): the Custody app and custody discovery between contacts. The items are listed in the Custody app note and the discovery approval request. They are not approved yet.
- L1 and L2: none. They add no public route, approval type or capability. The Runtime operation allowlist only limits what capsules can do.
- L3: route
/api/creator/channels. Creator getselastos://object/*publish,publish_progress,rootsandstat. Elacity Reader getsopen_viewer,read_viewerandclose_viewer, which Elacity Player already has. - L4a: routes
/api/apps/marketplace/{items,listing,channels,channel-access,pay-tokens}and the object operationbuy_offer(renamed in L4a). Marketplace getsbuy,download_owned_copy,import_runtime_custodyandlist_runtime_custody. Buy uses the existing payment wallet approval and adds no new approval type. - L4b: new Chain provider operations for offers, listing management and earnings. Each one uses the existing payment wallet approval.
- L5: a new admission class for an external-viewer session (wallet only, one asset,
https://base.ela.cityonly), plus its session and segment routes. This needs a security review (After 0.8.0: Open Runtime-minted protected content in external apps such as base.ela.city (L5 of #388, options) #400).
Verification for each layer
- Every layer:
just ci-local-prepush,check-product-data.mjswith its tests,vendor-ui-tokens.sh --check, andjust test-behaviour, each run once with the gate off and once with it on. - L1: protected-content crate tests, shared golden vectors, fake-backend substitution and rejection, and the route-gate test.
- L2: the custody harness smoke, the update-hop journey, Carrier tests under the new limits, and chain-provider tests.
- L3: the Creator, Reader, Player and Library tests and smokes, plus
home-phone-layout-smoke.mjsat 0 small targets, with Creator and Reader added to its app list. - L4a/b: the marketplace tests and smokes (including the phone layout smoke), and the buy, listing and directory gateway tests. L4b adds v3 ABI fixtures and an installed mint → list → offer → accept → withdraw journey.
- L5: the prototype results in After 0.8.0: Open Runtime-minted protected content in external apps such as base.ela.city (L5 of #388, options) #400, a refused-case test for each admission rule, and a test that a release to a recipient that is not on the decrypt list is refused.
Acceptance record
- Candidate: not started.
- Exercised checks: none yet.
- Reviews: none yet.
- Installed release evidence: none yet.
- Remaining gate: L1 branch from current
develop. - Next owner: @irzhywau.
Updates
- 2026-10-28: added the integration-branch line under "Approvals needed (Required: Isolation as promised #173)". It links the approval requests for the Custody app and custody discovery. Nothing else changed.
Metadata
Metadata
Assignees
Labels
No labels
Activity
andersalm commentedon Oct 19, 2026
Proposal for review, not a decision, by OMP for Anders (19 Oct: "lay the groundwork for dKMS node operators and the federation working together; people and agents will need to set up and manage this; consider future scaling and decentralization").
@irzhywau, please review security and architecture, starting with the open questions in §7, especially "who signs a per-object epoch once committees vary". @SashaMIT, please review the names and wording.
dKMS node operators and federations — design for review
Design proposal, no product code. Reviewers: Irzhy (security, architecture), Sasha (wording, UX). Parent #91; builds on #388 (L1–L5, "Candidate: not started") and #400. No permanent home: accepted decisions become one comment on #388 and one on #400;
docs/PROTECTED_CONTENT.mdchanges when code lands. Code read at develop3beb8164.Prefixes:
contracts/,custody/,pcruntime/=elastos/crates/elastos-protected-content-{contracts,custody,runtime}/src/;provider-contracts/=elastos/crates/elastos-protected-content-provider-contracts/;server/=elastos/crates/elastos-server/src/;elastos-runtime/=elastos/crates/elastos-runtime/;custody-provider/,chain-provider/undercapsules/. "esp-port" = branchorigin/feat/dkms-esp-port(PR #15), specdocs/superpowers/specs/2026-07-14-dkms-tofn-pool-custody-design.md, briefingsdocs/dkms/deploy/. [A] = assumption or proposal; everything else cites its source.0. Summary
One concept. A federation is the signed custody pool already in code (
SignedCustodyPoolV1,contracts/custody_pool.rs:376), extended with operator identity, a revision chain and later multi-party governance. No second registry.Today the code supports one operator only: one policy-authority key signs pool, epoch and committee authorization (
custody_pool.rs:730-740); 2-of-3 is fixed (:741); every mint uses one configured 3-node epoch (pcruntime/mint.rs:204); each custody node trusts exactly one Runtime issuer for provisioning and release evaluation (custody-provider/src/main.rs:168-208;server/server_infra.rs:540-566); "distinct operators" are free-text labels (server/protected_content_config.rs:613-621).First two slices (gate off): (1) real operator identity and a revision chain on the one pool document; (2) one custody node serves several admitted Runtimes. These are the smallest changes that make a multi-operator federation possible; re-sharing, larger thresholds, multi-party governance and payouts follow.
base.ela.city. Follow #400 comment 6224540651. A Runtime node with the native decrypt provider serves
media-player, and the custody nodes check entitlement independently. Who operates that decrypt node is still open (Q6). §6 compares this with Lit.1. Roles, trust model, threat model
custody_pool.rs:98-107). Its Carrierdid:key(server/carrier.rs:1036-1046) is linked to them only by the descriptorcustody/node_store.rs,custody/node_share.rs:10-23)docs/PROTECTED_CONTENT.md:124-130). Re-sealing its share only to the authorized recipient (custody/release.rs:49-136)protected_content_config.rs:760-767). [A] Proposed: the operator's DIDCustodyEpochIssuerKeyV1today. [A] Later: m-of-k governance DIDsserver/protected_content_runtime.rs:1502-1507;server_infra.rs:561-563)docs/PROTECTED_CONTENT.md:35-37)contracts/rights.rs:124-131)protected-content-decrypt(docs/PROTECTED_CONTENT.md:55-57,145-151)elastos-runtime/src/capability/token.rs:186-225)custody_pool.rs:758-765). PR #204 crypto review: "Two colluding custody nodes recover the key … a deployment question"custody/release.rs:74-87)docs/PROTECTED_CONTENT.md:134-138)contracts/custody_envelope.rs:173+)custody-provider/src/lib.rs:20-23,196-215). No backup and no re-share; losing 2 of 3 "permanently strands the object" (Irzhy, PR #15 comment 5369906640)valid_until(365 days by default) and aRevokedstate (custody_pool.rs:82-107;protected_content_config.rs:699-800). Nothing moves sharesvalid_until(§3)docs/PROTECTED_CONTENT_CONTRACTS_V1.md:335-343)docs/PROTECTED_CONTENT.md:155-156)elastos-runtime/src/provider/bridge.rs:1216-1270)2. Federation formation and membership
Document. Today a federation is three artifacts signed by one key:
custody_pool.rs:16,303-376);custody_epoch.rs:167-172);custody_pool.rs:482-486).[A] The proposed
/v2schema replaces v1. Per #388 L1, there is one schema-ID path, and no v1 validator is kept alongside, because #173 says "no … parallel DTO authority" (#173 body). No installed operator state needs migrating: the installer never creates it (docs/PROTECTED_CONTENT.md:189-197), and the gate is off. The v2 document:revisionplusprevious_sha256, the pattern ofserver/collaboration_network.rs:36-44,150-160. The federation id is the hash of the genesis revision.operator_did, a node DID that signs the node-key/custody-key binding (this closes the descriptor-only link), and an operator DID countersignature.contracts/identity.rs:15,216).Admission.
Removal and rotation. Governance signs a revision that marks the member
Revokedor gives it a new custody key. Every committee that contains the member then owes a re-share. Recorded rule: "Pool changes must not silently replace an object's nodes. Replacement requires an explicit re-share and a new signed custody epoch" (PR #15 comment 5321392792).Discovery and pinning. This reuses the Community network pattern, so there is no new gossip topic.
components.jsoncollaboration_network(expected_network_id,head_cid,trusted_profile_signer_dids).server/node_cmd.rs:14-30). It accepts the highest revision that chains from the pinned genesis.federation_id, the revision and the epoch in its manifest, which already carries the pool, epoch and authorization (custody_envelope.rs:173+).Path to decentralization [A]. Each step is a governance revision plus re-shares. Content is never re-encrypted.
LIFECYCLE_AND_FUTUREPROOFING.md§2)3. Key lifecycle and where each step runs
custody/provision.rs:216-229), seal each share to a node's X-Wing keypcruntime/mint.rs:182-206)custody-provider/src/main.rs:184-190)custody/node_store.rs:30), so open provisioning would be a storage denial-of-servicedocs/PROTECTED_CONTENT.md:140-143)federation_idand the revisionpcruntime/coordinator.rs:476-650;custody/reconstruct.rs:123-149)coordinator.rs:495, anawaitinside a loop). The chain plane checks one fixed issuer (chain-provider/src/main.rs:903-921); the custody plane already checks the issuer the operation declares (provider-contracts/src/custody.rs:388-397)hasAccessByContentId)SCALING_AND_INTEROP.md§2b; a 2-of-3 → 3-of-5 test exists there,LIFECYCLE…§1)4. Operator setup and management (people and agents)
Install. [A] A dedicated custody host: an OS service under its own account, not a role inside an everyday Home and not an extra role on the seed. The composition exists (
elastos run custody-provider --with chain-provider;docs/PROTECTED_CONTENT.md:263-275,server/provider_host.rs:44-170); the managed Home profile excludes custody (components.jsonprofilehome);custody-providerships for linux-amd64, linux-arm64 and darwin-arm64 (components.json). Same signedinstall.sh/elastos updatepath;deploy/custody-hostis simulation-only (docs/PROTECTED_CONTENT.md:277-283). Storage and custody stay separate roles (PR #205--role). Own account because the seed, running as aNOPASSWD: ALLaccount, scored 9.2 onsystemd-analyze security(#174).Management uses the existing operator control.
elastos nodealready hasinfo,peer,status --peerandupdate --peer --check/--applyover Carrier (server/node_cmd.rs:14-30).node status --peerandnode update --peer. Status gains custody fields: revision, share count, store headroom, chain-RPC agreement and membershipvalid_until. No heartbeat topic or new dashboard is added.capsules/services/browser/index.html). It shows the epoch-expiry countdown that Protected-content UI/UX parity gaps vs feat/dkms-esp-port: protect any file type, Creator app, open-through-decryption #42 asks for. Inbox handles join, leave, revision signing and re-share approval.One CLI and API for people and agents (PRINCIPLES.md §7). [A] Names are for Sasha. The
elastos protected-content-configcommands (protected_content_config.rs:77-160) move with no aliases:provision-custody-nodeelastos node custody initcreate-policy-authority-keyelastos federation propose,elastos federation signgenerate-custody-compositionelastos federation propose,elastos federation signverify-custody-compositionelastos federation verifynode custody join-request | backup | rotate-key | leave. Every command is a typed Runtime operation with JSON output and stable error codes.expiry,max_uses,delegatable):custody.status(read),custody.operate(backup, update),federation.propose.Payouts. None in Phases 0–1, consistent with "no staking" (5321392792). A per-release fee or a share of sales would need a change to the Base
AuthorityGatewaycontract, so it is a business decision for Anders.5. Scaling and decentralization
node_store.rs:30) needs an indexed store before about 4,000 assets per node. The esp-port spec estimates "~2 GB per node per million assets" (§14.3, not measured). With per-object selection, the load per node is about assets × n / N [A].SCALING_AND_INTEROP.md§1).6. base.ela.city, and what replaces Lit
Flow (#400 comment 6224540651):
https://base.ela.city, under origin policy.AuthorityGateway.hasAccessByContentIditself.media-player, with session-authorized segment reads. The fallback iselacity-player.Also tracked under #388 (6224540651):
Excluded. Option C (dual wrap with Lit) is excluded by both Irzhy and Anders (#400).
AuthorityGatewayitself, with two agreeing RPCs out of 2–5ddrm-readerusesencryptedDataCid(#400)elastosPSSH (PR #204). The DASH adapter is not yet provenMigration. Lit content stays on Lit until it is re-minted. New Runtime mints go to the federation, and no dual-custody bridge is built.
7. Mapping to #388, first two slices, open questions
Slice 1 (L1, gate off): operator identity and revision chain on the one pool document.
operator_did, node-DID-signed key binding, operator countersignature,revisionandprevious_sha256. It keeps one governance key and a policy-declared 2-of-3.validate_custody_epoch_against_pool_atand the ceremony CLI only.Slice 2 (L2, gate off): one custody node serves every admitted Runtime.
trusted-runtime-issuerfile (custody-provider/src/lib.rs:71,126) with the pinned federation.server/protected_content_runtime/tests.rs:9827-9862). Both mint and release, including with one node stopped. Before the fix, the second Runtime is rejected.tests.rs:9864-9937). On a real node, the chain plane would reject a second Runtime's operations (server_infra.rs:540-566). This is an inference from the code and has not been exercised.Open questions for Irzhy
custody_pool.rs:732-734). If the publisher signs, nodes must verify that committee ⊂ pool and that the selection rule was followed.max_n, which puts all load on the same nodes? What default caps?For Sasha. The user-facing word for "federation". The Services card and Inbox steps for join and re-share. How readers learn where their content is decrypted. The command names in §4.
Record vs code, found while reading.
docs/PROTECTED_CONTENT.md:134says "distinct operators", but the code checks labels only.availability-providerfrom the node, butPROTECTED_CONTENT.md:267-269still lists it.deploy/custody-hostREADME calls a 3-node bring-up "a separate task", yetdocker-compose.ymlandup.shexist.8. What not to do
SCALING_AND_INTEROP.md§1).irzhywau commentedon Oct 21, 2026
@andersalm, here are my answers to Q1, Q2 and Q8 in §7. The approvals that #173 requires are now listed in the issue body.
custody/release.rsonly checks that the key matches). So any entitled wallet can name its own key, rebuild the CEK with a short script, and continue to decrypt the public ciphertext after its right ends. That breaks the rule in After 0.8.0: Open Runtime-minted protected content in external apps such as base.ela.city (L5 of #388, options) #400 that no reusable key leaves a decrypt provider. Today the only control is the single trusted Runtime issuer on each node.irzhywau commentedon Oct 27, 2026
Progress: integration branch, before L1
This is a status note. The work is on the local branch
feat/pc-integration; it is not pushed yet, and no PR is open. It contains two commits on the integration reference665d35ac.1.
a5b30bf5: fresh-Home onboarding, release timing and replica placement fixes2.
c872c82d: Custody app. A pool owner forms the dKMS custody pool from Home instead of six CLI commands.protected-content-configcustody handlers are now shared functions. The CLI and the new routes call the same code. The Runtime loader stays the single validation authority, and the pool file stays create-only.custody.node_card.read, is answered only to the DID of the Runtime issuer that the node trusts./api/apps/custody/*, one per capsule method.custodypurpose allows only resource identifiers and fullsha256:fingerprints.Verification
cargo fmtand clippy (-D warnings) pass.cargo test -p elastos-serverpasses, with 3,325 + 220 tests and the provider test binaries.check-product-dataandvendor-ui-tokens --checkpass.Approvals needed (#173) before a PR opens
/api/apps/custody/*and thecustodycapsule with its interface methods.custody.local_node.includeandcustody.pool.install.custody.node_card.read.custody.identifierandcustody.fingerprint.Next
collaboration-custodymessage plane, acustodysigning scope and an Inbox approval type. These need Required: Isolation as promised #173 approval too. A Community-wide offer list comes later on the same messages.irzhywau commentedon Oct 28, 2026
Approval request: custody discovery and consent, contacts first
This adds to the earlier progress note. The work is on the local branch
feat/pc-integration. It is not pushed yet, and no PR is open.What it does
Approvals needed (#173) before a PR opens
collaboration-custody, with the opsquery(offer query, answered at once) anddeliver(request, decision and pool notice, answered with a signed receipt).custody. It is opt-in, never a default scope, and granted with a passkey. Its payload types:elastos.custody.offer-query/v1elastos.custody.offer/v1elastos.custody.node-request/v1elastos.custody.node-decision/v1elastos.custody.pool-active/v1elastos.collaboration.acceptance-receipt/v1, for the receiptsGET /api/apps/custody/offerPOST /api/apps/custody/offerPOST /api/apps/custody/messages/enablePOST /api/apps/custody/nodes/findPOST /api/apps/custody/node-requestsPOST /api/apps/custody/pool/notifycustody:custody.offer.readcustody.offer.setcustody.messages.enablecustody.nodes.findcustody.node_requests.sendcustody.pool.notifyPOST /api/apps/inbox/actions:custody-approve-request:<request id>andcustody-decline-request:<request id>.custody_node_request,custody_node_approving,custody_node_resumable,custody_node_incompleteandcustody_node_taken.custody_pool_active.custody.node_request.approve, which approves exactly the request the node recorded.custody.messages.enable, which grants the signing scope.protected-content/:custody-offer.json,custody-node-requests.json,custody-served-consent.jsonandcustody-served-pool.json.custody-node-requests-out.jsonandcustody-pool-notices.json.Privacy
Known limits
Verification
Reviews: every task had a review and fix rounds. A whole-change review found no Critical findings. Its two Important findings were both about term consent: the owner could not re-ask a node for a new term, and the node did not check the pool's term. Both are fixed: the node now refuses a pool longer than the term its admin approved (
term_mismatch).Full gate before the final fixes:
-D warnings);cargo test -p elastos-serverwith the provider test binaries: 3,657 passed, 0 failed;check-product-dataandvendor-ui-tokens --check.After the final fixes: the focused tests pass, including end-to-end runs over Carrier:
The full gate re-run and a scoped re-review are in progress.
Still to do:
irzhywau commentedon Oct 30, 2026
Plan update: custody pools as a service ("Plan B")
The protected-content integration branch is now rebased on the latest
developas a single commit. Develop's 0.8.0 rules win:setup --with.Because of that, a mint now needs one verified copy, on the creator's Home. Before we split the branch into layered PRs, we are making custody and dKMS fully functional.
Goal
How it works
Play:
The buyer's Home never holds the key.
Mint: the minter's Home sends sealed parts to a pool sealer. The sealer encrypts, provisions the shares and publishes, and the minter gets signed receipts only.
Pool rules live in a signed pool descriptor and can change without code: servers per session, no serving or sealing for a member's own Home, who may mint, and quotas.
Existing pools, items and listings keep working. The current pool becomes pool chore(cve): runtime CVE hygiene — 32/34 closed (94%), wasmtime 17→36, 207-line Cargo.lock reduction #1.
Milestones
Each milestone ends with a live check on a set of demo Homes. Each stage is one commit, so the later split has clean cut points.
Out of scope until after the split
For review (Anders), each with a path inside today's rules:
Every new Carrier target, op, route, step-up op and message type will be listed in the #173 approval record when the PRs open.
irzhywau commentedon Nov 5, 2026
Public metadata after 2.0: four open questions
Context. Since 2.0, each Home's IPFS runs offline, so a new mint's public description never leaves the creator's Home. That includes the title, description, cover and the
metadata.jsonthat the on-chaintokenURIpoints to. The indexer behind the Marketplace catalog cannot fetch it. It records the item with an empty name and no content id, and the Marketplace drops it. Two mints made today on a demo Home show exactly this: they are on chain and in the index, withname: nullandmetadata.kid: null.Rule adopted for this work. All metadata must always be public and available for external sync:
metadata.json, the per-token ERC-1155 JSON,content.jsonand the like, plus covers. Core data stays private: ciphertext, keys and private files. Each Home's IPFS stays offline. At mint time, the Home sends only the public files over Carrier to a seed, which stores them and serves them publicly. We checked a protected mint's outputs: thetokenURIfolder holds only public JSON (no key material), the cover is a separate file, and the ciphertext folder never leaves the Home.Q1. Consent: who agrees that these files go public? BLOCKING (design, and shipping)
SECURITY.mdsays a seed accepts no new copies from other Homes. This work needs a narrow exception for public metadata and covers only. Two parties are involved:content.replicagrant, where the receiving owner decides.The seed accepts only an allowlist of metadata files and a raster cover, under size caps. It requires an exact CID rebuild and a matching on-chain mint (the content id bound to the token, and
tokenURIequal to the bundle's CID).Proposal: both consents. Needs Anders' approval for the exception to
SECURITY.md(seed copies),docs/CONTENT_AVAILABILITY.mdand the "publish stays local" rule. It also needs a #173 record for the new seed-only Carrier opcontent/publish_public_metadata.Q2. Which node is the seed? BLOCKING (it is the target of the transfer)
elastos.elacitylabs.com: the ElastOS seed the code already uses by default. It serves files by CID.base.ela.city: the web app and indexer. Its/ipfs/…path answers with the web app's HTML, so it is not an ElastOS gateway.Proposal:
elastos.elacitylabs.com, unless the production seed is elsewhere.Q3. How does the indexer get the files from the seed? BLOCKING end to end (not blocking the Runtime-side work)
The indexer reads metadata only from its own IPFS node, through the public IPFS network. The seed's IPFS is offline under the same 2.0 rule, so the files on the seed alone don't reach it.
ipfs://…resolves for anyone: wallets and other indexers. This changes the 2.0 offline rule for the seed, so it needs Anders.Proposal: 3a now, and 3b for Anders together with Q1.
Q4. What if the seed can't be reached at mint time? BLOCKING (it shapes the Creator flow)
The files go to the seed just before the wallet signature, while nothing is on chain yet.
Proposal: block with Retry.
Plan once these are answered
develop, which now includes Home session renewal and the Jetson Browser work. Same process as before: backup, classified conflicts, full gate.irzhywau commentedon Nov 5, 2026
@andersalm your input is needed on the comment above (#388 (comment)):
tokenURIresolves for anyone.Q2 and Q4 are open too, if you have a view.
andersalm commentedon Nov 5, 2026
@irzhywau, here are Anders's answers (5 Nov) to your four questions. Thanks for the clear write-up, and for checking that the tokenURI folder holds only public JSON.
The direction behind the answers
elastos://; IPFS is one availability backend behindelastos://content, and apps never call it.ipfs://appears only at the edge with the outside world, such as the tokenURI. Private or public is a policy on each object, not a separate network.Q1. Who agrees that the files go public?
content.replicagrant: the metadata allowlist and one raster cover, size caps, a quota per uploading Home, an exact CID rebuild, uploads that no mint binds are deleted after a day, and takedown.Q2. Which node?
A new ElastOS node run by Elacity in a public availability role: not the release seed, and not the base.ela.city web server. The release seed keeps "accepts no new copies", so uploads can never affect updates, and
SECURITY.mdgains a paragraph for the new role. Build and test the role on any node; the host is a deployment choice, and Anders names who runs it.Q3. How does the indexer get the files?
ipfs://resolves for wallets and other indexers. That gives 3b's result without putting any ElastOS node on public IPFS.Q4. What if the node can't be reached at mint time?
Block with Retry, before the wallet step, as you propose.
Notes on the plan
ipfs://link resolves on a public gateway.metadata.jsonon develop lacks the image, media and publisher fields the indexer reads. Your branch writes the full set, so that lands with this work.content/publish_public_metadata, check whether the existing replica path (ensureandimport_exact) with a public policy and this admission rule covers it: fewer ops and one Required: Isolation as promised #173 row.SECURITY.mdanddocs/CONTENT_AVAILABILITY.md(this direction) change in the same PR, and the node role goes into the Required: Isolation as promised #173 approval record.andersalm commentedon Nov 6, 2026
Anders, 6 Nov: Elacity will host the ela.city ElastOS node on Google Cloud, where ela.city's other servers already run, when the time is right. No date yet. Until then, the public availability role can be built and tested on any node, as in the answers above; the host is a deployment choice.
irzhywau commentedon Nov 10, 2026
Public metadata for Home mints: new direction (design approved, not built yet)
The problem. A protected mint from a Home puts
ipfs://<folderCID>/metadata.jsonon chain. The folder and thecover live only in the Home's IPFS store, which runs offline. A marketplace that reads the tokenURI cannot fetch
them, so the item shows blank, as the two mints of 2026-11-05 do.
The decision. We don't rely on Elacity's upload API or on its IPFS node as a service. The Home publishes the
metadata to the IPFS network itself, under its own CIDs. How a marketplace reads it is the marketplace's side.
Phase 1 (next to build)
Two stores. Each Home gets a second IPFS store that holds only public objects. The existing private store
stays
--offlinein every mode and never shares a repo, port or process with the public one.Consent first. The first time the Creator opens, the Home admin is shown what will become public, and
enables the store with a passkey. The notice says:
Without consent, new mints stay locked. "Stop sharing metadata" is always available, and unfinished mints can
still be resumed.
At mint, before a new wallet approval:
finish.
A failure blocks with Retry before the wallet. A mint whose approval already exists settles without touching
the public store.
A narrow surface. The public store has no HTTP gateway and accepts only that validated import. No app can
reach it.
Peers. There are two editable lists with release defaults:
A new Home therefore joins the network without depending on any single node.
No change on chain. The CIDs on chain stay the Home's own.
Phase 2 (later)
One Home option runs the public store as an always-on public node, and such nodes join the default preferred
peers. This closes phase 1's gap: availability while the creator's Home is offline. Which CIDs those nodes keep
permanently, and on what admission rules, is decided in that phase.
Departures from earlier answers (for Anders)
public metadata.
approval restates that the metadata is public.
ipfs.ela.city.
metadata, and this ordering is what lets indexers find it at once.
What's next
release.
shows its name and cover.
The design was reviewed independently and passed after three rounds.