Skip to content

dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) - #15

Open
irzhywau wants to merge 20 commits into
mainfrom
feat/dkms-esp-port
Open

irzhywau wants to merge 20 commits into
mainfrom
feat/dkms-esp-port

Conversation

@irzhywau

Copy link
Copy Markdown
Contributor

Lands the decentralized key custody (dKMS), content protection (dDRM), and content-commerce rails — originally built on the pre-ESP runtime (review/dkms-foundation) — rebased and re-seated onto the ESP architecture, hardened, test-anchored, and consolidated into 7 commits.

What this delivers

  • Protected content, end to end: creators seal assets (CENC + PQ-hybrid threshold envelopes, CEK escrowed to a custody quorum); buyers acquire on-chain rights; viewers (elacity-player, ddrm-viewer) decrypt through capability-scoped provider chains with per-open sessions (open → session-bound streaming → close → sweeper reap), fail-closed at every unauthorized step.
  • Commerce rail: publish → index → buy → acquire with Wallet and Chain integration, content-market capsule, and Marketplace/Creator surfaces.
  • Key custody: dkms-authority/dkms-keygen with a CI-enforced release invariant (release build fails to compile with legacy-receipt-authz/dev-modes).

How it was adapted to ESP (main's architecture wins everywhere)

  • WASI-era execution dropped; providers run on the component model / microVMs; manifest-capability ceilings enforced fail-closed in the bus hostcall.
  • Manifests migrated to the ESP projection schema; catalog uses typed affordance dispatch; op→action capability enforcement converged into provider_operation_action (preview==enforce conformance down to 1 tracked divergence).
  • Money verbs (/api/market/buy, /api/create/mint) require passkey step-up — intent-, launch-, and time-bound, single-use — brokered under Home's cookie authority with a new Home-chrome spend-confirmation dialog (what's displayed is what's signed). Launch tokens moved to URL fragments (never query strings).
  • Security hardening found and fixed along the way: localhost scheme + boot sub-route seizure (first-writer-wins pinning, identity-keyed teardown), authority refusals reaching guests as Denied (was Internal), release-gating of ELASTOS_DDRM_SUBJECT.

Testing

~1,700 tests green across both CI lanes (default + dev-modes): workspace suites, 10 capsule crates, capability conformance + token fuzz, egress spine, and a new dkms_rail e2e anchor proving the full commerce/viewer contract. verify-ci, verify-capsules, bus-conformance (made non-vacuous), lint/fmt, entropy/WCI checkers all pass. Known pre-existing flakes: browser_reconciliation timing family (passes in isolation, predates this branch).

Docs

All dKMS/dDRM/commerce docs consolidated into one onboarding hub at docs/dkms/ (README entry → architecture, security model, media pipeline, viewer sessions, commerce, dev setup; historical notes under history/). Roadmap coverage baseline + gap plan in docs/superpowers/specs/.

Known follow-ups (documented, non-blocking)

  • Subject-resolution rewire (RequiredHomeLaunchToken threading): chain-gated open/buy is dev-lane-only until it lands — deliberately fail-closed; prerequisites in the viewer_open.rs PARKED block.
  • ESP facts/verbs migration for the commerce/viewer conversation plane (shared with main's own surfaces; tracked in the gap plan).
  • Real decrypt-binary chain needs a CI lane; two Linux-only smokes need a Linux runner; manual browser smoke checklist owed before release.

…trate

Key custody, content-protection, on-chain rights, and commerce rails
seated on the ESP component runtime: op->action capability enforcement,
first-writer-wins provider pinning with identity-keyed teardown,
passkey step-up on money verbs (intent-, launch-, and time-bound),
fragment-delivered launch tokens, viewer session lifecycle with
sweeper, and the dkms rail end-to-end test anchor.
CENC cipher core with PQ-hybrid threshold envelopes, ddrm media
packaging, encrypt/decrypt/key/rights/drm providers, content-market
and act-emitter microvm capsules, dkms-authority with its release
fail-closed invariant, protected viewers (elacity-player, ddrm-viewer),
the Creator portal, and ESP projection-schema manifests.
Owned-asset open flow with staged loading and buy-then-retry, viewer
session release on window close, Home-chrome spend-intent confirmation
gating the passkey ceremony, Home-cookie-authenticated money verbs,
wallet-metamask approval auto-poll and EIP-6963 rediscovery, and
Library open-with-viewer with .ddrm cover art and session-scoped tags.
verify-ci and the dDRM capsule gate, the dkms-authority release
invariant job, a non-vacuous bus-conformance recipe, review/** branch
gating, entropy/alignment checker updates for the commerce shell, and
dkms dev scripts (docker harness, av-forensics, demo servers).
Consolidate the scattered convergence-era and marketplace notes into one
onboarding-first hub at docs/dkms/, and add the terminology glossary.

Structure
  docs/dkms/README.md        the entry: what/why, the three rails, the security
                             model in brief, a quickstart, and a guided reading path
  ARCHITECTURE.md            planes, isolation substrates, the full pipeline,
                             capsule inventory
  SECURITY_MODEL.md          boundaries, the two invariants, threat model,
                             invariant -> test table
  MEDIA_PIPELINE.md          encrypt, CENC, envelopes, packaging, the decrypt rail
  VIEWER_SESSIONS.md         session lifecycle, launch tokens, the open path
  COMMERCE.md                discovery, buy, acquire, resale, the Library handoff
  COMMERCE_API.md            the /api/market/* seam between shell and gateway
  COMMERCE_CONTRACTS.md      selectors, addresses, ABIs on Base
  RUN_E2E.md                 the operator runbook
  DEV_SETUP.md               the local harness: smoke scripts, run-creator-gateway.sh,
                             dkms-docker, dev-mode caveats, macOS vs Linux
  deploy/                    quorum node provisioning, runbook, ceremony, templates
  diagrams/                  rendered architecture diagrams + mermaid sources
  history/                   convergence-era working notes, with a README index
                             explaining they are rationale, not current status
  tools/                     selector verifier, index prototype, design tokens

Consolidation
  36 markdown pages under docs/convergence/ + docs/marketplace/
  -> 32 under docs/dkms/: 25 relocated, 11 folded into the current pages, and
     7 written fresh (README, ARCHITECTURE, COMMERCE, MEDIA_PIPELINE,
     VIEWER_SESSIONS, DEV_SETUP, history/README). Both old directories are gone.

Accuracy
  Every claim is checked against the code on this branch; where an older doc
  disagreed, the code won. That covers fragment-form launch tokens, the
  dev-modes-only ELASTOS_DDRM_SUBJECT pin and its release-build boot guard,
  the 404-non-oracle contract on unowned assets, the Home-cookie money verbs
  with their spend confirmation dialog, and first-writer-wins provider pinning.

Inbound links
  68 references to the moved and deleted paths retargeted repo-wide, across
  GLOSSARY.md, the docs index, THREAT_MODEL/SECURITY_AUDIT/ARCHITECTURE_MAP,
  the dev and dDRM scripts, and doc-path comments in capsules/ and
  elastos-server. The comment updates are comment-only; no code changed.

Gates: home-entropy-check (markdown links resolve), check-wci-alignment,
just alignment-check — all green.
Coverage report (functional vs ESP-aligned percentages per roadmap item)
promoted to the measurement baseline, plus one bulk plan covering every
sub-100% item: current state, remaining workstreams, test anchors,
dependencies, and open questions where implementation is unsettled.
@irzhywau
irzhywau requested review from SashaMIT and andersalm August 12, 2026 10:49

@SashaMIT SashaMIT left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Irzhy — thanks for the review request. Focused early security pass on the money / viewer / custody seams (not a full 529-file product review). CI green and the architecture claims in the PR description largely hold on the branch.

Verified as solid (matching what you listed):

  • Passkey step-up on /api/market/buy and /api/create/mint (authorize_money_verb — intent-/launch-/time-bound, single-use)
  • Launch tokens in URL fragments, not query strings
  • Live buy binds real ledger tokenId via resolve_token_id_live (not a content-hash fallback)
  • ELASTOS_DDRM_SUBJECT behind dev-modes + release boot guard
  • Viewer close routes + 60s sweeper
  • dKMS release invariant (compile_error! / CI job)

Remaining items I'd treat as pre-merge (or explicitly gate as operator-dogfood-only):

  1. High — ELASTOS_DDRM_BUY_SIGN=wallet auto-approve. Passkey is required (good), but sign_with_managed_account still inline-approves. Combined with the known PARKED subject-resolution rewire (empty subject fail-closed — which you already call out), that flag also lets chain buy proceed with no linked wallet and no Wallet/Inbox human ceremony. Fine as a documented operator opt-in for dogfood; for anything beyond that, live signing should go through Inbox consent (the footgun comment in wallet_signer.rs already points there).

  2. Medium — expired media sessions. authorize_media_session does not check expires_at, so manifest / init / cover can still serve after expiry until the 60s sweeper. Segments already fail closed — worth aligning those routes.

  3. Medium — trade-approval vs buy resolver. scan_asset_created_for_content_id continues when mint calldata is unavailable; buy's resolver fails closed (candidate_input_unavailable). Same fail-closed rule on the trade-approval path would close a wrong-operative window.

  4. Medium — Library re-buy content_id. Open prefers .ddrm capsule content_id/kid; legacy Library buy still uses content_cid / owned:{name}. Share one resolver so buy and open key the same id.

Not blocking on the PARKED RequiredHomeLaunchToken subject rewire itself — fail-closed empty is the right interim; just don't let the wallet auto-sign flag quietly widen that gap.

Happy to re-review a follow-up commit.
— Sash

@irzhywau

Copy link
Copy Markdown
Contributor Author

@SashaMIT 2 points have been retained, the rest have already been treated my side as hotfix


12. Addendum — 2026-08-12 external review (Sash, feat/dkms-esp-port)

Sash's early security pass on the money/viewer/custody seams raised four remaining items. Each was verified against the reviewed commit (1502db9f, which is also the merge-base of hotfix/dkms-security) before registration. Two are new confirmed findings (ESP-1, ESP-2); two are already covered or did not reproduce (dispositions below). These are marketplace/runtime seams, not dKMS-authority-node paths, so they do not join the DKMS-1..8 table; they still block production exposure of the branch's buy/trade path.

New confirmed findings

ID Severity Established finding Required outcome
ESP-1 P1 The trade-approval resolver scan_asset_created_for_content_id (capsules/chain-provider/src/main.rs) silently continues when a candidate mint's calldata is unavailable (tx_input → Ok(None)), while the buy resolver's MKT-1 hardening fails closed with candidate_input_unavailable on the same condition. Because candidates are sorted newest-first and a re-mint reuses the KID, an unavailable newest calldata can fall through to an older binder — a wrong-operative/token-id approval window. Trade approval applies the same fail-closed rule as the buy resolver: any candidate whose mint calldata cannot be fetched aborts the resolve; no decision is made on a partial candidate set.
ESP-2 P1 Legacy Library re-buy (buy_owned_access in elastos/crates/elastos-server/src/api/viewer_open.rs) derives content_id as owned.content_cid / owned:{name} and never parses the .ddrm capsule, while the open path prefers the capsule's content_id/kid first. The doc comment claiming buy resolves "EXACTLY as /api/viewers/open does" is false for dKMS capsule assets: a re-buy is keyed on an id the rights gate never reads back, so money is spent and the open still denies. Buy and open share one content-id resolver (capsule content_id/kid first, then content_cid, then owned:{name}); a regression test proves a dKMS-capsule Library asset re-buy keys the same id the subsequent open's rights check reads.

ESP-1 acceptance: unavailable calldata on any candidate aborts with a stable fail-closed error (mirror candidate_input_unavailable); a test with newest-calldata-unavailable + older same-KID binder proves no fallback to the older asset; transport errors keep propagating as errors (already the case via ?).

ESP-2 acceptance: shared resolver extracted and called by both buy_owned_access and the open path; re-buy of a dKMS-minted capsule asset records ownership under the capsule content_id; the misleading doc comment is corrected by the shared code path itself.

Dispositions — no plan change

  • Wallet auto-approve (ELASTOS_DDRM_BUY_SIGN=wallet), raised High: already shipped in the state the review asks for. wallet_signing() requires cfg!(feature = "dev-modes") and is unconditionally false in release builds (buy_authority.rs), the inline approve carries the explicit council-S41-F1 FOOTGUN comment pointing at the Wallet/Inbox consent split, and the PARKED subject-resolution rewire is tracked in docs/superpowers/specs/2026-08-07-roadmap-gap-bulk-plan.md and the viewer_open.rs PARKED block. Routing live signing through Inbox consent remains the tracked follow-up before anything beyond operator dogfood; the compile gate is the "explicitly gate as operator-dogfood-only" mitigation.
  • Expired media sessions (manifest/init/cover), raised Medium: did not reproduce on the reviewed commit. Every media route — manifest, init, cover, segment, track init/segment — authorizes via authorize_media_session → get_media_session, and get_media_session runs session_bounds::sweep_expired inline on every lookup (viewer_media.rs), so an expired session 404s immediately on all routes; there is no 60s-sweeper window. The explicit expires_at checks in the segment handlers are redundant defense-in-depth, not the only expiry gate.

Staged remediation of eight authorization, replay, lifecycle, revocation,
initialization, persistence, and configuration findings from the 2nd review
of the dKMS path (feat/dkms-esp-port), without weakening the fail-closed CEK
boundary. Each stage was implemented test-first and security-reviewed; every
regression test's #[ignore] is removed as its fix lands, so the final tree
leaves zero dKMS regression tests ignored.

Stage 0 — reproduce and freeze the vulnerabilities:
- 16 focused regression tests across capsules/dkms-authority and
  capsules/ddrm-envelope, each asserting the secure invariant and run RED on
  the reviewed baseline.
- Adversarial counting RPC double (magic / non-magic / revert / malformed /
  timeout / transport-error, ordered scripts) at the node's RPC boundary.

Stage 1 — bound and bind grant addresses (DKMS-1, DKMS-4):
- Owner-only access: pure validate_covered_addresses rejects empty, over-
  cardinality, over-size, malformed, duplicate, and unrelated entries before
  any signature or RPC work; normalize_evm_address canonicalizes every
  address before equality, dedup, cache-key, and RPC construction.
- VerifiedAccess now exposes only the validated [owner] set, never a
  caller-supplied clone; new CoveredInvalid fail-closed error.
- Named bounds (MAX_COVERED_ADDRESSES, per-entry/per-list size caps) and an
  explicit pre-dispatch fan-out guard (validated_subjects x rpc_pool_len).

Stage 2 — unify EIP-1271 quorum decisions (DKMS-2):
- One typed RpcVote<T> outcome model and a single generic combine_votes<T>
  agreement combiner shared by hasAccessByContentId and EIP-1271, so one
  endpoint can never decide smart-account signature validity.
- EIP-1271 aggregation polls the entire configured pool (no first-reply
  short-circuit), classifies each endpoint (magic=Valid, non-magic/revert=
  Invalid, malformed/transport=Unavailable), and fails closed on
  insufficient reachability or valid/invalid disagreement; verdict is
  order-independent. EIP-191 fast path and endpoint-URL non-leakage preserved.

Stage 3 — bounded two-phase replay state machine (DKMS-3):
- New ddrm-envelope::replay module: injected ReplayStore + RAII Reservation
  (begin->InFlight, commit->seen, Drop->abort) so no lock is ever held across
  EIP-1271/access RPC, KEM, signature, or I/O work. Fixed-size Keccak nonce
  keys with decoded-length caps; time-ordered heap pruning with bounded work
  per call; documented global + per-delegation bounds; capacity fails closed
  without evicting unexpired records. Structural-, owner-signature-, and
  request-signature-invalid input reserves nothing; concurrent identical
  requests commit at most once. commit records the seen entry unconditionally,
  and NodeChain::from_env fails closed unless
  IN_FLIGHT_TTL > MAX_COVERED_ADDRESSES x pool_len x ETH_CALL_TIMEOUT.
  Retires the process-global replay_guard singleton for an injected store.

Stage 4 — secure bootstrap and atomic persistence (DKMS-7):
- New secure_store module: exclusive same-directory temp created O_EXCL at
  mode 0600 (no chmod-after-write window), hard_link install that refuses to
  overwrite or follow a planted final symlink, fstat-verified no-follow read,
  fsync of file and directory, RAII temp cleanup. Concurrent first starts
  converge on one durable identity (race loser re-reads the winner); corrupt
  or unreadable state fails closed and never resets to empty.
- Node identity is loaded/validated before any listener binds; Request::Init
  is refused on every network transport; keystore path comes only from
  process configuration; provisioning moves to an offline provision
  subcommand. Primitive is reusable by Stage 6 revocation state.

Stage 5 — version and fully bind lifecycle authorization (DKMS-5):
- One shared ddrm-envelope::lifecycle v2 encoder with four phase-separated
  domains (reshare-contribute, reshare-install, dkg-contribute, dkg-install),
  length-framed and collision-resistant, locked by byte-pinned golden
  vectors. LifecycleManifestV2 -> SHA-256 digest -> domain-separated AAD binds
  operation+version, kid, scheme, ceremony id, old/new set ids, thresholds,
  ordered member records INCLUDING recipient keys (the field the node-set-id
  derivation provably omits), executing node/coordinate, cek_len, and
  per-input-object digests. All four node ops verify operator authorization
  and reserve ceremony replay BEFORE opening any share/sub-share or deriving
  polynomial material; dkg_install recomputes node_set_id from supplied
  membership. v1 lifecycle AAD is gone from node paths (no mixed v1/v2).

Stage 6 — complete and persist revocation (DKMS-6):
- New security_state module: versioned durable SecurityState (caller +
  delegation revocations) persisted through the Stage 4 secure_store
  primitive; in-memory RevokedSet/ReplayStore are caches hydrated from durable
  truth at startup on every adapter including stdio. New node-reachable
  revoke_delegation op carries an operator-signed, domain-separated record
  (delegation nonce, scope, expiry, issued-at) verified before persisting;
  wrong-signer/foreign-nonce/expired are rejected. Caller and delegation
  revocations persist before success is returned and are consulted before
  replay reservation / CEK work. Corrupt/unreadable/unknown-schema state fails
  closed and never resets to empty. Compaction drops only expired delegation
  revocations, never standing caller revocations, atomically. Durable write
  failure cannot return success.

Stage 7 — fail-closed allow-list configuration (DKMS-8):
- Typed caller_policy_from_env -> Result<CallerPolicy, ConfigError> parsed
  once before any listener binds on every serve path. Anonymous mode requires
  the explicit DKMS_AUTHORITY_ALLOW_ANONYMOUS=1 opt-in; unset, empty,
  malformed, mixed, duplicate, and contradictory are distinct fatal states
  that can never silently degrade to anonymous. Whole-list rejection on any
  malformed entry; duplicates rejected deterministically; startup diagnostics
  report mode/count only (never key material).

Stage 8 — integration, observability, docs, and CI gate:
- Wire capsules/dkms-authority into the just verify-capsules gate (both
  default and dev-modes lanes) so the whole dKMS regression suite runs in CI;
  the suite previously never executed under any gate.
- New secret-free operational counters (counters.rs) for fail-closed events:
  rejected list bounds, quorum disagreement/unavailability, replay capacity/
  in-flight, revocation load/write failure, invalid allow-list, and lifecycle
  manifest mismatch. Counters are event-only AtomicU64 tallies rendered to
  stderr; no keys, nonces, seeds, addresses, signatures, or URLs. Wiring is
  strictly observe-only and changes no control flow or auth decision.
- Deployment runbooks, DKG/re-share ceremony docs, env templates, preflight,
  and dev harnesses updated for offline provisioning, the revocation
  migration (existing nodes start with empty durable revocation state after
  upgrade and must re-issue standing revocations), v2 lifecycle manifests
  (no mixed v1/v2 ceremony), owner-only grants, and the explicit-anonymous
  posture. The ddrm-runtime-open dev harness main verify rail is migrated to
  offline provisioning; its adversarial-probe / live-lifecycle scenarios
  (which still drive wire init + v1 auth against the now-hardened node) are
  marked PENDING-MIGRATION in-code and documented in DEV_SETUP, and are
  excluded from the security gate.
…family

Fixes a family of pre-existing flaky tests in the elastos-server gateway test
suite that failed non-deterministically under `cargo test --workspace` /
`just test` parallel load. All are TEST-HARNESS timing artifacts — no
production reconciler/gateway/room code is touched.

Three distinct mechanisms, one theme (a test waits on a proxy signal instead
of the real completion condition, and a clock race cancels an instant op):

- browser_reconciliation (hanging / stale / transient / runtime_restart, and
  the latent sibling newly_committed_launch_obligation_wakes_reconciler):
  under a paused virtual clock, a retry close briefly parked on the global
  BROWSER_SESSION_REGISTRY async mutex (held by parallel tests across blocking
  std::fs I/O in load_durable_ownerships) leaves the runtime idle, so tokio's
  paused-clock auto-advance jumps virtual time to the 30s tokio::time::timeout
  and cancels a close that completes in microseconds. Fixed by a settlement
  helper that keeps a task runnable (denying auto-advance) and waits on the
  real drained/obligation condition under a bounded cap, replacing fixed
  0..N sweep budgets.
- gateway_browser_route_tests (wallet_read_timeout_does_not_poison_
  chain_dispatch / _wallet_refresh): a real-clock race on the
  `!late_result_observed` observation; fixed by converting to a paused clock
  (start_paused) so the ordering is deterministic.
- room (chat_room_transport_joins_bootstrap_peer_after_topic_already_joined):
  a real-clock cadence race against a ~2s OS-thread bridge starved under CPU
  oversubscription; fixed with a bounded wall-clock deadline in the shared
  transport test helpers.

No assertion was weakened and no retry budget was merely bumped: every fix
waits on the real completion condition under a generous BOUNDED cap, so a
genuine hang still fails loudly. The periodic controlled-reconciler tests
were investigated and deliberately left unchanged — their retry sweeps
legitimately require virtual-time auto-advance and are not part of this
family.

Proof: full elastos-server lib 11/11 runs under parallel load at
1359 passed / 0 failed; focused hammer of all five affected tests 30/30 runs
at 5 passed / 0 failed.
Two P1 findings from the 2026-08-12 external review (Sash) of the branch's
money path. Marketplace/runtime seams, not dKMS-authority-node paths, but
they block production exposure of the buy/trade path.

ESP-1 — trade approval fails closed on unavailable mint calldata:
- capsules/chain-provider/src/main.rs scan_asset_created_for_content_id (the
  trade-approval resolver) silently continue'd when a candidate mint's
  calldata could not be fetched, while the buy resolver already fails closed
  (candidate_input_unavailable). Because candidates are sorted newest-first
  and a re-mint reuses the KID, an unavailable newest calldata could fall
  through to an OLDER same-KID binder — a wrong-operative/token-id approval.
  The trade resolver now pre-fetches every candidate's calldata and aborts
  the whole resolve on any unavailable one, mirroring the buy resolver; no
  decision is made on a partial candidate set. Transport errors still
  propagate. Regression tests: newest-calldata-unavailable + older same-KID
  binder aborts (no fallback); fully-available set still resolves; transport
  error propagates.

ESP-2 — buy and open share one content-id resolver:
- elastos-server/src/api/viewer_open.rs buy_owned_access derived content_id
  from content_cid / owned:{name} and never parsed the .ddrm capsule, while
  the open path prefers the capsule content_id/kid first — so a dKMS-capsule
  Library re-buy was keyed on an id the rights gate never reads back (money
  spent, open still denies). One shared resolve_owned_content_id (capsule
  content_id -> kid -> content_cid -> owned:{name}, byte-identical to the
  open path's prior derivation) is now called by both the open path and
  buy's legacy owned-root branch; the req.content_id fast path is unchanged
  and the previously-false "resolves EXACTLY as /api/viewers/open does"
  comment is now true. Regression test proves a capsule re-buy keys the same
  id the subsequent open's rights check reads.
seal_operator_auth was the v1-AAD operator-seal test helper; the DKMS-5
v2-manifest migration moved every call site to seal_v2, leaving it dead in
both CI lanes and failing just verify-capsules under -D warnings.
@irzhywau

Copy link
Copy Markdown
Contributor Author

Anders' review, mapped to current branch state — remaining work

Mapping of Anders' 2026-08 review note against what has already landed on feat/dkms-esp-port. Two findings are already treated and are dropped from the todo list:

  • Cross-wallet grant flaw — closed by 943c10d1 (DKMS-1/DKMS-4): grants are owner-only; validate_covered_addresses rejects any non-owner entry before signature/RPC work and VerifiedAccess exposes only the validated [owner] set. Regression-tested, wired into just verify-capsules.
  • Conflicting execution model — reconciled by the ESP re-seat + docs consolidation: WASI-era app execution dropped, providers run component-model/microVM (subprocess adapter documented explicitly as such in docs/dkms/ARCHITECTURE.md's actual-vs-aspirational table), and docs/CONFIDENTIAL_COMPUTE.md marks TEE as unimplemented research.

Remaining (untreated) — todo

  • Extraction strategy / base branch. Nothing on the branch addresses the "wrong base" point: preserve feat/dkms-esp-port as-is, and extract the coherent parts (dKMS custody + ddrm-envelope crypto, media/viewer stack, commerce rail, test/CI harnesses) into small review branches built on the collaboration candidate. This is a merge-strategy decision, not a code change on this branch.
  • Carrier end-state for the dKMS quorum transport. The Phase-1 relay-sidecar scaffold over Carrier landed, but the deployed path is still WireGuard mesh + bespoke sidecars (dkms-carrier-client/dkms-carrier-node, ALPN elastos/dkms-authority/1, tcp:10.66.66.x:9443). docs/DKMS_OVER_CARRIER.md itself marks routing recovery through the Runtime-owned carrier-provider plane (elastos.provider.invocation/v1) as a follow-on. That follow-on is the fix for this finding.
  • Bind the release path to the Runtime-approved Wallet account. resolve_subject_address (viewer_open.rs) returns fail-closed empty because a principal-only signature cannot mint a RuntimeWalletAuthority; chain-gated open/buy is dev-lane-only via ELASTOS_DDRM_SUBJECT. The documented "subject-resolution rewire" (thread RequiredHomeLaunchToken through the dDRM open/buy path) is exactly this finding and has not landed.
  • Installer does not install the dKMS providers. components.json was last touched at 0.6.0 metadata prep and contains none of the branch's new capsules (content-market, encrypt-provider, elacity-player, ddrm-viewer, marketplace-content, ddrm-media, …), so a normal install.sh install cannot run the dKMS/commerce rail. Registry (and any packaging/CID publishing) needs updating.
  • Slim the trusted Runtime. Content/commerce/rights/key-workflow logic has grown inside elastos-server (viewer_open.rs ~2.2k lines including buy/content-id resolution; access_grant.rs grant assembly + delegation session cache). No extraction has happened; fold this into the extraction-branch plan so the trusted gateway shrinks toward dispatch + authority checks.
  • Documentation truth pass (residual). The docs/dkms/ consolidation landed, but top-level docs still describe divergent states (WireGuard-era DKMS_NODE_PROVISIONING.md, phase-status claims in DKMS_OVER_CARRIER.md, ~75 top-level docs alongside the hub). Align claims with the actual product path as each extraction branch is cut.

Note: just verify-capsules CI failure (seal_operator_auth dead-code under -D warnings) fixed in 58ce2bca.

@irzhywau
irzhywau removed the request for review from andersalm August 13, 2026 16:02
@andersalm

Copy link
Copy Markdown
Contributor

@SashaMIT @irzhywau

Decision: do not merge feat/dkms-esp-port into upstream/0.7-dev as one change.

Keep the branch as source material. We will extract the useful work into smaller review branches built on review/collaboration-candidate.

The August goal remains one complete path:

A creator encrypts content with a CEK. The custody layer protects that key under a declared quorum policy. A buyer finds the content, pays, and receives a signed right. Opening the content requires both the declared availability threshold and dKMS threshold.

The raw CEK must never appear in the content object, Runtime, Carrier, an application capsule, or an ordinary receipt. It may exist only inside the reviewed custody and decrypt boundaries.

feat/dkms-esp-port combines custody, protected open, commerce, Home UI, deployment configuration, and a large Runtime workflow. That is too much authority and too much product behavior for one merge.

The intended boundaries are:

  • Runtime derives identity, checks capabilities, selects providers, coordinates the operation, and records audit.
  • Wallet Provider owns Wallet keys, signing, and approvals.
  • Product workflow remains in providers.
  • Carrier transports authenticated Runtime-selected provider traffic. Capsules do not select IP addresses, ports, WireGuard, or ALPN routes.
  • dKMS nodes decide and contribute independently. Carrier is not rights or custody authority.
  • Decrypt reconstructs the CEK only inside an authorized session.

Current base

The current 0.7 work line is review/collaboration-candidate. It contains the collaboration foundation, product integration, Profile authority, and Runtime-owned Carrier provider path.

The protected-content work must build on that line, not on bare upstream/0.7-dev or feat/dkms-esp-port.

Before publishing it, we will add the general Home source-setup guide to review/collaboration-candidate and rebase the protected-content branch onto the updated parent.

First branch for review

feat/protected-content-contracts currently contains seven commits. It defines:

  • strict canonical DID key authority
  • content and key-envelope identities
  • owner Wallet, Profile, and recipient bindings
  • signed rights requests and preliminary rights receipts
  • bounded key-release requests
  • signed per-node decisions
  • recipient-labelled node contributions
  • signed terminal release receipts
  • canonical encoding, domain separation, schema versions, bounds, and golden vectors
  • replay, expiry, mutation, wrong-binding, and threshold-settlement tests

This branch is source-only. It does not run dKMS nodes, replace the provisional Runtime provider surface, reconstruct keys, prove recipient encryption, decrypt content, play media, sell content, or modify the installer.

The branch needs independent cryptographic and custody review before provider integration.

Proposed extraction order

  1. Protected-content custody
    Define and implement key generation, share custody, recipient-key proof, contribution encryption, durable replay protection, node admission, and threshold settlement.

  2. Content availability
    Produce signed replica receipts and prove that the required independent availability threshold is met.

  3. Runtime integration
    Replace the provisional protected-content DTOs atomically. Add the Wallet approval and typed Runtime-to-provider operations over Carrier. Do not keep parallel legacy authority.

  4. Protected content open
    Add encryption, key release, decrypt sessions, media handling, and viewer integration.

  5. Content commerce
    Add publishing, indexing, purchase, rights acquisition, and Marketplace UI after custody, availability, Wallet, and open are proven.

Required gates

  • No release fallback, subject override, legacy receipt authority, or parallel DTO authority.
  • Runtime remains orchestration, capability, provider selection, and audit.
  • Capsules never receive network topology or raw CEK material.
  • Node contributions are cryptographically sealed to a verified recipient key.
  • Replay decisions are durable across restart.
  • Wrong user, wrong Wallet, wrong Profile, wrong content, expired requests, duplicate nodes, and insufficient thresholds fail closed.
  • Relay, seed, Carrier, Runtime, and unrelated Runtimes cannot decrypt protected content.
  • New providers and capsules are present in the normal installer before any installed-product claim.
  • Physical quorum tests cover restart, revocation, partial node failure, and exact terminal settlement.

The old WireGuard deployment and bespoke dKMS ALPN bridge remain useful evidence only. They must not define the 0.7 capsule or Runtime contract.

We will mine feat/dkms-esp-port for reviewed code and tests. We will not merge it as one branch.

… and document viewing

- Introduced elacity-player capsule for playing owned fMP4/CENC audio and video assets.
- Added ddrm-viewer capsule for viewing owned non-media assets.
- Updated entry points in capsule.json files to reflect new directory structure.
- Implemented HTML, CSS, and JavaScript for elacity-player, including media streaming and overlay handling.
- Enhanced library actions to ensure protected assets are opened correctly from the user's library.
- Introduced server-side support for owned asset opening, ensuring proper authorization and context handling.
- Added favicon and styling for elacity-player interface.
- Updated setup scripts to include new viewer capsules for installation.
- Introduced a new HTML interface for creating assets in the `capsules/creator/browser/index.html` file.
- Updated the capsule entry point in `capsules/creator/capsule.json` to point to the new HTML file.
- Adjusted comments in `capsules/home/browser/home-shell-host.js` to reflect the new file structure.
- Modified tests in `elastos/crates/elastos-runtime/tests/capability_conformance.rs` to use a temporary directory for audit logs.
- Cleaned up imports in `elastos/crates/elastos-server/src/api/gateway.rs` and `elastos/crates/elastos-server/src/api/viewer_open.rs`.
- Added a test to ensure browser capsules remain launchable after changes in `elastos/crates/elastos-server/src/api/gateway_tests/dkms_capsule_manifests.rs`.
…signed grants

Protected `.ddrm` / owned media assets hung forever on "Preparing secure
session" (or looped on a passkey sign-in) and never opened. The ESP Home
shell could not produce the wallet-signed access delegation the dKMS quorum
requires: the open path signed the delegation via `window.ethereum`, which is
not injected into the shell iframe, and several downstream gates were unwired
for the shell-driven, connector-brokered flow.

Route the delegation signature through the user's DEFAULT wallet connector
capsule (which holds the injected wallet), end to end:

- Resolve the DRM subject per-principal from the signed-in principal's own
  verified EVM proof binding — no operator-pinned wallet.
- Admit the graphical/CLI Home shells to the owned-open auth gate (same actor
  set, no widening) and add a launch-returning variant so the open path can
  mint a wallet authority.
- `prepare-grant` surfaces the principal's default EVM connector; a new
  `/api/viewers/prepare-grant/sign` mints a `ddrm_delegation_sign`
  personal_sign approval bound to the owned open and validated against that
  default connector; the connector signs the `delegation_canonical` verbatim
  with its own wallet; the gateway assembles the grant. The wallet-provider
  now accepts, message-binds, and surfaces this signing intent.
- The shell auto-routes the approval to the default connector and polls
  (bounded, fail-soft) for the signature, then opens. A 403 now shows an
  explicit error instead of an infinite sign-in reload loop.
…arrier-node

- Added a logging mechanism with configurable log levels (error, warn, info, debug, trace) for both dkms-authority and dkms-carrier-node.
- Enhanced logging to include connection details, stream relays, and error handling while ensuring no sensitive information is logged.
- Updated docker-compose to bind log directories for easier access to logs during debugging sessions.
- Modified entrypoint script to mirror daemon logs to host-visible files for better traceability.
A chain of legacy→ESP contract mismatches broke minting and opening protected
dDRM assets in the ESP shell — each a component still speaking the pre-ESP
contract. This fixes those code paths and adds the per-stage diagnostics used to
find them.

Mint (creator.rs):
- Migrate the entire creator money-path off the legacy v1 wallet protocol to the
  v2 WalletContract: account lookups, signature requests, and approval reads now
  dispatch WalletProviderOperationV2 (ListAccounts / RequestApproval /
  ListApprovals) through a single `wallet_invoke` seam and one
  `request_transaction_approval`. The v2 provider rejected the old
  `{op:"accounts"|"request_signature"|"approval_requests"}` shapes. Each handler
  mints a RuntimeWalletAuthority from its creator launch token; since the authority
  carries the principal, the now-redundant `principal_id` params and the duplicate
  token-validation gates were removed.
- Build a fully-specified transaction via chain-provider `prepare_transaction`
  (nonce/gas_price/gas_limit) before the connector handoff — the ESP handoff
  validates gas/gasPrice/nonce and rejected the gas-less "let MetaMask estimate"
  intent ("missing field `gas`").

Open (viewer_open.rs):
- Mint viewer launch tokens via projection (authority_actor=home), not direct
  (authority_actor=<viewer>). The auth-session grant admits only the shells
  [home, system], so direct tokens failed object-session reads with a 401
  "authority context mismatch". Applies to both object and media viewers.

Diagnostics:
- viewer_open: host-side dKMS node-set precheck (MATCH/MISMATCH) + vk-fingerprint
  evidence and per-attempt recover traces.
- creator: creator_status / creator_wallet outcome logging.
- viewer_object: surface the object-session 401 reason.
- gateway_home_runtime: log the launch route with the home_token value masked
  (proves fragment delivery without leaking the capability).
- browser_capsules: log every capsule asset fetch.

Docs:
- TROUBLESHOOTING.md §11–§17: symptom→cause→fix for each failure, plus the log
  surfaces and grep lines used to diagnose the flow.
- run cargo fmt over the three files committed unformatted
- shrink the raw-wallet-dispatch debt ledger: creator.rs no longer
  dispatches raw "op":"accounts" (rewired onto RuntimeWalletAuthority)
- browser wallet-read timeout tests: measure the paused virtual clock
  (tokio::time::Instant) instead of wall time, which flaked under load
- wrong_session_key_fails_closed: assert the CEK never materializes
  rather than relying on PKCS#7 unpadding to reject (~1/256 flake)
@irzhywau

Copy link
Copy Markdown
Contributor Author

@andersalm it's now fixed, e2e mint -> open/playback is working now.

Im not sure if the implementation fit with the intended way you were thinking but today dkms and dDRM workflow is working on top of the ESP basically.

Any further insights would be welcome so we can adjust it when working it on the new branch based on the collaboration-candidate

…inks (PoC)

A purpose-built logging library (not log/tracing) with five severity levels and a
multi-sink writer interface, wired into the Rust components touched by the recent
dDRM/dKMS work as a proof of concept. Motivated by that debugging session, where
diagnostics had to be added ad-hoc across `eprintln!` providers and two hand-rolled
per-component leveled loggers.

Crate elastos/crates/elastos-logger:
- Level: Trace < Info < Warn < Error < Critical; a record emits iff level >= threshold.
  "debug" is accepted as an alias for Trace.
- LogSink writer interface (the SOLID extensibility seam): StderrSink, StdoutSink,
  FileSink, VecSink (test double). A future provider sink is one more impl with no
  change to Logger.
- Logger facade fans a single record out to many sinks at once; process-global
  init/enabled/emit with a pre-init stderr@Info fallback so early logging never panics.
- LoggerConfig + resolve_level with precedence CLI > env > default(Info); each binary
  owns its own arg parsing and chooses which env vars it honors.
- log_critical!/log_error!/log_warn!/log_info!/log_trace! macros (lazy — no formatting
  cost when below threshold) + fp() privacy fingerprint so TRACE stays secret-free.
- Dependencies: std + time + sha2 + hex only. Full unit coverage of level filtering,
  parsing, fan-out, config precedence, macros, and fp.

Wiring (the components changed in the last commits):
- key-provider, chain-provider, wallet-provider: eprintln! -> levelled logging.
- dkms-authority, dkms-carrier-node: bespoke LogLevel/oplog! subsumed into the shared
  crate; the legacy DKMS_*_LOG_LEVEL env vars are preserved; every lifecycle/error line
  converted and the doubled component prefix dropped so the level knob governs all output.
- docker-compose: the local dKMS quorum nodes default to trace.

The gateway (elastos-server) and JS/browser capsules are intentionally out of scope for
this PoC and fold in during the system-wide rollout.

Plan: docs/superpowers/plans/2026-08-17-elastos-logger.md
@andersalm

Copy link
Copy Markdown
Contributor

@irzhywau Thanks. The working mint -> open/playback path is useful. We will use it as product and test reference, but we still will not merge this PR as one branch.

The extraction has moved forward:

  • feat/protected-content-contracts at 1865595f: canonical identities, rights, release requests, node decisions, contributions, and terminal receipts.
  • feat/protected-content-custody at 29879b25: share custody, recipient-sealed release, and durable replay handling.
  • feat/protected-content-key-reconstruction at a8d0a9a6: threshold reconstruction inside the decrypt boundary.
  • Local source-only follow-ups now cover the private provider protocol, payload sealing, and staged decrypt output. They are not installed product paths yet.

We will bring over the PR's user flow: Creator publish, buyer purchase, and viewer open/play. We will also reuse reviewed node-operation ideas such as encrypted share storage, signed contributions, quorum failure tests, health checks, and rotation runbooks.

The final boundary is different from the current PR wiring:

  • Capsules make typed protected-content requests. They do not select nodes or network routes.
  • Runtime derives authority, selects providers, coordinates the operation, and records audit.
  • Carrier transports remote provider calls. No IP, port, WireGuard, or custom ALPN enters the capsule or product contract.
  • Each custody node independently checks the exact signed operation and rights evidence, then returns one recipient-encrypted signed contribution.
  • The decrypt boundary reconstructs and uses the CEK. Runtime, Carrier, and capsules never receive it.

We are adding one missing policy layer before product integration: a signed eligible custody pool and a fixed per-object committee. The first release should use 2-of-3 nodes across independent operators or failure domains. Pool changes must not silently replace an object's nodes. Replacement requires an explicit re-share and a new signed custody epoch. We are not adding staking, election, or consensus in this release.

The most useful follow-up from your side would be a short map of:

  1. the exact Creator and viewer UI files needed for publish, buy, open, and play;
  2. the smallest dKMS node files needed for encrypted share storage and signed release;
  3. which of those files still depend on direct TCP, WireGuard, or the custom ALPN bridge.

The new elastos-logger proof-of-concept at the PR tip is separate work. We will not include it in the protected-content extraction.

@SashaMIT

Copy link
Copy Markdown
Contributor

@andersalm Re: your required gate "new providers and capsules are present in the normal installer before any installed-product claim" — we've set up CI that mechanically proves this: #19 runs setup-source-home.sh + installed-provider-verify.sh on clean linux-amd64, linux-arm64, and darwin-arm64 runners on every PR, with a dispatch input for testing any SHA. It already caught a clean-machine install break (the Browser VM guest helpers' musl target was never ensured; fixed in 3df7a3aba). Details on #19.

(For avoidance of doubt: the failing CI / Test (push) on this PR is this branch's own workflow — browser_reconciliation::shutdown_cancels_active_reconciliation_and_releases_its_durable_claim, the timing flake @irzhywau documented as pre-existing. 1396 passed, 1 failed. Not the new jobs.)

@irzhywau

Copy link
Copy Markdown
Contributor Author

@SashaMIT re-run the failing job (without any changes) seems have the failure gone :-/
anyway before final push I've tested it and ran the ci workflow on my local machine

@SashaMIT

Copy link
Copy Markdown
Contributor

Thanks. Good the re-run went green. Still treating this PR as source material for the smaller extract branches, not as one merge, per Anders.

@andersalm

Copy link
Copy Markdown
Contributor

@irzhywau @SashaMIT update since the last extraction note.

PR #15 remains useful source material, not the merge target. The next source-only custody slice is now complete locally:

  • branch: feat/protected-content-custody-provider
  • commit: f7cd6c3dfe4fc3f6899c88af3ee4c082b49e3a49
  • tree: b9b93460f66b94759100b5993887e24c599e27a8

This branch now proves the custody-provider boundary:

  • each object is bound to the exact custody pool, epoch, committee authorization, node set, and threshold;
  • each selected node receives and stores only its own sealed share;
  • provisioning and release require the expected Runtime issuer and local node identity;
  • node state is owner-only, durable, restart-validated, corruption-detecting, and exact-idempotent;
  • release requires a valid signed rights decision and returns only a recipient-sealed contribution;
  • provider frames are bounded, shutdown is strict, and responses do not expose CEKs, raw shares, routes, Carrier details, addresses, ports, paths, or credentials.

The relevant contract, custody, and native provider-process tests pass. Independent review found and closed path-redirection, unbounded-frame, shutdown, secret-zeroization, and rights-denial classification issues.

From PR #15, we kept the useful product behavior and test ideas: the mint/open/playback flow, threshold custody shape, one-share-per-node direction, recipient-sealed contributions, CEK commitment, and lifecycle/failure cases.

We did not carry forward the public aggregate shares[] shape, capsule-owned authority, raw CEK operations, direct network topology in product contracts, compatibility rails, or static authorization fallback.

The new provider is still local, unpublished, unregistered, and not installed. The old provisional key-provider remains the only active registered path. Runtime and Library have not been cut over, so this is not yet a product completion claim.

Next:

  1. Atomically register and select custody providers in Runtime, remove the old key/DRM authority path, and prove allow, deny, replay, restart, and audit with no fallback or dual authority.
  2. Connect recipient contribution collection to private reconstruction/decryption and scoped viewer output.
  3. Run the real installed mint -> buy -> open -> play flow, including deny, tamper, replay, restart, and cleanup cases.

So PR #15 remains valuable as working UX and integration evidence, while the merge path stays the smaller reviewed protected-content branches.

@irzhywau

Copy link
Copy Markdown
Contributor Author

Protected-content alignment — preliminary brainstorming

Date: 2026-08-18 · Revised: 2026-08-20 (remote refresh: custody-provider published, three
new stack branches -wallet-rights/-runtime/-rights, PR #22 journey plan, truth-pass docs)
· Revised again 2026-08-21: digested the reviewer insight note (§10) + code verification;
restructured to be self-contained for AI agents (§0).
Status: preliminary — input for the next brainstorming/planning session, not a decision record.
Sources: PR #15 body + full conversation thread, the pinned refs in §0, and the repo truth
chain (PRINCIPLES.md → docs/ARCHITECTURE.md → state.md → TASKS.md → AGENTS.md).


0. How to use this document (for humans and agents)

This file is the working synthesis for the protected-content alignment program: getting
a working e2e Mint → Play/Read on the canonical boundary by mining PR #15
(feat/dkms-esp-port) as reference while building on the extraction stack. It is a
navigation and analysis aid — it is not a truth surface. If anything here disagrees
with the truth chain (PRINCIPLES.md → docs/ARCHITECTURE.md → state.md → TASKS.md
→ AGENTS.md → the per-surface contract), the truth chain wins; fix this file.

Rules for agents working from this doc:

  • Architecture design reference: docs/system-map/. For any architecture question
    (tiers, trust boundaries, who owns what, where a new component sits), read
    docs/system-map/c4.md first (canonical; viewer.html is a hash-bound projection,
    tree.md maps responsibilities to paths), then the per-surface contract it delegates
    to. Protected content appears in the map today only as the CEK invariant (c4.md:102)
    and a traceability row (c4.md:674) pointing at docs/PROTECTED_CONTENT.md — the
    coordinator/custody/decrypt tier is deliberately in prose contracts, not yet drawn.
    Any map edit requires the change-gate record (Layer / Capability / Hidden detail /
    Done-check) and a passing node scripts/system-map-check.mjs.
  • Everything on the feat/protected-content-* stack is source-only: not registered,
    not installed, no product claims. Never describe it as shipped or working product.
  • The reviewer (Anders) repeatedly reviews local, unpublished work in PR-thread notes
    before pushing it
    — a claim in a note that has no matching published code is normal
    here, not an error. Verify against refs before asserting either way.
  • Branch bases matter (see AGENTS.md): do not create branches or push without the
    user's explicit base choice and instruction.
  • Verification entry points: node scripts/system-map-check.mjs (map edits),
    scripts/protected-content-provider-contract-smoke.sh (provisional providers —
    labeled a retirement guard, not architecture proof), just verify-ci /
    just verify-capsules (on feat/dkms-esp-port only), per-crate
    cargo test -p elastos-protected-content-{contracts,custody,runtime,rights}.

Pinned refs (all verified 2026-08-21; re-run git fetch origin --prune before relying):

Ref SHA Role
origin/main d358dedb stable line; also PR #15's base (upstream/0.7-dev tip)
review/collaboration-candidate 5f1fb004 current base; system-map docs merged at tip
b07160cf — fork point of the extraction stack (3 commits behind candidate tip)
origin/feat/dkms-esp-port 27d85c6f PR #15 — working-prototype reference, will not merge
origin/feat/protected-content-contracts 1865595f stack step 1
origin/feat/protected-content-custody 29879b25 stack step 2
origin/feat/protected-content-key-reconstruction a8d0a9a6 stack step 3
origin/feat/protected-content-custody-provider f7cd6c3d stack step 4
origin/feat/protected-content-wallet-rights 2c69d0c2 stack step 5
origin/feat/protected-content-runtime b00bfeeb stack step 6
origin/feat/protected-content-rights 43a83e5b stack tip; carries the truth-pass docs
PR #22 (cursor/protected-content-journey-plan-af9e) — proposal-only journey plan, unreviewed

Reading order: §1–2 (goal + canonical invariants) → §5–6 (stack + gap) → §10 (latest
reviewer insights + verification deltas) → §12 (concern coverage audit) → §11 (open
questions). §3–4 are the PR #15 inventory/decision history; §7 the file-map deliverable
draft; §8–9 the side-tracks.


1. Goal

A working application with e2e Mint → Play/Read — like feat/dkms-esp-port demonstrates —
but implemented on the canonical boundary and rebuilt on review/collaboration-candidate
piece by piece to keep review possible. dkms-esp-port is the product and test
reference
(Anders, PR #15, 2026-08-17): its flow, tests, and node-operation ideas
are mined; its wiring (capsule-owned transports, WireGuard/ALPN bridge) is not.

2. Source-of-truth status

  • origin/docs/elastos-system-map is already merged into review/collaboration-candidate
    (tip 5f1fb004 is the merge commit). The current working tree IS the source of truth.
  • The map (docs/system-map/{README,c4,tree}.md + viewer.html, gated by
    node scripts/system-map-check.mjs) holds no statuses by design. Status lives in
    state.md; open work in TASKS.md.
  • No doc names feat/dkms-esp-port or the feat/protected-content-* branches. The only
    "port from" semantics are PC2-as-implementation-reference (docs/PROTECTED_CONTENT.md:41,86,92).

Already implemented on the candidate (the "foundational parts"):

  • Fail-closed protected-content provider skeleton: drm-provider, rights-provider,
    key-provider, decrypt-provider registered under elastos://…, typed validation,
    everything returns fail-closed/not_configured (docs/PROTECTED_CONTENT.md "Current
    implementation"; proof scripts/protected-content-provider-contract-smoke.sh).
  • Typed chain-provider has_access_by_content_id reads (configured selectors only).
  • content-provider sealed-object publish gate (elastos.sealed.object/v1 completeness).
  • The collaboration base: signed Profile authority, Runtime-owned Carrier (iroh 1.0.2),
    Profile-bound conversations/Chat/People, Home shell chrome, boundary hardening.

Still todo / to-pick (adjusted, not copied): everything that makes those providers real —
dKMS custody backend, decrypt/render backend, producers/encryption, viewers, commerce.

Canonical boundary invariants (the bar every ported piece must meet):

  • CEK: "raw CEKs never enter Runtime, Carrier, an ordinary app, or a viewer"
    (docs/PROTECTED_CONTENT.md:166, docs/GLOSSARY.md CEK entry, c4.md:102).
  • Route selection: "Runtime selects the route after authorization; the caller does not
    name a peer, host path, backend, or transport" (docs/PROTECTED_CONTENT.md:152).
  • Transport: same-node dKMS = private Runtime-owned adapter; off-box = Carrier as the
    canonical transport
    , "instead of a parallel app-visible TCP, HTTP, or VPN contract"
    (docs/CARRIER.md "dKMS over Carrier"). Carrier stays an untrusted byte carrier;
    endpoint auth never authorizes key release.
  • Recommended CEK rail: dKMS-direct sealing to a one-time decrypt-session key; Runtime may
    relay only transcript-bound sealed material (docs/PROTECTED_CONTENT.md options table).
  • Topology honesty: all-on-one-machine quorum is a dev/contract-test topology only.

3. PR #15 inventory (what exists on feat/dkms-esp-port)

562 files, +228,591 / −1,605 vs base upstream/0.7-dev @ d358dedb; 20 commits.

Area Key files (branch: feat/dkms-esp-port)
Creator publish/mint capsules/creator/; elastos-server/src/api/{creator.rs,mint_authority.rs} (passkey step-up on /api/create/mint; WalletProviderOperationV2 rewire in ffea5998/e148218b)
Encrypt/package pipeline capsules/encrypt-provider/ (+CENC vectors), capsules/cenc-core/, capsules/ddrm-media/ (mp4.rs,mpd.rs,rail.rs,seal.rs), capsules/ddrm-envelope/, capsules/ddrm-plan-runner/, capsules/publish-provider/, capsules/availability-provider/
Buyer purchase /api/market/* rail: gateway_marketplace.rs, market_reads.rs, buy_authority.rs, trade_authority.rs, owned_ledger.rs, rights_authority.rs, chain_tx.rs, wallet_signer.rs, gateway_passkey_step_up.rs; capsules/content-market/, capsules/marketplace-content/, capsules/chain-provider/
Viewer open/play /api/viewers/*: viewer_open.rs (~2.2k L, PARKED block), viewer_media.rs, viewer_object.rs, viewer_gateway.rs, viewer_grant_sign.rs, media_authority.rs, object_authority.rs, access_grant.rs, session_bounds.rs, session_lifecycle.rs; capsules/elacity-player/ (fMP4/CENC), capsules/ddrm-viewer/ (3D/docs, vendored three.js loaders)
dKMS node capsules/dkms-authority/ (main.rs 8.8k L, secure_store.rs, security_state.rs, counters.rs, node_chain.rs; release compile_error! guard on dev-modes/legacy-receipt-authz), capsules/dkms-keygen/, capsules/key-provider/ (client, 5k L), quorum smokes (1-node / 2-of-2 XOR / 2-of-3 Shamir GF(256)), scripts/dev/dkms-docker/ 3-node compose, docs/dkms/deploy/ runbooks
Decrypt boundary capsules/decrypt-provider/: CENC engine (src/cenc/), PQ envelope x25519+ML-KEM-768 (pq_envelope.rs), pixel-lock renderers (src/render/), 14 golden-vector suites
Transports (rejected wiring) see §7.3
Logger PoC (tip) elastos/crates/elastos-logger/ — see §8
CI / proof just verify-ci, just verify-capsules, dKMS release-invariant CI job (release must FAIL to build with dev features), ddrm-*-smoke.sh family, gateway_tests/dkms_rail.rs
Docs docs/dkms/ hub (50 files incl. 16-file history/), DKMS_OVER_CARRIER.md, specs under docs/superpowers/specs/

Working-e2e caveat. Mint → open/playback works and is accepted as reference, but
chain-gated open/buy is dev-lane-only: resolve_subject_address (viewer_open.rs)
is fail-closed pending the PARKED RequiredHomeLaunchToken subject rewire;
ELASTOS_DDRM_SUBJECT is dev-modes-only with a release boot guard. The dkms_rail.rs
gateway tests prove the join, deliberately not the decrypt legs (stated in-file).

4. PR #15 thread — decision log (chronological)

  1. 2026-08-12 SashaMIT (review) — targeted security pass; money-verb step-up, fragment
    launch tokens, live tokenId binding, dKMS release invariant all verified; 4 pre-merge
    items (wallet auto-approve lane, media-session expiry, trade resolver, Library re-buy id).
  2. 2026-08-12 irzhy — 2 items retained as ESP-1/ESP-2 (P1, marketplace/runtime seams);
    wallet auto-approve already dev-modes-gated; media-expiry did not reproduce
    (sweep_expired runs inline on every get_media_session).
  3. 2026-08-13 irzhy — maps Anders' review note: cross-wallet grant flaw closed
    (943c10d1, owner-only grants), execution model reconciled (WASI dropped); six open
    items acknowledged incl. wrong-base, carrier end-state, installer, trusted-core slimming.
  4. 2026-08-13 Anders — the governing decision. Do not merge as one branch; keep as
    source material; extract onto review/collaboration-candidate. August goal: one complete
    path (creator encrypts CEK; custody under declared quorum policy; buyer pays and receives
    signed right; open requires availability threshold AND dKMS threshold; raw CEK only inside
    reviewed custody + decrypt boundaries). Extraction order: 1 custody → 2 content
    availability → 3 Runtime integration → 4 protected open → 5 commerce.
    Gates: no dual
    authority, installer presence before installed-product claims, physical quorum tests
    (restart, revocation, partial failure, exact settlement). WireGuard/ALPN bridge =
    evidence only.
  5. 2026-08-17 irzhy — e2e mint → open/playback working.
  6. 2026-08-17 Anders — accepted as product/test reference; still no single-branch merge.
    Extraction now at contracts 1865595f / custody 29879b25 / key-reconstruction
    a8d0a9a6. Final boundary restated (capsules: typed requests only; Runtime: authority,
    provider selection, coordination, audit; Carrier: transport, no IP/port/WireGuard/ALPN in
    capsule or product contract; nodes: independent verification, one recipient-encrypted
    signed contribution; decrypt boundary: reconstructs and uses CEK). New policy layer before
    product integration: signed eligible custody pool + fixed per-object committee, first
    release 2-of-3 across independent operators/failure domains; pool changes must not
    silently replace an object's nodes — explicit re-share + new signed custody epoch; no
    staking/election/consensus.
    Requests the file map (§7). elastos-logger excluded
    from the protected-content extraction as separate work.
  7. 2026-08-18 SashaMIT — PR chore(ci): source-gate and source-home on ubuntu-latest and macos-14 #19 adds the installer CI gate (setup-source-home.sh +
    installed-provider-verify.sh on linux-amd64/arm64 + darwin-arm64 per PR); the PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15
    CI red is the known browser_reconciliation timing flake (1396/1397, green on re-run).
  8. 2026-08-18 18:51 Anders (latest, after the comment quoted in-session) — local branch
    feat/protected-content-custody-provider @ f7cd6c3d (tree b9b93460): custody-
    provider boundary proven source-only — per-object binding to exact pool/epoch/committee/
    node-set/threshold; one sealed share per node; owner-only durable restart-validated
    corruption-detecting exact-idempotent node state; release requires valid signed rights
    decision, returns only a recipient-sealed contribution; frames bounded; responses expose
    no CEKs/shares/routes/addresses/ports/paths/credentials. Kept from PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15: flow shape,
    threshold custody, one-share-per-node, recipient-sealed contributions, CEK commitment,
    failure cases. Not carried: public shares[], capsule-owned authority, raw-CEK ops,
    network topology in product contracts, compatibility rails, static auth fallback.
    Next: (1) atomic Runtime registry cutover removing old key/DRM path, (2) contribution
    collection → private reconstruction/decrypt → scoped viewer output, (3) real installed
    mint → buy → open → play with deny/tamper/replay/restart/cleanup.
  9. 2026-08-18/20 (branches, no new PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15 comments) — the custody-provider branch is
    published (origin/feat/protected-content-custody-provider @ f7cd6c3d), and the
    stack extends with three new published steps: feat/protected-content-wallet-rights
    (2c69d0c2), feat/protected-content-runtime (b00bfeeb), feat/protected-content-rights
    (43a83e5b) — see §5. The rights tip carries a truth pass (43a83e5b) that rewrites
    docs/PROTECTED_CONTENT.md/TASKS.md/ROADMAP.md/state.md with a formal
    "PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15 disposition" (keep-as-research / reimplement-at-boundary / reject lists) and
    an 8-step "Remaining sequence" — this supersedes §6's first gap table.
  10. PR docs(protected-content): mint→sell→buy→play journey implementation plan #22 (cursor/protected-content-journey-plan-af9e, based on custody-provider,
    one doc docs/PROTECTED_CONTENT_JOURNEY_PLAN.md) — proposal-stage mint→sell→buy→play
    journey plan with UI/UX focus (design tokens, Home spend prompt, consolidated Store);
    schedules the PARKED RequiredHomeLaunchToken rewire into its Phase 2; includes a
    reuse/re-derive/reject disposition vs PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15. No review comments yet.
  11. Housekeeping — PR chore(ci): source-gate and source-home on ubuntu-latest and macos-14 #19 CI matrix now ubuntu-latest + macos-14 (1d6b49e3); PR feat(home): paint house URUX onto freeze contracts #23
    feat/home-urux-on-freeze (Home UX track, separate); PR docs(agents): add Cursor Cloud environment setup notes #21 Cursor cloud env docs;
    stale shell-protocol/shell-ui branches deleted on the remote.

5. Extraction stack — precedence and per-step additions

origin/main (d358dedb)
  └─ review/collaboration-foundation ── product-integration ── … ── b07160cf  ← fork point
        ├─ +3 → review/collaboration-candidate @ 5f1fb004   (HEAD; adds system-map docs merge)
        └─ +7 → feat/protected-content-contracts @ 1865595f
                  └─ +14 → feat/protected-content-custody @ 29879b25
                             └─ +1 → feat/protected-content-key-reconstruction @ a8d0a9a6
                                       └─ +19 → feat/protected-content-custody-provider @ f7cd6c3d
                                                  └─ +1 → feat/protected-content-wallet-rights @ 2c69d0c2
                                                            └─ +2 → feat/protected-content-runtime @ b00bfeeb
                                                                      └─ +3 → feat/protected-content-rights @ 43a83e5b  ← stack tip

The stack forks 3 commits behind the candidate tip: it sits on the full collaboration +
Home-shell base but predates the system-map docs merge. Strictly linear, no merges.

  • contracts (+7, 62 files, +5.4k) — crate elastos-protected-content-contracts:
    canonical domain-separated binary encoding (JSON inspection-only); content/key-envelope/
    recipient/Profile identities; NodeSetV1; EIP-191 wallet-signed rights requests (strict
    0/1 recovery byte); signed rights receipts; bounded key-release requests; per-node signed
    decisions; recipient-sealed contributions (no Serde on sealed types); signed terminal
    receipts (window nesting enforced); 38 tests + golden vectors. Plus strict did:key
    Ed25519 authority threaded through did-provider/identity/collaboration files.
  • custody (+14, 42 files, +9.2k) — contract modules: CustodyEnvelopeV1 (+HPKE suite
    pins), SignedCustodyEpochV1 (immutable epoch over node keys/coordinates/threshold/
    suites — the review's "signed custody epoch"), narrow EVM RightsPolicyBodyV1,
    SignedRecipientKeyAuthorizationV1 (explicitly not possession proof),
    SignedRuntimeReleaseOperationV1 (verification yields replay-pending only). New crate
    elastos-protected-content-custody (forbid(unsafe_code); hpke 0.13, vsss-rs GF256
    Shamir): provision.rs (split + seal per node + CEK commitment), release.rs (node
    re-seals own share to recipient; 13 negative tests), replay_store.rs (1.9k L durable
    dual-key claim store, exact-result replay after restart, fail-closed torn state),
    reconstruct.rs (exact-threshold, commitment-checked). ~116 tests across both crates.
  • key-reconstruction (+1, 4 files) — reconstruction re-keyed to
    AuthenticatedRuntimeReleaseOperationV1 (old bare entry demoted to #[cfg(test)]);
    doc wording: reconstruction happens "inside the decrypt boundary"; 24 tests.
  • custody-provider (+19, 37 files, +15.7k) — now published. Lands all three previously
    "local follow-ups" plus the policy layer: private provider protocol (706f330d),
    authenticated chunk payload (3a72ca5d), local decrypt output boundary (4521fd4d),
    object-bound custody pool policy + object↔policy binding (11bda1a0, f1b3dd4b),
    one-node custody state + protocol (bbe0691d, c5bf7fca), provisioning authorization
    and validation (88c592df, 7777e88f), sealing derived from the verified committee
    (25d3e72f), pinned Runtime issuer validation (71f51efc), node-local durable share
    persistence (7d5467c9), and the custody provider process itself (1fefa07c); plus
    authority-truth doc reconciliations.
  • wallet-rights (+1) — wallet-provider signs rights requests: approval.rs,
    crypto/evm/signature.rs, validation.rs, new tests/approvals/protected_content.rs.
  • runtime (+2) — new crate elastos-protected-content-runtime
    (coordinator.rs, journal.rs): durable Runtime release state persistence
    (7f609118) and Runtime release-authority coordination (b00bfeeb); touches
    elastos-server/src/provider_resource.rs.
  • rights (+3, stack tip; CP..tip = 35 files, +8.4k) — new crate
    elastos-protected-content-rights; chain rights evidence via chain-provider
    (backends.rs, validation.rs, tests) (7c747253); typed rights decision evaluation
    (a32ae85a); and the truth pass 43a83e5b rewriting the four truth files (see §6).

All of it is stamped source-only at every level (crate docs, Cargo descriptions,
docs/PROTECTED_CONTENT.md remaining-sequence stages A–D). Known stated limits: no
Runtime wiring/replay orchestration/possession proof/decrypt/install; hpke 0.13 not
formally audited (no external crypto claim); commitment ≠ verifiable secret sharing;
replay store does not defend malicious same-UID rewrite; no durable operation-resume
journal (crash between claim and settlement ⇒ fresh operation).

Anders' three "local source-only follow-ups" map to documented stages: private provider
protocol → stage C atomic DTO/provider replacement; payload sealing → "Future sealed
decrypt handoff"; staged decrypt output → scoped output/opaque session handle in
DECRYPT_PROVIDER.md. The custody-provider branch (§4.8) has since landed the first of
these locally.

6. Gap to the goal

The governing order is now the 8-step "Remaining sequence" in the rights tip's
docs/PROTECTED_CONTENT.md (43a83e5b), which refines Anders' original 5 steps. Status
per step (everything on the stack remains source-only — nothing registered/installed):

# Remaining-sequence step (rights tip) Status today
1 Inactive Runtime provider lifecycle/registration/routing + durable reconciliation seam (Runtime owns selection, journal, audit, retry, terminal settlement, crash recovery) Started source-only — elastos-protected-content-runtime (coordinator.rs, journal.rs)
2 Typed rights-provider process adapter, Chain evidence only through Runtime/provider registry (no direct RPC, no caller-supplied evidence) Started source-only — elastos-protected-content-rights + chain-provider evidence + wallet-signed rights requests
3 Recipient key-possession proof + decrypt-session authority before CEK reconstruction/use Not started (Profile authorization alone declared insufficient)
4 Runtime-owned producer mint journal/state machine for one media flow (stage → bind facts → per-stage receipts → provision shares → recover after restart → commit availability → list; durable mint-abort on partial provisioning; first proof needs three distinct custody identities for 2-of-3) Exists locally, unpublished per the 2026-08-21 note (§10) — with three known problems to fix before further product work; nothing on published refs. Absorbs the old "content availability" step (availability commit is a mint stage)
5 Real Wallet purchase + exact Chain evidence + Runtime open + private reconstruction/decrypt + opaque scoped viewer output (no bearer playback URL; launch tokens stay HTTP-edge credentials) Not started — this is where PR #15's open/play flow gets rebuilt
6 Full inactive e2e proof: mint → buy → open → play, 3 custody providers, allow/deny/tamper/replay/restart/cleanup, no CEK/share/topology leakage Not started — adapt PR #15 dkms_rail as behavioral oracle (per PR #22)
7 Atomic replacement/removal of provisional drm/key/decrypt authority — no fallback, no dual authority Not started; only after 6
8 Minimum one-media UI + installed two-principal acceptance proof Not started — PR #22 journey plan covers the UI/UX side

Cross-cutting gaps (not owned by a single step):

  • Transport rewrite — key-provider's endpoint-aware client (Unix/tcp:/carrier:
    • sidecars) must become a Runtime-selected route behind the provider registry/Carrier
      adapter; the WireGuard mesh + ALPN bridge stay evidence only. See §7.3.
  • PARKED subject resolution — RequiredHomeLaunchToken rewire so chain-gated open/buy
    can leave the dev lane; prerequisite for step 4/5 being honest product paths anywhere.
  • Committee/pool policy — signed eligible custody pool + fixed per-object 2-of-3
    committee + re-share/epoch rotation: partially present in custody epoch types and the
    local custody-provider; the pool-signing and rotation runbook side is open. Note the
    availability corollary: with a fixed 2-of-3 committee and no silent replacement, losing 2
    operators permanently strands the object — rotation must be monitored and run before
    reaching threshold (rotation runbooks exist on esp-port as reference: docs/dkms/deploy/).
  • Installer gate — every new provider/capsule must be in components.json +
    setup-source-home.sh before installed-product claims; PR chore(ci): source-gate and source-home on ubuntu-latest and macos-14 #19 CI enforces on 3 platforms.
  • The owed file map — §7, Anders' explicit request to irzhy.

7. DRAFT — the file map Anders requested (review before posting to PR #15)

All paths on feat/dkms-esp-port.

Note (2026-08-20): the rights tip's docs/PROTECTED_CONTENT.md now carries a formal
"PR #15 disposition" — keep-as-research (threshold crypto, node-local custody,
recipient-sealed contributions, CEK commitment, negative tests), reimplement-at-boundary
(durable shard storage, DKG/rotation/re-share, pool/governance, provider roles,
Runtime-open scenarios), reject (public shares[], capsule-owned authority, raw-CEK ops,
rail_shim, old drm-provider orchestration, direct TCP/IP/port topology, static auth
fallbacks, standalone harness as product route). The map below stays useful as the
file-level answer to Anders' three questions, but should cite that disposition
rather than re-argue categories.

7.1 Creator and viewer UI files for publish, buy, open, play

Capsule surfaces:

  • Publish/mint: capsules/creator/{capsule.json,browser/index.html,browser/creator.js}
  • Buy: capsules/marketplace-content/browser/{index.html,app.js,api.js,styles.css}
    (+ wasm/main.rs); capsules/content-market/{capsule.json,src/main.rs} (microVM side)
  • Library/open-with: capsules/library/browser/{index.html,library.css,src/{actions,api,app,menu,render,tags}.js}
  • Play (media): capsules/elacity-player/{capsule.json,browser/{index.html,player.js,style.css}}
  • Read (non-media): capsules/ddrm-viewer/{capsule.json,browser/{index.html,viewer.js,style.css}}
    (+ vendored three.js loaders, VENDORING.md)
  • Home chrome pieces the flow depends on: capsules/home/browser/{home-spend-prompt.js,home-wallet-connector-host.js,home-shell-host.js},
    capsules/home-gui/browser/shell-windows.js, capsules/wallet-metamask/browser/wallet-metamask.js

Gateway counterparts (all elastos/crates/elastos-server/src/api/):

  • Publish/mint: creator.rs, mint_authority.rs, content_index.rs
  • Buy: gateway_marketplace.rs, market_reads.rs, buy_authority.rs, trade_authority.rs,
    owned_ledger.rs, rights_authority.rs, chain_tx.rs, wallet_signer.rs, gateway_passkey_step_up.rs
  • Open/play: viewer_open.rs, viewer_media.rs, viewer_object.rs, viewer_gateway.rs,
    viewer_grant_sign.rs, access_grant.rs, media_authority.rs, object_authority.rs,
    session_bounds.rs, session_lifecycle.rs, library.rs

7.2 Smallest dKMS node set for encrypted share storage and signed release

  • capsules/dkms-authority/src/main.rs — request/response ops, release invariant guard
  • capsules/dkms-authority/src/secure_store.rs — atomic 0600 no-follow durable share store
  • capsules/dkms-authority/src/security_state.rs — durable versioned revocations
  • capsules/dkms-authority/src/counters.rs — secret-free fail-closed tallies (health)
  • capsules/dkms-authority/src/node_chain.rs — node-local pinned-RPC rights self-check
  • capsules/dkms-keygen/src/main.rs — keygen/ceremony helper

(Reference material rather than product files: docs/dkms/deploy/ runbooks + DKG ceremony,
scripts/dev/dkms-docker/ quorum harness, scripts/ddrm-consumer-dkms-*-smoke.sh.)

7.3 Which files still depend on direct TCP, WireGuard, or the custom ALPN bridge

  • capsules/key-provider/src/main.rs — the main offender: raw TcpStream connects
    (~L775–838), endpoint-prefix selection (Unix path / tcp:HOST:PORT / carrier:→loopback
    sidecar DKMS_CARRIER_CLIENT_ADDR); knows WireGuard/ALPN context. Under the final
    boundary this client logic moves behind the Runtime provider registry/Carrier adapter.
  • scripts/dev/dkms-carrier-node/src/main.rs — node-side iroh bridge, custom ALPN
    elastos/dkms-authority/1, relays to 10.66.66.1:9443 (WireGuard mesh). Evidence only.
  • scripts/dev/dkms-carrier-client/src/main.rs — runtime-side loopback sidecar
    (127.0.0.1:9444, DID preamble, warm QUIC streams). Evidence only.
  • capsules/dkms-authority/src/main.rs — DKMS_AUTHORITY_LISTEN framed TCP serve
    transport (node-side; less problematic, but transport knowledge lives in the capsule).
  • Runtime-side (acceptable location under the final boundary, needs review not removal):
    elastos/crates/elastos-server/src/{carrier.rs,operator_control.rs}.
  • Clean already: decrypt-provider, encrypt-provider, cenc-core, ddrm-media,
    ddrm-envelope, the viewer capsules and market/creator surfaces (they talk gateway/
    provider APIs, not sockets).

8. Logging track (separate from the extraction — own branch)

Decision: port elastos/crates/elastos-logger to a dedicated feat/elastos-logger
branch off review/collaboration-candidate, run as its own PR track parallel to the
extraction. Status 2026-08-21: STARTED — worktree
/Users/maciz/www/ela.city/elastos-runtime-logger, branch feat/elastos-logger @
5f1fb004 (local only, not pushed); handoff brief for the implementing conversation at
<worktree>/.superpowers/sdd/2026-08-21-elastos-logger/brief.md. This respects Anders' exclusion
("separate work") while giving the whole system the tracing rail.

What the PoC already is (tip commit 27d85c6f):

  • Level Trace<Info<Warn<Error<Critical; case-insensitive parse with aliases; env fallback.
  • LogRecord + format_line(); LogSink trait (Send+Sync, non-panicking, drop-on-fail) —
    StderrSink/StdoutSink/FileSink/VecSink; Logger with OnceLock global + safe
    pre-init stderr fallback; resolve_level precedence CLI > env chain > default.
  • fp() = fp:+hex(SHA-256[..4]) privacy fingerprint; rule: TRACE must be secret-free.
  • Lazy macros log_trace!..log_critical! passing module_path!() as target.
  • Already wired: key/chain/wallet-provider, dkms-authority, dkms-carrier-node (legacy env
    vars preserved). Explicitly not yet: elastos-server, JS/browser capsules.

Rollout brainstorm (phases, each independently reviewable):

  1. Port the crate to the new branch + workspace registration + tests. Note: the
    referenced plan doc docs/superpowers/plans/2026-08-17-elastos-logger.md does not exist
    in the tree — recreate the plan under .superpowers/ or drop the reference.
  2. Gateway adoption (elastos-server): replace ad-hoc eprintln!/bespoke levels;
    component naming per surface; keep ELASTOS_LOG as the env root.
  3. Remaining Rust capsules/providers mechanical conversion.
  4. JS/browser capsules: a thin mirror (same levels/format) + gateway ingestion route,
    so shell/UI traces land in the same stream.
  5. AI-facing insight sink: the crate's own NOTE anticipates a ProviderSink — one more
    impl LogSink, no Logger change. Brainstorm: a structured JSON sink (level, component,
    target, ts, fp'd fields) writable to a bounded ring file per component, so an agent (or
    docs/DEBUG.md tooling) can tail/summarize runtime behavior for debugging. Keep it
    observe-only and secret-free by construction (fp() at call sites).

Constraint to respect: logging must not enter the protected-content extraction slices;
where those slices need logs, they adopt the crate only after it lands via its own track.

9. Docs & project-context track (CLAUDE.md + skill)

Decision: thin repo CLAUDE.md (auto-loaded every session) + an on-demand
.claude/skills/ skill; no second architecture ledger (system-map discipline:
"does not create a second architecture, glossary, backlog, or readiness ledger").

  • CLAUDE.md (<~80 lines): truth-chain order and when each file wins; branch roles +
    branching rules (base = upstream/XX-dev unless user chooses; ask before base
    selection); run Rust from elastos/; the standing verification gate (git diff --check,
    home-entropy-check.mjs, cargo fmt --check, narrow tests, system-map-check.mjs for
    map edits, protected-content-provider-contract-smoke.sh when touching those providers);
    publish/deploy verbs are user-gated; pointer to AGENTS.md for the full contract.
  • .claude/skills/protected-content-map/SKILL.md: the live navigation for this
    program — branch map (§5 diagram), extraction status table (§6), canonical invariants
    (§2), PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15 pointers and smoke entry points, the file map (§7). Loaded when working on
    protected-content/dKMS/dDRM topics. Keep it status-light: link state.md/TASKS.md
    instead of restating them, so it doesn't rot.
  • docs/dkms/ (50 files) stays on feat/dkms-esp-port as reference material; do not port
    it into the candidate line — the candidate docs already own the canonical contracts.
  • Possible follow-up: fold the durable parts of this brainstorm's §5/§6 into TASKS.md
    entries once the next planning session confirms ownership (keep volatile analysis here).
  • System-map update (deferred until Runtime integration lands): when the
    protected-content providers are registered/installed (remaining-sequence steps 1/7),
    docs/system-map/c4.md must gain the coordinator → rights-provider → custody providers
    → decrypt-provider elements (likely a Level 3 component view plus a dynamic
    release-operation view), each edit through the map's change gate
    (Layer / Capability / Hidden detail / Done-check) with
    node scripts/system-map-check.mjs green. Do not draw them earlier — the map must not
    depict uninstalled source-only components as product structure (readiness vocabulary
    rule, c4.md:34).

10. Reviewer insight note (2026-08-21) — digest + code verification

A narrative note (relayed by irzhy) restates the program and adds new specifics. Digest,
with each claim checked against the published refs:

Ownership split (Wallet proves right / Runtime authorizes+selects+records / one sealed
share per custody node / Carrier transports Runtime-selected calls / capsules = workflow
only, never CEK or node selection) — matches the published canonical architecture
(docs/PROTECTED_CONTENT.md "Canonical architecture" on the rights tip) and the
coordinator's shape (elastos-protected-content-runtime/src/coordinator.rs:
RuntimeRightsProvider/RuntimeCustodyProvider traits + WalletProviderOperationV2).

Mint flow (Runtime creates random CEK → encrypts content → m-of-n split → per-node
X25519+ML-KEM-768 seal → node stores only its share). Two findings:

  • PQ discrepancy — the published format is classical. The pinned suite is
    CUSTODY_HPKE_SUITE_ID_V1 = "hpke-rfc9180-base-x25519-hkdf-sha256-aes256gcm/v1"
    (custody_envelope.rs:11), hpke_helpers.rs pins X25519HkdfSha256, and the tip docs
    say verbatim "This helper does not make a PQ claim" (docs/PROTECTED_CONTENT.md:236).
    The note's "X25519 plus ML-KEM-768 from the first format, no classical fallback" is
    either newer local work or the activation bar — if the first shipped format must be
    hybrid, the published v1 suite cannot ship as-is (needs a hybrid suite id + envelope
    re-provision before any real content is minted, since there is deliberately no
    fallback/migration decoder).
  • Mint-side CEK in Runtime vs the CEK invariant. The published docs say "Runtime and
    capsules must never receive raw CEKs" (release-path framing); the note candidly states
    Runtime creates the CEK and sees plaintext at mint, listing it as a trust limit. Needs
    reconciliation: either a documented mint-side carve-out (Runtime mint boundary as
    reviewed custody) or moving CEK creation/encryption into a provider boundary later.

Pool/committee semantics (pool = signed eligible list; epoch freezes a selection;
committee authorization approves it; node set + threshold) — contracts exist and are
verified
: elastos-protected-content-contracts/src/custody_pool.rs (landed in the
custody-provider step, 11bda1a0/f1b3dd4b) validates signed pool + epoch against a
pinned expected_policy_authority. Confirmed: no product code loads a trusted signed
pool or selects a committee
— validation-only, exactly as the note says. Minimum
solution = one Runtime-pinned policy authority; no voting/elections/degraded-mint repair.
2-of-3 semantics: mint requires all 3 provisioned; playback needs any 2 (published
reconstruct.rs enforces exactly-threshold; provisioning seals to every committee node).

The three "Current Problems" (retry re-encrypts before loading the existing mint
journal; ciphertext stored before custody-provider preflight; docs claim terminal mint
abort but code keeps a nonterminal reconciliation obligation) — not verifiable on any
published ref
: the producer mint journal/state machine does not exist in the published
stack (the runtime crate's journal is the release journal,
runtime-release-journal/v1; "mint" greps hit only unrelated server code). Same pattern
as the custody-provider announcement: the note reviews local, unpublished mint-path work.
The docs half of problem 3 IS visible: the tip's Remaining sequence step 4 promises
"durable terminal mint abort" — so the divergence is real on the docs side.

Trust limits (Runtime sees plaintext at mint; decrypt boundary sees CEK+plaintext at
playback; any threshold of colluding nodes reconstructs; operator labels don't prove
independent ownership; rotation/revocation/retirement/orphan cleanup missing) — all
consistent with the published docs' own caveats; framed as completion requirements, not
design flaws.

10-step Path To Completion — refines the docs' 8-step Remaining sequence: adds the
three fixes up front (step 1), Runtime-pinned policy authority + signed pool loading
(step 2), a real immutable object provider (step 3), explicit Carrier connection for
remote custody (step 7), reuse of PR #15's tests as behavioral oracles (step 8; matches
§7/§6), and an independent cryptographic + protocol review before activation (step 10).
Final rejects list matches the in-docs "PR #15 disposition" plus "direct chain booleans"
(rights must flow as evidence-backed typed decisions, not bare hasAccess bools — consistent
with RightsEvaluationEvidenceV1 in policy.rs and the rights crate's typed decisions).

11. Open questions for the next session (revised 2026-08-21)

Resolved since 2026-08-18: custody-provider is published; the PARKED rewire is scheduled
in PR #22 Phase 2, pending review; availability is absorbed into the mint journal
(remaining-sequence step 4); recipient key-possession proof is an explicit gate (step 3);
the pool/committee policy layer has published contracts (custody_pool.rs) — loading and
selection remain product work, per §10.

Still open:

  1. Is the file map (§7) still owed to Anders as asked, or is the in-docs "PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15
    disposition" considered its answer? Recommend still delivering it — the disposition is
    category-level, the ask was file-level.
  2. PR docs(protected-content): mint→sell→buy→play journey implementation plan #22 (journey plan) has no review yet — adopt it as the working plan for steps 4–8,
    amend it, or wait for Anders' verdict before aligning port work to it?
  3. Where does our own work plug in next? Candidates: remaining-sequence step 3 (recipient
    possession proof — small, well-bounded, unblocks reconstruction use) or contributing to
    the mint-path fixes once that code publishes; steps 1–2 appear actively owned upstream
    (runtime/rights crates), and the note's Path-To-Completion step 1 (journal-first retry,
    provider preflight, partial-mint truth) reviews code we cannot see yet.
  4. PQ format decision (new, from §10): the published custody suite is classical-only
    (hpke-rfc9180-base-x25519-hkdf-sha256-aes256gcm/v1), while the note requires
    X25519+ML-KEM-768 "from the first format" with no fallback. Does a hybrid suite id
    replace v1 before any real mint, and who owns that change?
  5. Mint-side CEK carve-out (new, from §10): the note has Runtime creating the CEK and
    seeing plaintext at mint; the published docs say Runtime never receives raw CEKs.
    Which document gets the reconciliation — a mint-boundary carve-out in
    docs/PROTECTED_CONTENT.md/GLOSSARY.md, or a plan to move mint-side encryption into
    a provider boundary?
  6. Ask for the local mint-path code (journal/state machine, immutable object provider) to
    be published so the three "Current Problems" fixes can be tracked against real refs?
  7. Committee policy: what test topology proves "independent operators or failure domains"
    for 2-of-3 beyond the mandated "three distinct custody identities and state roots"
    (docker 3-node stays dev-only)?
  8. For the logger's JS mirror (§8 phase 4): gateway ingestion route vs. console-only first?
  9. The stack still forks at b07160cf: will it be rebased onto 5f1fb004 (system-map
    docs) or merged forward — and does port work target the stack tip
    (feat/protected-content-rights) or the candidate?

Per-question coverage status as of the 2026-08-21 branch audit: see §12.

12. Concern coverage audit (2026-08-21) — active branches vs §10/§11

Audited refs: the full feat/protected-content-* stack (tips unchanged from §0 pins),
cursor/protected-content-journey-plan-af9e @ f0b0aeaf (PR #22's 350-line
docs/PROTECTED_CONTENT_JOURNEY_PLAN.md, now read in full), feat/dkms-esp-port.
New facts found during the audit:

  • elastos-protected-content-provider-contracts (crate) and capsules/custody-provider
    (the provider process) are published on the stack tip — the private provider protocol is
    complete as source, not just described.
  • The runtime coordinator actively enforces the no-topology-leak invariant in tests:
    a forbidden-marker assertion rejects CEK, raw_share, provider_route, endpoint,
    host, ip, port, Carrier, http(s):// on its serialized surface
    (coordinator.rs ~L1295).
  • PR docs(protected-content): mint→sell→buy→play journey implementation plan #22's plan is substantive: phase-sequenced (0–5), principle-mapped, and schedules the
    PARKED subject rewire in Phase 2 with PR dKMS/dDRM foundation on the ESP substrate (inspired from flint-5.0) #15's dkms_rail adopted as a red-first
    behavioral oracle. It also surfaces a WIP item we hadn't tracked: the custody-provider
    process tests are wall-clock-coupled and need clock injection (its Phase 0).
Concern (§11) Verdict Evidence
Q1 file map owed to Anders Partially covered by PR #22 §8: the adopt/harden list names the Creator/Store/viewer/spend-prompt files and seams (map part 1). Parts 2 (smallest node files) and 3 (transport deps) exist only in our §7. Still worth delivering as the direct answer.
Q2 adopt PR #22? Still proposal, zero review comments — but it is the only artifact scheduling the PARKED rewire (Phase 2), the e2e oracle, Store consolidation, and naming convergence. Recommend: review it rather than write a competing plan.
Q3 where to plug in Better informed. Concrete unclaimed items: PR #22 Phase 0 (clock-injection fix for custody-provider tests; naming convergence; adopting dkms_rail as red oracle) and remaining-sequence step 3 (recipient possession proof — see below).
Q4 PQ format Contradiction, now three-way. Insight note: hybrid X25519+ML-KEM-768 "from the first format, no classical fallback". Published code + docs: classical suite, "no PQ claim". PR #22: states the classical suite as the custody invariant (§6) and defers PQ-hybrid custody past 0.7 (§7). Needs an explicit decision before any real mint — a deferred hybrid with no fallback decoder means re-provisioning every object minted classical.
Q5 mint-side CEK Diverges from the note, converges with the docs. PR #22 §4.1 places CEK minting "inside the encrypt/custody boundary", not in Runtime — matching the published invariant and contradicting the note's "Runtime creates the CEK / sees plaintext at mint". The carve-out question stands, but the plan's answer is the stricter one.
Q6 publish mint-path code Not covered. No mint journal/state machine on any ref; the note's three Current Problems remain unverifiable. PR #22 predates the note and does not mention them; its Phase 1 would re-derive the dev-lane mint slice.
Q7 committee test topology Tension surfaced. Docs remaining-sequence step 6 requires three distinct custody identities before cutover; PR #22 accepts one-node custody for the dev lane and defers "multi-node production custody topology" past 0.7 — while its Phase 5 performs the atomic cutover. Whether 0.7 cutover requires the 3-node proof is unresolved between the two documents.
Q8 logger Untouched anywhere (no elastos-logger reference on any stack branch or PR #22) — consistent with the exclusion; our §8 track stands.
Q9 fork/rebase Mechanism proposed, decision open: PR #22 Phase 0 proposes "a single 0.7 integration branch that composes collaboration + this journey".
§10: pool loading in product Not covered — still validation-only contracts; PR #22 does not schedule signed-pool loading/committee selection (the note's Path step 2).
§10: recipient possession proof Gap in PR #22 — required by docs remaining-sequence step 3 and the note's Path step 4, but absent from PR #22's phases and its deferred list. Must be added to whichever plan wins, or it silently falls through.
§10: Carrier connection for remote custody Planned only (note Path step 7; PR #22 principle-4 row). No transport code in the stack crates — by design (coordinator uses in-process provider traits; the forbidden-marker test keeps transport out of the contract surface).
§10: terminal-abort docs truth Not covered — the docs still promise a terminal mint abort; the corrective code is unpublished.
§8/§9 tracks (logger rollout, CLAUDE.md + skill) Ours alone — nothing on any branch touches either.

Net reading: the authority spine (contracts → custody → provider process → wallet-signed
rights → runtime coordination/journal → typed rights evaluation) is published and further
along than §6's table suggested when written; the product journey has a serious but
unreviewed plan (PR #22); and the three sharpest unresolved items are the PQ format
decision, the mint-path publication (with its three fixes), and the possession-proof gap
in PR #22.

@andersalm

Copy link
Copy Markdown
Contributor

@irzhywau @SashaMIT

This replies to the 21 Aug alignment note.

Update

  • feat/protected-content-rights 43a83e5b
  • feat/protected-content-runtime-lifecycle 34465959
  • this PR head feat/dkms-esp-port 27d85c6f
  • review/collaboration-candidate 5f1fb004

34465959 is on GitHub. It adds protect, custody, and decrypt
processes, an inactive mint journal, a chain policy resolver,
recipient possession in the custody crate, and the wrap suite
elastos-xwing-draft06-hkdf-sha256-aes256gcm/v1.

At 34465959, Library still stops before protect dispatch.
TASKS.md there still has creator mint/list wiring open.

Four later local commits exist (35ea84cc .. b8b527ac).
They are not on GitHub. They add Library mint, object.buy,
and Library viewer open/play in source tests. That object.buy
writes protected-content/runtime-purchases. It is a Runtime
journal. It is not a Base buyAccess call.

Installed drm / rights / key / decrypt stay the live
product path.

The 17 Aug mint → open report on this PR remains the product-flow
reference. I have not re-run that path on 27d85c6f.

§11

  1. §7 is the file-level map I asked for. Thank you.

  2. PR docs(protected-content): mint→sell→buy→play journey implementation plan #22 is still an open draft on
    feat/protected-content-custody-provider. The lifecycle tip is
    past that base. Do not treat PR docs(protected-content): mint→sell→buy→play journey implementation plan #22 as the current plan until
    it is revised against 34465959 or later.

  3. Proposal: the shared next question is the access-function
    mismatch below. That question affects both lines.

  4. 43a83e5b still pins
    hpke-rfc9180-base-x25519-hkdf-sha256-aes256gcm/v1 and says it
    makes no PQ claim. 34465959 already pins
    elastos-xwing-draft06-hkdf-sha256-aes256gcm/v1.
    Open: which suite is the first installed mint?

  5. On 34465959, protect creates ContentEncryptionKeyV1.
    Runtime mint state stores content_key_commitment.
    Open: keep that split in the docs?

  6. The inactive mint journal is on GitHub at 34465959.
    Library mint / buy / open is only in the four local commits.

  7. The published bar is still three distinct custody identities
    and state roots. We do not have a proof of independent
    operators.

  8. This PR tip is now the logger PoC (27d85c6f). I have no
    product decision on a JS mirror.

  9. b07160cf is in the rights / lifecycle line. It is not in
    this PR. This PR and rights meet at d358dedb
    (upstream/0.7-dev / main).
    5f1fb004 is the current candidate. It already contains
    system-map 5a1fed5c. Rights and lifecycle do not contain
    5a1fed5c.
    Open: which line should the next port work use?

Alignment problem

The extraction policy ABI, on GitHub, is only:

hasAccessByContentId(string contentId, address subject, string right) → bool

Content id on that line is
content:{hex(EncryptedContentIdentityV1.canonical_hash())}.

The live base.ela.city script I fetched today
(/assets/index-D3GMdaVA.js, sha256
287951a0…41f199) binds Base 8453
AUTHORITY_GATEWAY to
0x09dBe796f40ECEffEAccf243c3d758C4c1d8D87D.
That script’s hasAccessByContentId is:

hasAccessByContentId(address accessor, bytes16 contentId) → bool

This PR’s chain-provider names that second form the production
read (selector 0x54d42821) and names the string form
legacy/guessed.

A dummy eth_call of the live function reverted. The data
started with 0xcad88223. I do not have the Solidity error
name.

I do not know:

  • whether the proxy also implements the string function
  • whether ela.city will add a right argument
  • whether a Runtime purchase must call buyAccess for the
    site to see it

So the two lines do not yet share one on-chain access fact.

Decisions that unblock the next shared step

  1. Which function is the shared access check?
  2. Which bytes are the on-chain content id?
  3. Does the chain store View / Download, or only yes / no?
  4. Must a Runtime purchase write the same gateway that
    base.ela.city reads?

Irzhy: is hasAccessByContentId(address, bytes16) on proxy
0x09dBe796… the intended access check for this PR?


Where we are

The extraction line is public. feat/protected-content-rights is 43a83e5b. The child feat/protected-content-runtime-lifecycle is 34465959. That child has protect, custody, decrypt, an inactive mint journal, possession in the custody crate, and the X-Wing wrap suite.

Library mint, object.buy, and viewer open exist only in four local commits after 34465959. They are not on GitHub. That buy writes a Runtime journal under protected-content/runtime-purchases. It does not call Base buyAccess.

Your 17 Aug mint → open path is still the product-flow reference. I have not re-run it on today’s PR tip (27d85c6f, the logger).

The interop problem

Runtime and base.ela.city do not ask the chain the same question.

Runtime’s published policy is only:

hasAccessByContentId(string contentId, address subject, string right) → bool

The content id on that line is content:{hex(canonical hash of EncryptedContentIdentityV1)}.

The live site script I fetched today (/assets/index-D3GMdaVA.js) binds Base 8453 AUTHORITY_GATEWAY to 0x09dBe796…. Its function is:

hasAccessByContentId(address accessor, bytes16 contentId) → bool

Your chain-provider on this PR already treats that second form as the production read (0x54d42821) and keeps the string form as legacy.

A dummy call of the live function reverted. The data started with 0xcad88223. I do not know the Solidity error name.

I also do not know whether the proxy has the string function as well, whether a right argument was dropped on purpose, or whether a Runtime purchase has to call buyAccess before the site will treat the wallet as entitled.

If those stay different, we get two “has access” facts. Home and base.ela.city will disagree about the same object.

What I want from you

You know this contract and the mint / KID / tokenId path. I do not want to guess it into a Runtime policy.

Please say:

  1. Is (address, bytes16) on proxy 0x09dBe796… the access check we should share, or is there a better function on that gateway?
  2. What are the 16 bytes? CENC KID, something derived from mint, or tokenId in disguise?
  3. Why does a dummy KID revert instead of return false? Is that intentional?
  4. Was the right string dropped on purpose, or did View / Download never live on chain?
  5. If Runtime mints and later sells, must it call the same buyAccess (or equivalent) so base.ela.city can see the buyer? Or is a Runtime-only journal enough for a first install?
  6. Which line should your next work sit on: feat/protected-content-runtime-lifecycle, feat/protected-content-rights, or review/collaboration-candidate (5f1fb004)?

Proposal (Elastos side, not decided)

If both products must see the same paid / may-open fact, Runtime should speak the function ela.city already calls. Do not deploy a second rights contract that only Runtime understands.

That would mean:

  • on-chain content id = the 16-byte id ela.city already uses (if that id is the CENC KID, protect already creates one)
  • EncryptedContentIdentityV1 stays as ciphertext integrity, pinned in the signed policy, not used as the EVM argument
  • View / Download stay on the signed policy until the site actually sells those as separate on-chain rights
  • CEK creation and 2-of-3 release stay in protect / custody / decrypt. The gateway answers “may open”, not “here is the key”

If that is wrong for how AuthorityGateway, Channel, and tokenId actually work, say so. Better we change the Runtime ABI, or you tell us the site will add a function, than we ship two ledgers and debug them later.

The four local commits can wait until this is clear. The string ABI is already public on 43a83e5b and 34465959, so the decision is already in front of us.

@irzhywau

irzhywau commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor Author

@andersalm Answers on the access check, plus status from our side.

hasAccessByContentId — answers

1. Which function is the shared access check?
hasAccessByContentId(address accessor, bytes16 contentId) → bool on proxy
0x09dBe796f40ECEffEAccf243c3d758C4c1d8D87D is the intended one. That is what the
AuthorityGateway contract implements, and it is already on-chain today. The string form
should be treated as legacy; the extraction's published string ABI needs to move to this
form.

2. What are the 16 bytes?
The KID — the CENC KID from the dDRM pipeline. The gateway resolves it through
CentralStorage.ipReference(bytes16); a minted/bound content id has an ipReference
mapping, and access is evaluated against that.

3. Why does a dummy KID revert instead of returning false?
Intentional. 0xcad88223 is the selector of the custom error UnboundContentId(bytes16)
(cast sig 'UnboundContentId(bytes16)' → 0xcad88223). It is thrown when the bytes16
KID has no mapping at all — unbound/unknown content id reverts; a bound KID for which the
accessor has no right returns false. Your dummy call reverted because the KID was not
mapped, not because of a policy failure. Trace of the same call against the gateway:

❯ cast call 0x09dBe796f40ECEffEAccf243c3d758C4c1d8D87D "hasAccessByContentId(address,bytes16)(bool)" 0x0917Aa260359670F7855a5454c630993ce40C52D 0xafb03037e2ed4d51b6921d8662d72e39 --rpc-url http://localhost:8545 --trace
Traces:
  [20627] 0x09dBe796f40ECEffEAccf243c3d758C4c1d8D87D::hasAccessByContentId(0x0917Aa260359670F7855a5454c630993ce40C52D, 0xafb03037e2ed4d51b6921d8662d72e39)
    ├─ [15681] AuthorityGateway::hasAccessByContentId(...) [delegatecall]
    │   ├─ [9773] TransparentUpgradeableProxy::fallback(0xafb03037e2ed4d51b6921d8662d72e39) [staticcall]
    │   │   ├─ [4831] CentralStorage::ipReference(0xafb03037e2ed4d51b6921d8662d72e39) [delegatecall]
    │   │   │   └─ ← [Return] 0x0000000000000000000000000000000000000000, 0
    │   │   └─ ← [Return] 0x0000000000000000000000000000000000000000, 0
    │   └─ ← [Revert] UnboundContentId(0xafb03037e2ed4d51b6921d8662d72e39)
    └─ ← [Revert] UnboundContentId(0xafb03037e2ed4d51b6921d8662d72e39)

So the Runtime chain read should treat UnboundContentId as a distinct fail-closed case
(unknown object), not as access-denied.

Questions 4 (the right argument), 5 (whether a Runtime purchase must call buyAccess
for the site to see the buyer), and 6 (which line the next work sits on) — follow-up
coming after we check the gateway/buyAccess path in the contracts repo.

Alignment status from our side

We reviewed feat/protected-content-runtime-lifecycle @ 34465959 in a detached
worktree: all five elastos-protected-content-* crates build and pass their suites
(~328 tests, 0 failures). We track the A–F slice cut from
docs/PROTECTED_CONTENT_EXTRACTION.md as the current plan and read the state as:
A / Pre-C0 / Pre-C1 / Pre-C2 done, C–F open, with slice D parked on the ABI decision
above — which question 1 here resolves in favor of (address, bytes16).

Logger track status (separate work, as agreed)

PR #25 (feat/elastos-logger, based on review/collaboration-candidate), pushed:

  • crate ported from this PR's tip 27d85c6f (fb6a3022), plus per-record component
    override log_*!(component: …) / emit_as (02214e38);
  • gateway adopted: global bootstrap with ELASTOS_LOG root (2c8152d9), tracing call
    sites converted (5232911b), daemon eprintln! routed (20ad4fee), library crates
    converted (fe74edeb), tracing subscriber and dependencies dropped (f737ca2b);
  • privacy fix: full VM provider payloads no longer logged (54220f62);
  • components regrouped into dotted buckets gateway.* / cmd.* / vm.* / host.*
    (92714e4e);
  • AI-facing JsonRingSink with per-component JSON ring files, wired via
    ELASTOS_LOG_JSON_DIR (2e843e16).

Local, pending push (3fe4fd4d): per-component logger modules generated by
component! / component_mod! macros — each crate/surface declares its dotted
component once in src/logger.rs and call sites become bare logger::warn!(…);
all 59 per-file LOG_COMPONENT consts migrated. This closes the component vocabulary
(one declaration point per surface), which the per-component ring files rely on.
Verified there: logger 20 tests + doctest, server suite at baseline, clippy/fmt clean.

Still open: the JS mirror question you flagged — no product decision assumed; browser
capsules remain untouched until that is decided.

@irzhywau

Copy link
Copy Markdown
Contributor Author

Update — the remaining 0.7 work is now tracked as Jira Deliverables (label protected-content-07), one per step of the remaining order:

Ordering: 2295 and 2296 run in parallel; 2296 → 2297 → 2298 → 2299 → 2300, and 2299 also waits on 2295 (no cutover before the chain is merged).

@andersalm

Copy link
Copy Markdown
Contributor

@irzhywau I checked the current refs and the remaining 0.7 work.

We are using PR #15 as the working example for mint, buy, and playback. The reviewed implementation is split across PRs #27–#36. The current tip is feat/protected-content-runtime-lifecycle at 8ebc35e3.

That branch contains the complete source flow:

  • protect and mint
  • confirm availability and create the listing
  • buy through Runtime and Wallet approval
  • check the buyer’s rights
  • obtain two of three custody contributions
  • decrypt, read, and close

This code is not selected by the installed Runtime yet. Library is also not connected to it. Library’s current Open action tries a normal viewer or local preview. If neither works, it shows Properties. There is no call from the Library browser code to the protected-content open → read → close operations.

The Library integration should work as follows:

  1. Library sends the selected object identity to Runtime.
  2. Runtime resolves the verified object, listing, and protected-content metadata.
  3. Runtime checks rights and availability.
  4. Runtime coordinates the two-of-three custody release and opens a decrypt session.
  5. The viewer reads from that session and closes it when finished.

The Creator, Owned Assets, and Media Player work in PR #15 may still contain useful screens, interactions, and tests. We should adapt those parts to the current contracts:

  • Library is the user interface for import, protect, mint, list, and open.
  • Marketplace is the user interface for browsing and buying.
  • Runtime owns authorization, provider selection, lifecycle, and audit.
  • Wallet and Inbox handle transaction approval.
  • Carrier transports remote provider calls without exposing network details.
  • The viewer reads through a Runtime-owned decrypt session.

The selected UIUX work from Sash is now published as [feat/0.7-uiux-candidate](https://github.com/Elacity/elastos-runtime/tree/feat/0.7-uiux-candidate) at 8b547590. It is based on upstream/0.7-dev and contains the reviewed Home, app, Wallet, model, assistant, platform, and capsule UI work.

It also contains the exact Browser orphan-process fix from PR #24, commit 706cd351. The implementation is the same as the one in review/collaboration-candidate.

The UIUX candidate does not contain protected-content PRs #29–#36. Those are still a separate review stack. PR #25 is also separate because its logger work may be replaced by tracing.

Before consolidation, we still need to close these points:

  • PR feat(collaboration): signed network protocol, Carrier transport, Profile authority #27 currently targets main. Please retarget it to upstream/0.7-dev. The refs are identical today, but main is the stable release line.
  • Confirm the exact KID-binding write operation and who may authorize it.
  • Confirm the deployed meaning of View and Download.
  • Confirm the exact buyAccess ABI, receipt, and event, and whether Runtime must send that transaction.
  • Prove AuthorityGateway with one bound KID and known allowed and denied wallets. Keep the unbound result separate.
  • Each custody node must use its host Chain provider. Protected-content evidence must use two to five unique RPC sources and require at least two matching finalized results. There must be no fallback to the normal network RPC.
  • Install one signed three-node, two-of-three custody configuration.
  • Prove three content replicas and repair after one replica is lost.
  • Complete an independent cryptographic review before cutover.

The Jira order still looks right: review the PR stack and prove AuthorityGateway in parallel, install the custody and Chain configuration, test the new code while it is still disabled, connect Library playback, perform one cutover, then run mint → buy → play with two principals.

We need to list the specific interactions or tests from Creator, Owned Assets, and Media Player that are still missing from Library, Marketplace, or the viewer. File and test references would help us carry over the useful parts without bringing back the old structure.

@irzhywau

Copy link
Copy Markdown
Contributor Author

Progress update on the 0.7 completion checklist (ELACITY-2295, step 1 — review & merge the stacked chain #27–#36):

Remaining inside step 1: land the queued test-correctness fixes, close #36's CI, then the base reconciliation (the system-map docs merge 5f1fb004 and the #24 hotfix merge 46e51a77 are not yet contained in the extraction stack) and the merge-to-main decision. Steps 2–6 of the checklist (ELACITY-2296→2300) are untouched and next in order.

@irzhywau

irzhywau commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor Author

0.7 status update — the review-and-integration phase is complete:

@irzhywau

Copy link
Copy Markdown
Contributor Author

Deployed-contract facts on Base 8453 — verified (ELACITY-2296)

Follow-up to the revert-trace analysis in the earlier deployed-facts comment: all remaining open questions on the deployed AuthorityGateway (0x09dBe796f40ECEffEAccf243c3d758C4c1d8D87D, Base 8453) are now closed with on-chain evidence. All probes read-only against https://mainnet.base.org. Authoritative contracts repo is v3-drm-protocol; the older drm-contracts deployment (0x8fe6bf98…) is deprecated and was the source of the earlier bindIP caution — its conclusions must not be carried over.

1. KID-binding write (bindIP) — proven, including authorization

CentralStorage.bindIP(bytes16 contentId, address channel, uint256 tokenId) — whitelistOnly: only acknowledged protocol contracts may call it; sole caller is AssetFactory.registerNewAsset inside the mint flow. Each binding emits IPBound(bytes16 indexed, address indexed, uint256 indexed) (topic0 0x69eafb02…); 97 bindings exist on the deployed CentralStorage (0x0C1EeA2A3361B80AC0e42179335dB536A951760b).

$ cast call 0x0C1EeA2A…760b "acknowledged(address)(bool)" 0x4c80A6209F16437f0dc4a98E3D43f08aeBF57765   # AssetFactory
true
$ cast call 0x0C1EeA2A…760b "acknowledged(address)(bool)" 0x1111111111111111111111111111111111111111
false
$ cast call 0x0C1EeA2A…760b "bindIP(bytes16,address,uint256)" 0x…01 0x1111…1111 1 --from 0x1111…1111
Error: execution reverted, data: "0x552b3ecd…1111"        # UnrecognizedContractError(address)

2. buyAccess ABI + paymentProcessor — confirmed; answer to Q5: yes, Runtime must issue it

  • Native: buyAccess(address seller, address ledger, uint256 tokenId, uint256 _quantity, uint256 _pricePerToken) payable → 0xf7580ad9 (requires msg.value == price*qty).
  • ERC-20: same + address _payToken, non-payable, rejects zero sentinel → 0x0ede2294. The buyer must first approve the per-operative paymentProcessor() (0xf1c6bdf8) — not the gateway. Verified live on a real operative: paymentProcessor() = 0x5cC63348e0Fa400B2D8BA49e538636D4c60c7b32.
  • Receipt proof: AssetCreated topic0 0xc0a995e4…dba46 is the EventHub shape (3 indexed topics; emitter must be the EventHub proxy 0x5a694A6d…, not the channel) — 97 live events.

Q5: hasAccessByContentId resolves exclusively through (a) ERC-1155 ACCESS_TOKEN/DISTRIBUTION_RIGHT balances on the operative — moved only by the buyAccess trade path — or (b) an active channel subscription / token-ownership gate. No off-chain purchase can make base.ela.city see a buyer. The landed Runtime code's assumption stands confirmed.

3. View/Download semantics — answer to Q4: chain is boolean-only

Deployed access state is a single boolean per (holder, contentId); checkAccess returns per-token-id booleans for ids 1 (ACCESS_TOKEN — playback/read access, and by extension download) and 3 (DISTRIBUTION_RIGHT — the right to sell/distribute the access token, not a consumption right). No View/Stream/Download/Execute concept exists on-chain; per-right policy stays exclusively in the signed Runtime rights policy.

$ cast call <operative> "checkAccess(address)((bool,uint256)[])" 0x1111…1111
[(false, 1), (false, 3)]

4. Bound-KID proof — allowed + denied + unbound

KID 0x2c27d859924f93f14aa8071f7ba8192e (channel 0x807f9EB5…, bound in tx 0xc2d3e563…):

$ cast call 0x0C1EeA2A…760b "ipReference(bytes16)(address,uint256)" 0x2c27d859924f93f14aa8071f7ba8192e
0x807f9EB55A165c2daa74A5baEFC6f47324A2825d
103914444461609397060396457450024725073477177803557633838354212496568524749843

$ cast call 0x09dBe796…D87D "hasAccessByContentId(address,bytes16)(bool)" 0x34DAF31B99B5A59cEB18E424Dbc112FA6e5f3Dc3 0x2c27d859…192e
true            # minter / holder of the asset's tokens

$ cast call 0x09dBe796…D87D "hasAccessByContentId(address,bytes16)(bool)" 0x1111111111111111111111111111111111111111 0x2c27d859…192e
false           # random wallet

$ cast call 0x09dBe796…D87D "hasAccessByContentId(address,bytes16)(bool)" 0x1111…1111 0xdeadbeefdeadbeefdeadbeefdeadbeef
Error: execution reverted, data: "0xcad88223deadbeef…"    # UnboundContentId(bytes16) — unbound ≠ deny

Observation while probing: channel 0x0EBaC909… returns true for arbitrary wallets — it carries a token-ownership gate (TokenAccessRegistered) accepting USDC with threshold 1 raw unit (1e-6 USDC), so any wallet that ever touched USDC dust passes. That is channel configuration, not a protocol defect, but the threshold choice may be worth revisiting. Deny proofs must use a gated channel (5 of the 6 live channels deny correctly).

5. Creator royalty unit — 0x3b6 = 950 ROYALTY_SHARE units = 95%, confirmed

1000 ERC-1155 ROYALTY_SHARE (id 2) units per asset: creator 950 (95%) + protocol owner's protocolShares() = 50 (5%). Observed live on a deployed operative:

$ cast call 0x0C1EeA2A…760b "protocolShares()(uint256,address)"
50
0xdB0E70C1451aFA45D34FC16f55B7dbd9b4181010

$ cast call <operative> "balanceOf(address,uint256)(uint256)" <creator> 2   → 950
$ cast call <operative> "balanceOf(address,uint256)(uint256)" 0xdB0E70C1…1010 2   → 50

Landed alongside this verification

  • capsules/chain-provider: the royalty constant renamed …ROYALTY_TENTHS_PERCENT → …ROYALTY_SHARE_UNITS with a corrected comment (value 0x3b6 unchanged and now deployment-verified).
  • docs/PROTECTED_CONTENT_EXTRACTION.md, docs/PROTECTED_CONTENT.md, TASKS.md: the "still open" deployed-facts lists rewritten as confirmed facts with this evidence.
  • docs/CHAIN_PROVIDER.md: the has_access_by_content_id example now documents the canonical selector 0x54d42821 (previously a placeholder). Follow-up flagged: that selector is operator-config and only shape-validated, unlike its five hardcoded siblings in abi.rs — worth considering a pinned default.

@andersalm

Copy link
Copy Markdown
Contributor

@irzhywau not merged; kept as research.
Please confirm keep-as-reference or close.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants