Skip to content

Feature: MPEG-DASH/CENC compliance + DKMS hardening + creator flow#8

Closed
irzhywau wants to merge 218 commits into
mainfrom
feat/0.5.0-with-mpeg-dash-compliance
Closed

Feature: MPEG-DASH/CENC compliance + DKMS hardening + creator flow#8
irzhywau wants to merge 218 commits into
mainfrom
feat/0.5.0-with-mpeg-dash-compliance

Conversation

@irzhywau

@irzhywau irzhywau commented Jul 2, 2026

Copy link
Copy Markdown

Summary

This branch brings the runtime to 0.5.0, headlined by standards-compliant MPEG-DASH / CENC media DRM (ELACITY-2283) and a hardening pass on the DKMS quorum key-authority plane (ELACITY-2282). Along the way it lands the creator publishing flow, per-capsule WASM resource limits, forensic A/V watermarking, a batch of serve/library performance work, and a security-review hardening pass. main has been merged in (--no-ff) and all conflicts resolved.

Scope note: this is a release-train branch (~218 commits, 339 files). Much of the line count is vendored/generated (Cargo.lock, compiled WASM, docs, audit packets); the reviewable surface is the Rust capsules/crates and the gateway.

What's in it

🎬 MPEG-DASH / CENC compliance (ELACITY-2283)

  • Producer emits standards-compliant CENC-signaled init segments (encv/enca sample entries carrying sinf/schm/tenc, pssh injection) so a stock CENC player / FFmpeg keys decryption off tenc.
  • The server-side decrypt rail strips the signaling back to a plaintext-looking init for its own player — one asset, one wire form for both compliant clients and the runtime.
  • Full-sample AES-CTR encryption with per-sample IVs; the enca/encv choice is driven by the track's authoritative hdlr handler type.
  • Touches ddrm-media, encrypt-provider, decrypt-provider, ddrm-envelope, elacity-player, ddrm-viewer.

🔑 DKMS quorum reliability + hardening (ELACITY-2282)

  • Thread-per-connection serving so a slow/idle client can't head-of-line-block the quorum node.
  • Host-independent tests; re-seal AAD bound into the recover possession-proof; single-use grant nonce refreshed on quorum retries.
  • Shared live revocation set (a revoke binds every open connection immediately), bounded connection concurrency, and pooled-connection retry — see the code-review pass below.

🎨 Creator publishing flow

  • Creator capsule UI, media-provider, gateway creator route, and marketplace/content-market contracts wiring the mint → publish path.

🧱 Per-capsule WASM resource limits

  • Enforces each capsule's declared memory budget (clamped to a host ceiling), plus table/instance limits, fuel metering, and epoch-based termination so a runaway capsule is operator-terminable. One shared Engine across capsules.

🕵️ Forensic A/V variant watermarking

  • Mint-side asset-secret KDF + variant manifest; serve-time per-buyer variant selection welded into the CENC AAD, exercised end-to-end on real fMP4.

⚡ Performance

  • Single-copy in-place CENC decrypt; boot-stable gateway DID memoization; dropped redundant per-segment capsule re-resolve in viewer authz; bounded (LRU) library cover cache; cached file-listing facts on the directory hot path.

🛡️ Security-review hardening pass

A focused pass on the DKMS/encryption workflows, each with regression tests:

  • Revocation is now shared live state (immediate across connections), not a per-connection snapshot.
  • Connection-concurrency cap + slow-loris deadline on the network quorum node.
  • Pooled DKMS connections retry once on a transport fault (idle-timeout) but fail closed on a genuine node rejection.
  • enca/encv classification driven by the authoritative hdlr type, with an expanded codec fallback.
  • read_dash_init propagates strip errors instead of masking them as opaque decrypt failures.
  • decode_kid16 rejects malformed (multibyte) KIDs instead of panicking the capsule.
  • Browser decrypt-stream sockets use per-euid 0700 dirs and refuse foreign-owned/writable dirs (closes a /tmp squatting vector).

📚 Audit & conformance

  • External-auditor packets for the dKMS decrypt plane, verified-safe scope-out registries, and build-visible conformance/audit-verdict ratchets.

Merge with main

main was merged in (--no-ff); 25 files conflicted and were resolved considering both branches. Highlights:

  • wasm.rs — unified ours' memory-clamp + epoch-termination with main's fuel metering, hostcall wiring, and wall-clock timeout into one execute_wasm.
  • gateway_browser_stream.rs — folded ours' socket hardening into main's browser_stream_socket_path(directory) refactor (covers both stream + adapter-IPC sockets).
  • provider_resource.rs — extended main's peer capability allowlist with ours' gossip ops.
  • gateway.rs — env trusted-signer override (main) before the DID cache (ours).
  • runtime_control.rs — main's portable pid_is_alive(); chat/session.rs — ours' fail-closed presence signing.
  • Config — unioned main's alignment checks + ours' audit recipe; components.json took wallet-provider.

Testing

  • Full elastos workspace builds clean.
  • elastos-server 928 tests pass; elastos-compute 16, chat 31, ipfs-provider 23, dkms-authority 27, encrypt-provider 31 (escrow) all pass.
  • WCI-alignment script and components.json validate.
  • End-to-end mint → playback pass confirmed.

Risk / compatibility

  • DKMS wire protocol unchanged for the deployed quorum nodes (recover-proof kept at v1 to match deployed nodes).
  • Published media is a single compliant CENC asset; legacy/unsigned inits remain a no-op through the strip path.
  • One pre-existing flaky test (recovery_kit_password_package_imports_with_password_only) fails only under full-suite parallelism (passes in isolation); unrelated to this branch.

andersalm and others added 30 commits June 7, 2026 01:44
Add the Runtime provider registry and gateway proxy support needed for object/content provider invocation, stream sessions, progress/cancel metadata, and provider-backed viewer handoff.

This is the transport/control-plane slice. Concrete Library, content, Spaces, and package surfaces are committed separately so reviewers can separate runtime plumbing from product behavior.
Replace the static Library capsule with a PC2-familiar file-manager surface backed by the Runtime object-provider API. This includes source-split Library UI code, icons, navigation, selection, upload/download, rename/create/delete/trash, publish/share/status/properties hooks, and object CID metadata.

Add the standalone object-provider capsule and boundary tests while keeping publish/share availability authority separated through Runtime/content-provider coordination.
…0.4.0 plan

Durable agent-facing alignment docs for the PC2 -> Runtime convergence:
the capability/provider first-principles playbook, the finished-product
PRD, and the current-week coordination plan with Anders' 0.4.0 line.

Co-authored-by: Cursor <cursoragent@cursor.com>
… backend

Day 1 of the dDRM convergence. Brings PC2's CENC/AES-128-CTR fMP4 decrypt
engine (Elacity/pc2.net @ a0a910158) in-tree as a provider-internal module,
behind the existing fail-closed contract (not yet wired into open_session/
render). CEK stays inside the boundary and is zeroized after use.

Also fixes a pre-existing 0.4.0 drift: DecryptSessionRequestV1.release_receipt
is now a typed ReleaseReceiptV1, so the validator/test fixture were updated
to match. Adds aes/ctr/base64 deps. 14 tests pass incl. an end-to-end golden
decrypt with a CEK-containment assertion.

Co-authored-by: Cursor <cursoragent@cursor.com>
elastos-crosvm's guest-network module uses Linux-only TAP/ioctl + libc
(SOCK_CLOEXEC, sockaddr_in.sin_len), which broke `cargo build -p
elastos-server` on macOS and blocked local gateway/Home development.

Gate the Linux network module to target_os="linux" and provide a
non-Linux network_stub.rs that mirrors NetworkConfig's public surface and
fails closed (microVM launch already fails closed via the /dev/kvm check
in vm.rs::start()). Scope the mkfs.ext4 rootfs test to Linux. Linux paths
are byte-for-byte unchanged (gate, not rewrite).

This is the minimal Darwin slice extracted from the Mac VZ branch
(sash/local-test-v030); crosvm is otherwise identical between the two.
With this, elastos-server builds on macOS and `elastos gateway` serves
the 0.4.0 Home UI at http://localhost:8090/apps/home/.

18 crosvm tests pass on macOS incl. fail-closed stub tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
…o decrypt-provider

Day 3 surfaced that DecryptSessionRequestV1 carries authority+intent only
(release_receipt attests release; no CEK, no ciphertext). End-to-end decrypt is
blocked on an architecture decision: how the CEK (VM-sealed) and ciphertext
reach the decrypt microVM. Documents Option A (material pushed in request) vs
Option B (provider-chain capability calls), recommends A-for-decrypt composed
with B-upstream, and lists questions for Anders. Contract-first: not inventing
the security boundary unilaterally.

Co-authored-by: Cursor <cursoragent@cursor.com>
…Abstraction)

Introduces decrypt_session_segment (validated material -> cenc engine -> plaintext,
CEK owned+zeroized by the engine) and scoped_session_response (containment-safe
caller response). Not yet wired into open_session/render — the CEK+ciphertext
transport rail is the open decision in DDRM_DECRYPT_RAIL.md (Hybrid chosen).

Tier/rail-agnostic, pure Rust: ready to connect once the rail lands. Adds provider-
boundary tests: golden decrypt through the seam, fail-closed on bad CEK, and an
assertion that neither the CEK nor decrypted plaintext can cross to the caller.

Co-authored-by: Cursor <cursoragent@cursor.com>
…hardening

decrypt-provider's cenc engine is pure compute (ideal wasm workload, runs on
macOS today, composes with the Hybrid rail). microVM stays the later max-isolation
upgrade; same Rust code targets both. Security contract is tier-independent.

Co-authored-by: Cursor <cursoragent@cursor.com>
cargo build --target wasm32-wasip1 succeeds with zero code changes; full decrypt
path (cenc engine + elastos-common protected_content) is wasm-clean and all 17
host tests stay green. Hard evidence for the wasm-now tier recommendation.

Co-authored-by: Cursor <cursoragent@cursor.com>
… execution

decrypt-provider.wasm runs correctly under wasmtime 45.0.1: status advertises
blocked raw authority, malformed ops + raw_plaintext output rejected, valid
sessions fail closed (not_configured) until the rail lands. Adds reusable
scripts/wasm-smoke.sh (CI-suitable) and records the evidence in DDRM_DECRYPT_RAIL.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fixes pre-existing 0.4.0 drift (KeyReleaseRequestV1 gained rights_receipt) and
closes a fail-closed gap: key-provider now verifies the upstream rights decision
(allowed + principal/session/object/right must match the request) before it would
release a key. Adds 4 binding tests (9 total), confirms host + wasm32-wasip1
builds, and adds a WASI-sandbox smoke harness. CEK stays wrapped-only.

Records rights->key->decrypt chain parity in DDRM_DECRYPT_RAIL.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ypt chain parity

rights-provider already had the contract + 9 tests (incl. wire-level rejection of
hidden chain/wallet/key fields). Brings it to the proven bar: confirms host +
wasm32-wasip1 builds and adds scripts/wasm-smoke.sh (4/4 pass under wasmtime).

All three dDRM chain links now compile to wasm, run under WASI, and are fail-closed
end-to-end; only the CEK/ciphertext rail (Anders' call) remains. Updates the
chain-parity table in DDRM_DECRYPT_RAIL.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Brings the drm/open orchestrator to the proven bar: confirms host + wasm32-wasip1
builds, adds scripts/wasm-smoke.sh (4/4 pass), and adds chain_seam_tests proving
the receipts compose across the chain — RightsDecisionReceiptV1 deserializes into
the key request, ReleaseReceiptV1 into the decrypt request (12 host tests).

All four dDRM links (drm/open -> rights -> key -> decrypt) now compile to wasm,
run under WASI, are fail-closed, and have verified inter-provider handoffs. Updates
chain-parity table in DDRM_DECRYPT_RAIL.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds scripts/ddrm-chain-smoke.sh that runs all four provider WASI smokes
(drm -> rights -> key -> decrypt) and emits one consolidated PASS/FAIL report —
a single-command demo that the whole chain is fail-closed + wasm/WASI-proven.
Adds docs/convergence/DDRM_STATUS.md: parity table, proven security properties,
how-to-run, the one open rail decision, and commit inventory for review.

Co-authored-by: Cursor <cursoragent@cursor.com>
Captures PC2 ddrm-decrypt's CEK-sealing envelope (P-256 ECDH unwrap, AES-256-CBC,
v2/v3 layouts) as an executable, tested characterization spec in decrypt-provider
(envelope.rs). All recovered material is held in Zeroizing; golden round-trips,
truncation/wrong-key fail-closed cases, and a CEK-containment assertion pass on
host and the crate stays wasm32-wasip1-clean. The module is intentionally NOT yet
wired into live dispatch — it pins the rail's byte contract pending Anders' Option
A + tier confirmation (contract-first).

Adds docs/convergence/PC2_PLAYER_ALIGNMENT.md: validates the two viewer capsules
(media: elacity-player; non-media: ddrm-viewer/wasm-renderer) and adopts Irzhy's
two invariants as binding rules — CEK/KID generated in-wasm on encrypt; CEK
recovery + content decryption colocated in one wasm boundary with zeroization on
decrypt (CEK never crosses FFI). Adds docs/convergence/PUSH_PLAN.md for clean
reinstatement (branch->PR order + per-PR test plans).

Co-authored-by: Cursor <cursoragent@cursor.com>
…sules

Characterization tests for the chain's downstream boundary: the scoped response
carries metadata only (allow-listed keys), a forbidden-key check rejects any
cek/iv/key/plaintext/... field for both the media (elacity-player) and non-media
(ddrm-viewer) players, and a media-segment decrypt run asserts neither CEK nor
decrypted bytes reach the player-facing output. Pins PC2 ddrm-decrypt's
"public functions never return a CEK; only a handle" rule (Irzhy invariant #2 at
the consumer edge). 25 host tests green; crate stays wasm32-wasip1-clean.
Documents the consumer contract in PC2_PLAYER_ALIGNMENT.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds docs/convergence/DDRM_SECURITY_MODEL.md — a self-contained trust-model
walkthrough for the team (Irzhy/Anders): the two invariants, actor/boundary map,
encrypt + decrypt mermaid flows, a threat model per boundary, and an
invariant->enforcement table where every claimed property cites a passing test
(envelope, cenc, consumer-contract, key-provider rights-binding, decrypt-provider
opaque-id/output-kind/fail-closed).

Records the inter-stage CEK transport decision (Irzhy's ECDH+DSA point): keep
key/decrypt as separate boxes but seal the CEK to decrypt-provider's per-session
key and unwrap+decrypt+zeroize in one boundary; strongest variant has the dKMS
seal directly. Flags the PC2 (classical P-256) -> runtime PQ-hybrid
(x25519+ml-kem-768, ml-dsa-65) reconciliation and sharpened questions for Anders.

Co-authored-by: Cursor <cursoragent@cursor.com>
Consolidate the dDRM review package to current truth and de-risk the one
rail-independent PQ piece ahead of the rail landing.

- DDRM_STATUS.md: full +14 commit inventory, parity table (decrypt-provider
  now 25 host tests: cenc + envelope + consumer contract), both chain ends
  pinned (upstream envelope spec, downstream consumer contract), and the
  sharpened 3 open rail sub-questions for Anders/Irzhy.
- Prove PQ-hybrid is viable inside the wasm boundary: ml-kem 0.2.3 (ML-KEM-768)
  and ml-dsa 0.0.4 (ML-DSA-65) both compile to wasm32-wasip1 on pinned 1.89.0.
  GO, with a pin-exact caveat (ml-dsa is 0.0.x). Recorded in both docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
… gap

Closes the encrypt side of Irzhy's security invariants. Adds a fail-closed
encrypt-provider capsule (the chain's producer end) with the boundary contract
pinned by characterization tests, plus a precise gap analysis vs the PC2
reference.

- encrypt-provider skeleton (microvm tier), self-contained (no elastos-common
  yet) to stay rebase-safe while 0.4.0 churns (Anders: ~20% on GitHub, commits
  being redone). Builds to wasm32-wasip1; 6 host tests pass.
- Invariant #1 pinned: caller cannot supply a CEK on the wire (deny_unknown_fields
  + no key field); sealed output carries only ciphertext + wrapped_cek (never raw
  CEK); CEK zeroized; status blocks raw_cek + plaintext authority; fail-closed.
- The one gap (CEK+KID generated in-boundary, not in the Node host as PC2 does
  today) is captured as an #[ignore]d test + DDRM_ENCRYPT_INVARIANT.md, with a
  scoped landing and reconcile-to-elastos-common plan.
- Updated DDRM_SECURITY_MODEL.md (invariant #1 -> test rows, second open item)
  and DDRM_STATUS.md (encrypt-provider row, both-ends note, base-volatility note).

Co-authored-by: Cursor <cursoragent@cursor.com>
… drift)

Anders force-pushed origin/0.4.0 (redone commits, more to come). Reconcile
without rebasing yet (base still in flux):

- Verified elastos-common/protected_content.rs is byte-identical between this
  branch and the redone origin/0.4.0 — the contract converged, zero type drift.
  The redone base independently added the exact types our providers consume.
- scripts/ddrm-drift-check.sh: guards every schema const, struct, and
  chain-binding field the dDRM chain depends on; fails loudly on a future 0.4.0
  type move. Currently PASS. Lists encrypt-provider's reconcile-to-common items.
- Recorded the rebase recipe + safety backup (backup/decrypt-provider-cenc-preD17)
  in PUSH_PLAN.md, and the reconciliation + 61 green provider tests in
  DDRM_STATUS.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
…context

A new engineer/agent can start cold from HANDOVER.md with no blindspots: the
30-second picture, mission + priority stack, the capability/dDRM mental model,
what's built (61 green provider tests), the ordered doc map, key people and their
concerns, the hard constraints (GitHub suspended, 0.4.0 in flux, zero type drift),
branch topology, open blockers, verify commands, the working method, the 10/10
daily-prompt methodology, a Day 1-17 log, and the next unblocked options.

Also corrects the DDRM_STATUS commit-count note (17 ours; rev-list shows 19 vs the
force-pushed base due to 2 orphaned old-upstream commits the rebase drops).

Co-authored-by: Cursor <cursoragent@cursor.com>
…p flag

Join the two tested islands of the decrypt boundary — the upstream
CEK-sealing envelope unwrap (envelope::{parse,ecdh_unwrap,extract_cek}) and
the decrypt-step core (decrypt_session_segment) — into decrypt_sealed_segment,
the single in-boundary operation the Hybrid decrypt rail will invoke once the
CEK-transport rail is confirmed (DDRM_DECRYPT_RAIL.md).

Mirrors PC2 ddrm-decrypt::session::unwrap_envelope -> cenc decrypt: the CEK
materializes only after a correct ECDH unwrap against the session key, is held
in Zeroizing for its whole lifetime, is consumed + zeroized by the cenc engine,
and never reaches the scoped response. Pinned by two characterization tests
(end-to-end recover + containment; fail-closed on wrong session key) and proven
to build to wasm32-wasip1.

Gated behind the rail-prep Cargo feature (OFF by default, Parallel Change): the
live dispatch and the 25-test default suite are unchanged, so the live wiring is
a one-step swap into open_session/render once the rail + session-key
provisioning land. No behaviour change on the default path.

Co-authored-by: Cursor <cursoragent@cursor.com>
…y CEK+KID

Vendor PC2 cenc-encrypt's AES-128-CTR cipher core (Elacity/pc2.net @ a0a910158)
into encrypt-provider/src/cenc.rs — the symmetric counterpart of the AES-CTR core
decrypt-provider vendored from cenc-decrypt — and add the in-boundary keygen PC2
lacks:

- mint_cek_and_kid() mints a 16-byte CEK + 16-byte KID with a CSPRNG (getrandom ->
  WASI random_get on wasm32-wasip1). Generation is unconditional, takes no caller
  input, and never leaves the sandbox. This is the precise move that closes the
  gap: PC2 minted the CEK in the Node host (dashPackager.ts::generateCEK).
- seal_segment_in_boundary() mints the key, CENC-encrypts the asset, scrubs the
  CEK on drop (Zeroizing<[u8;16]>), and returns SealedSegment — which has no CEK
  field, so invariant #1's output half is enforced by construction.

The once-#[ignore]d cek_and_kid_generated_inside_boundary now passes;
encrypt-provider goes 6(+1 ignored) -> 13 green, 0 ignored. Full chain: 68 green,
drift PASS, builds clean to wasm32-wasip1.

Scope held deliberately tight (one boundary at a time): cipher core only. PC2's
fMP4 box surgery (mp4box) and Elacity PSSH injection were NOT vendored — PSSH
embeds chain/Lit authority we must translate, not copy (ACL). Those + the actual
CEK sealing are a later boundary sharing the decrypt rail dependency, so `seal`
dispatch stays fail-closed, mirroring decrypt-provider's fail-closed open_session.

No behaviour change on the live (fail-closed) dispatch path.

Co-authored-by: Cursor <cursoragent@cursor.com>
…gen deps

Lockfile byproduct of the Day-19 invariant-#1 closure (aes, ctr, getrandom +
transitives). Keeps the committed tree consistent with Cargo.toml.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ope island)

Add src/pq_envelope.rs — the post-quantum analogue of the classical envelope.rs,
behind the pq-envelope feature (default OFF, Parallel Change), proving the runtime
profile (elastos-pq-hybrid-threshold-v0) composes and recovers a CEK:

- Hybrid KEM: x25519 DH || ML-KEM-768 (FIPS 203). The AES-256-GCM wrap key is
  derived (SHA-256 KDF, labelled + length-prefixed) from BOTH shared secrets, so
  confidentiality holds if EITHER primitive stays unbroken.
- AEAD wrap is authenticated: wrong KEM secret / tampered blob fail closed
  (UnsealFailed), no plaintext on error.
- Signature sits behind a CekSealVerifier abstraction so ml-dsa-65 (or a hybrid
  ECDSA+ml-dsa during PC2 migration) plugs in without touching the unwrap path.
- CEK returned in Zeroizing; never present in the sealed bytes. Unwrap needs no
  RNG and no outbound authority — a pure in-VM transform, like the classical path.

Pinned by 4 characterization tests (round-trip recover; wrong-session fail-closed;
tampered-signature fail-closed; sealed blob has no raw CEK) and proven to build to
wasm32-wasip1 under 1.89.0. Resolved versions: ml-kem 0.2.3, x25519-dalek 2,
aes-gcm 0.10, sha2 0.10. cargo test --features pq-envelope: 29 green (25 default +
4 PQ). Default suite (25) and rail-prep (27) unchanged; the PQ crates are pulled
only when the feature is enabled.

This makes the PQ rail a known-good drop-in for the classical envelope the moment
Anders confirms the transport + signature scheme. No behaviour change on the
default path.

Co-authored-by: Cursor <cursoragent@cursor.com>
Bind the three in-boundary engines into one cross-engine proof:
decrypt_pq_sealed_segment (feature pq-rail-prep, default OFF, enables pq-envelope)
chains hybrid_unwrap -> decrypt_session_segment — the PQ analogue of the Day-18
classical decrypt_sealed_segment. The PQ unwrap slots exactly where the classical
ecdh_unwrap does (mirroring PC2 ddrm-decrypt::session::unwrap_envelope -> cenc),
with the CEK in Zeroizing throughout, consumed + zeroized by the cenc engine, and
never reaching the scoped response.

Pinned by a cross-engine golden: PQ-seal a CEK and CENC-encrypt a segment with
that SAME CEK, then prove the composed path recovers the plaintext while the CEK
stays off the boundary (pq_sealed_segment_decrypts_end_to_end_and_keeps_cek_off_the_boundary),
plus a wrong-session fail-closed case. Builds clean to wasm32-wasip1.

cargo test --features pq-rail-prep: 31 green (29 + 2 cross-engine). Default (25),
rail-prep (27) and pq-envelope (29) suites unchanged; no new dependencies. Full
chain 68 green, drift PASS.

This proves the entire PQ dDRM data path (sealed CEK in -> rendered bytes out, key
contained) before the rail lands — the remaining work is the transport shim, not
the crypto or the engines. Also refreshes DDRM_SECURITY_MODEL.md to current truth
(Day-19 keygen closure + the PQ rows). No behaviour change on the default path.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add substrate-independent golden-vector fixtures (characterization/golden-file
pattern) under tests/vectors/ that lock the dDRM decrypt data paths across
refactor, rebase onto 0.4.0, and a future microVM port:

- classical_cenc.json: P-256 ECDH envelope -> CENC AES-128-CTR, byte-compatible
  with PC2 ddrm-decrypt, so it doubles as a cross-impl conformance fixture.
- pq_hybrid_cenc.json: x25519+ML-KEM-768 hybrid seal -> CENC; replay reconstructs
  the typed PqSealedEnvelope from flat bytes (ML-KEM dk/ct (de)serialization),
  exercising the wire-decode the live rail will need.

Replay (recover CEK -> decrypt -> assert plaintext) and corrupted-input
fail-closed tests run with no in-test sealing and no RNG (every consumer step is
deterministic). Schema in src/vector_format.rs. Feature split: `vectors`
(default OFF, enables pq-rail-prep) replays the committed fixtures; `gen-vectors`
regenerates them. Base suites unchanged (25/27/29/31); `--features vectors` = 35
green. Builds clean to wasm32-wasip1.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add scripts/pc2-conformance.sh: decrypts the committed classical golden vector
(classical_cenc.json) using PC2 ddrm-decrypt's REAL code and asserts byte-for-byte
parity end to end:
  - CEK transport: PC2 envelope::parse -> ecdh_unwrap -> extract_keys_blob recovers
    the same 16-byte CEK from our sealed envelope.
  - media: PC2 mp4box::parse_segment -> cenc::decrypt_samples decrypts our segment
    to the same plaintext.

The harness compiles a small driver (scripts/pc2-conformance/driver.rs) against the
PC2 repo on demand via a temp crate, so no absolute path or PC2 coupling enters the
ElastOS build graph. Resolves PC2 via PC2_REPO (default local checkout); skips clean
(exit 0) when PC2 is absent so the default chain is never broken; fails (exit 1)
only on genuine divergence. Result against the live PC2 checkout: PASS.

No capsule code changed; base suites (25/27/29/31), vectors (35), chain (68), and
drift remain unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Add scripts/ddrm-verify.sh: one-button pre-rebase/PR gate aggregating the
  contract drift check + PC2 cross-impl conformance (skips clean without PC2,
  fails non-zero on any genuine gate failure).
- Widen the conformance driver to take multiple vectors and add a NEGATIVE case:
  for every vector it tampers the envelope and asserts PC2 fails closed too, so
  both implementations reject the same corruption identically.
- Add a v2 fixed-IV classical golden vector (classical_cenc_v2.json) alongside the
  v3 random-IV one; the harness cross-checks both PC2-supported envelope versions.
  Add an in-repo v2 replay test (refactored the emit/replay into shared helpers).

Base suites unchanged (25/27/29/31); vectors suite 35 -> 36 (+v2 replay); chain 68;
drift PASS. Live PC2 result: PASS for v3 + v2, positive and negative.

Co-authored-by: Cursor <cursoragent@cursor.com>
claude and others added 28 commits June 20, 2026 08:04
…n M1 verdict

Commit 3 of the audit follow-up. Both items were traced to ground by read-only
reachability passes before any code: A7 is a real availability bug (fixed); M1 is
not exploitable (cleared + pinned).

A7 (HIGH, reachable by every legitimate user on flaky Carrier transport): the quorum
open retry loop re-sent the SAME wallet-signed grant on attempts 2-3. Each grant
carries a single-use per-request nonce, so the dKMS node's process-global replay guard
(correctly) rejected the retry as "access grant rejected: replayed" — turning transient
sub-quorum transport loss on attempt 1 into a hard open failure. Fix: on retries,
assemble a FRESH grant from the already-cached wallet-signed delegation (new
request_nonce + timestamp, signed by the cached session key) — no MetaMask popup, and
the SAME delegation signature so the forensic watermark anchor is unchanged. Attempt 1
still uses the up-front grant. Fail-soft: if no live session is cached or regeneration
fails, fall back to the original (never worse than before). Regeneration runs inside
the existing spawn_blocking (it shells the grant sidecar), so the async executor is
never blocked. The decision is split into an I/O-free `pick_grant_for_attempt` so the
per-attempt logic is unit-tested without the sidecar (4 new tests). This never weakens
the gate: a retry sends a different LEGITIMATE nonce, it does not bypass the replay
guard, and the on-chain hasAccessByContentId check still runs on every recover.

M1 (cleared): ECDSA high-s malleability at the recover sites looked like it could
bypass a replay/dedup gate. A full sweep confirms NO replay/dedup key in the tree is
signature-keyed — the grant replay guard keys on explicit server nonces, recovered
identities derive from the public key (malleability-invariant), and signature_hash
fields are audit/equality values only. Safe-by-construction. Pinned in the
PRINCIPLES_CONFORMANCE "do not re-churn" register, with a note that naive low-s
normalization (without flipping recovery_id parity) would break owner recovery — so it
is deliberately not applied to the crown-jewel path.

Gate: cargo test -p elastos-server green — 774 + 95 passed, 0 failed (incl. the 4 new
attempt tests). No CEK-path, served-byte, or wire-format change; client-side only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…resolved

Chosen next target was "harden the audit/custody trail (PRE-3)". Traced to ground
first (the H1/M1 discipline) — and, like H1 and M1, the finding is stale: the work is
already done. No code change; this commit records the verdict so the audit docs match
reality and a future pass does not re-implement an already-tamper-evident log.

Ground truth (substantiated by passing tests, not assertion):
- AuditLog::emit returns Result and writes a ChainedRecord — monotonic seq + prev_hash
  + record_hash over domain‖seq‖prev_hash‖event_json, ed25519-signed with a
  crypto-agility tag — flushed and sync_all'd to disk BEFORE the chain head advances
  (failed write retries the same seq: no gap, no silent loss); verify_chain walks it
  for tamper-evidence (primitives/audit.rs:461-535).
- The dDRM open emits a FAIL-CLOSED content_open (viewer_open.rs:481): the open is
  refused (503) if the custody record can't durably commit, on the common path before
  object/media/quorum dispatch. Session-bound segment/byte serving is covered
  transitively (sessions are minted only by that handler).
- The "no open event, only a comment at viewer_open.rs:1021" claim was a misread:
  :1021 is media-layout detection, not an open path.
- Non-gaps confirmed: /api/provider/object/download/raw serves the principal's OWN
  library files (no decrypt/CEK/quorum; provider-effect audit is correct, and there are
  no chain rights to revoke on one's own files); the demo routes serve an operator-fixed
  sample, not attacker-selectable content.
- Genuinely open (separate roadmap, not a present defect): external anchoring of the
  chain head against a live-compromised runtime that still holds the signing key.

Test evidence: cargo test -p elastos-runtime audit — 17 passed, 0 failed, incl.
dropping_a_record_breaks_the_chain, emit_chains_seq_and_a_clean_log_verifies,
content_open_grant_digest_is_optional_and_chain_verifies,
the_chain_resumes_across_reopen_and_stays_append_only.

Also annotates PRE_AUDIT.md row 3 as RESOLVED with the implementing references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…partial

Re-verified all eight PRE_AUDIT findings against the current tree (deep + passing
tests for #1/#3/#6; code + mechanism for #2/#4/#5/#7/#8). Adds a dated verification
banner with file:line evidence and flips the #1/#2 status cells.

- #1 (was HIGH/CRITICAL) RESOLVED: CEK reconstruction is integrity-checked end-to-end
  and production-wired — producer publishes cek_commitment at mint, the open path
  reconstructs via reconstruct_quorum_cek_checked (3+ shares cross-check / commitment
  binding / degraded 2-share-without-commitment refused). Byzantine-share tests pass
  (decrypt-provider 146/0).
- #2 PARTIAL by design: log-redaction is done (log_fp fingerprints the wallet/content
  triple); blinded identifiers / oblivious lookup / frame padding / node-operator
  visibility remain documented roadmap, not code defects. Kept for the firm.
- #3 RESOLVED (audit tamper-evidence; recorded earlier this branch).
- #4 RESOLVED: central required_action_for map, fail-closed on unmapped ops, enforced
  at the bridge on the required (not token's) action.
- #5 RESOLVED: node-set-id pin mandatory in release (compile_error fence + authorize
  refuses a caller-declared node-set).
- #6 RESOLVED: vsock-proxy MAX_LINE_BYTES cap fails closed; binds the guest vsock
  wildcard CID, not TCP 0.0.0.0.
- #7 RESOLVED: GF(256) multiply rewritten branchless (mask selects, fixed 8 iters).
- #8 RESOLVED: effective_now clamps the caller clock in release (window can only shrink).

No code change. Purpose: hand the external firm a trustworthy "scope these OUT" list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
The List operation calls library_object() per directory entry, which for a file read
the whole file and hashed it TWICE — once for the `revision` token (SHA-256) and again
in raw_sha256_cid() for the `content_cid` (also SHA-256 over the same bytes, wrapped in
a CIDv1 multihash). On a folder of N files that is N reads + 2N full SHA-256 passes on
every listing — and listings are the file explorer's hottest path.

Hash once and derive both: split raw_sha256_cid into sha256_digest_to_raw_cid(digest)
and reuse the single digest for the revision hex + the CID. Byte-identical outputs
(revision is a compare-and-swap token threaded through ~14 mutation ops via if_revision,
so exactness matters) — pinned by a new test asserting the single-hash path equals the
prior two-pass form across representative inputs. ~Halves per-file hashing on every
directory listing; no behavior, API, or revision-value change.

Test: cargo test -p elastos-server single_hash_listing — ok.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
Re-listing a folder (navigation, refresh, polling — the file explorer's most frequent
op) re-read and re-hashed every unchanged file each time, just to recompute display
facts (revision token, content CID, size, thumbnail pointer). Add a bounded, process-
global cache of those file-content facts keyed on (path, len, mtime), consulted ONLY on
the LIST display path: a re-list of an unchanged directory now skips the full read +
SHA-256 entirely.

Safety: the cache is self-invalidating — any write bumps the file's mtime, so the key
changes and a stale entry is never served. Critically, it is wired ONLY through
library_object_listing_cached (the List loop); the CAS gate (check_revision) and every
mutation keep using library_object (fresh), so a stale entry can at worst cause a benign
false write-conflict, never a wrong overwrite. Bounded to 8192 entries (FIFO eviction);
keys are principal-scoped (no cross-tenant leakage).

Refactor: the file-facts computation is extracted into file_listing_facts (always
fresh), wrapped by file_listing_facts_maybe_cached; library_object becomes a thin
fresh wrapper over library_object_inner(.., use_list_cache=false).

Tests (cargo test -p elastos-server, 777 pass): cas_path_does_not_consult_the_listing_
cache (CAS path never reads the cache), listing_cache_key_changes_with_len_and_mtime
(self-invalidation), single_hash_listing_matches_two_pass_revision_and_cid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…ct URLs

The file-explorer cover cache (loadCover) was an unbounded Map of endpoint ->
Promise<objectURL>. Over a long browse session it grew without limit, and because each
entry holds an object URL (createObjectURL keeps the backing blob alive until revoked),
the blob memory leaked too — capping alone wouldn't have freed it.

Make it a bounded LRU (256 entries): touch on access (re-insert at the back) so a cover
still on screen is never the one evicted, and on eviction revoke the object URL so the
blob memory is actually released. Behavior-preserving — covers still load and cache, and
re-renders stay cheap on a hit; an already-rendered (then-evicted) URL stays visible
since revoke only blocks new resolutions.

Gate: node --check api.js passes (no behavioral JS harness exists for this capsule).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
The CI `check` job runs `cargo fmt --all --check` and `cargo clippy --workspace
-D warnings` (RUSTFLAGS=-D warnings) from elastos/. Two gate failures from this
branch's earlier commits, fixed here:

- rustfmt: reflow a few lines in access_grant.rs / viewer_open.rs / library.rs to
  match rustfmt (whitespace only, no logic change).
- clippy dead_code: the A/B list refactor left `raw_sha256_cid` (whole-buffer hash +
  CID) with no production caller — the list path now derives the CID from the already-
  computed digest via `sha256_digest_to_raw_cid`. It remains useful only as the
  independent reference the byte-identity test checks against, so gate it `#[cfg(test)]`.

Verified locally from elastos/: `cargo fmt --all -- --check` clean,
`cargo clippy --workspace --all-targets -- -D warnings` clean,
`cargo test -p elastos-server library::` 63 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…iewer authz

Every media segment and object byte/page request ran is_viewer_capsule ->
resolve_browser_capsule, which reads + JSON-parses the viewer capsule's manifest from
disk (plus an entrypoint stat) — re-resolved from scratch on every fragment, hundreds
of times per playback, for the same capsule.

It is redundant on these paths: a media/object session is created ONLY by the open
path, which resolved + validated the viewer capsule at creation and bound it into
session.viewer. On a segment request that proof is already in hand — the home-launch
token (verified fresh here, scoped to `viewer`; its DID load is memoized) plus the
session.viewer match and the principal match are the authorization. So we drop the
per-fragment manifest re-resolve and rely on the session binding.

Trade-off (documented inline): an active playback/view keeps serving if the viewer
capsule is uninstalled mid-stream, instead of 404-ing — the principal still owns the
content and the token + session + principal gates are unchanged, so it stays
fail-closed. A bogus viewer with no session now rejects via the token/session gate
(401/404) rather than the capsule check (404) — still fail-closed.

Gates (elastos workspace): cargo fmt --check clean, cargo clippy --workspace
--all-targets -D warnings clean, cargo test -p elastos-server viewer 26 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…et advance)

The capability-conformance KNOWN_GAPS registry still described GAP-8 as "audit sink is
best-effort and unsigned ... the log is not hash-chained/tamper-evident." That claim is
now false: emit_best_effort routes through emit(), which builds a hash-chained,
ed25519-signed, fsync'd ChainedRecord (PRE-3). A build-visible registry making a false
claim about the code is exactly the docs-code drift the ratchet exists to prevent.

- Rewrite GAP-8 to the TRUE residual (downgraded med -> low): capability grant/use/denial
  events emit best-effort, so a write failure drops the NON-custody event — a deliberate
  availability choice; custody opens are fail-closed. The log itself is tamper-evident.
- Strengthen denial_is_audited from "log is non-empty" to assert the record is a CHAINED
  envelope (carries seq + prev_hash + record_hash), pinning the now-closed tamper-evidence
  half so a regression flips the ratchet red.

Gate: cargo fmt --check clean; cargo test -p elastos-runtime --test capability_conformance
8 passed (denial_is_audited, gaps_registry_is_intact), 3 still-ignored open gaps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…log redaction

Docs-alignment was the lowest movable scorecard axis: the dDRM/dKMS verdicts (H1, M1,
M3, A7, A1/A2, PRE-1..8) lived as PROSE in PRE_AUDIT.md / PRINCIPLES_CONFORMANCE.md with
nothing failing if they drifted. This adds the DDRM counterpart of the capability side's
KNOWN_GAPS ratchet.

- New `elastos/crates/elastos-server/tests/ddrm_verdicts.rs`: a VERDICTS table pairing
  each verdict's load-bearing invariant with the test (or CI job / explicit structural
  reason) that pins it. `verdicts_registry_is_intact` keeps it honest — a settled
  security verdict must cite a real pin (test path / ci-job / structural), never bare
  prose or a todo. Runs in the CI test job. (The cited capsule-crate tests run under the
  `capsules` job; this registry centralises + indexes them, it does not re-run them —
  stated plainly in the file.)
- Closes the one verified-but-UNENFORCED verdict: PRE-2's log-redaction half is now
  pinned by `log_fp_redacts_sensitive_identifiers` (was convention-only). The raw
  (wallet, content_id) must never appear in the fingerprint.
- PRE_AUDIT.md + PRINCIPLES_CONFORMANCE.md now point to the registry as the
  build-enforced source of truth, the prose as the narrative.

The registry itself caught my first cut (PRE-7/PRE-8 marked settled with a structural
pin) — proof the ratchet has teeth; I made `structural:` an explicit, visible pin
category rather than pretend everything is unit-tested.

Gates (elastos workspace): cargo fmt --check clean, cargo clippy --workspace
--all-targets -D warnings clean, verdicts_registry_is_intact + log_fp_redacts pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…y, permissionless/TEE

Durable capture of the decisions discussed: where the scorecard points are, what was
banked on-branch (Lane A), and the three movers that need a decision:

- ① external crypto/protocol audit (schedule now; our verified-safe registries make it
  cheaper to scope).
- ② one coordinated dKMS-node redeploy bundling AAD-binding + blinded-content-ids/
  ephemeral-keys (privacy) + node-set-pin enforcement, with rollout-safety must-haves
  (execute later; operator has node access).
- ③ permissionless track: staking/slashing (economic security + accountability) +
  attestation — TEE OR reproducible-builds+signing — analysed honestly (TEE strengthens
  the t=2 collusion bar but re-centralizes trust in a chip vendor and isn't a silver
  bullet; staking is the bigger lever for permissionless).

Forward-looking design direction (node items re-verified at execution time), not a spec.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
… registries

The packet led well with the one OPEN invariant (§1 re-seal AAD binding) but didn't
surface the build-enforced evidence for the RESOLVED/cleared findings — which is what
lets the firm confirm-and-skip them and bill for the hard crypto instead.

Adds §4b: the dDRM verdict registry (tests/ddrm_verdicts.rs) + the capability KNOWN_GAPS
ratchet + the PRE_AUDIT 7/8-resolved pass as the scope-OUT index, with the registry
repro commands in §5 and a matching reviewer-checklist line. §1 remains the scope-IN
item. Supports scheduling decision ① at lower cost.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
… strip)

The workspace root tuned [profile.dev] and [profile.test] but had NO [profile.release],
so elastos-server, the runtime, and every member shipped release with rustc defaults
(no LTO, codegen-units=16, unstripped). Add a release profile:

  opt-level = 3   # server runtime: optimize for speed
  lto = "thin"    # most of LTO's size/speed win without fat-LTO's CI build-time blow-up
  strip = true    # smaller; KEEPS panic messages

panic deliberately stays "unwind": elastos-identity's WebAuthn handler relies on
std::panic::catch_unwind for its fail-closed DoS bounds-check (PRINCIPLE 11) — "abort"
would break it. Dial-up path documented inline (lto = true + codegen-units = 1).

Conformance: build-config only; touches no authority/transport/canonical-path/audit
behavior (principles-neutral); keeps the fail-closed WebAuthn path intact. `cargo
verify-project` clean. A full `cargo build --release -p elastos-server` is the confirming
gate; result + binary-size delta to follow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…one Engine

Closes the memory half of the untrusted-capsule DoS traced earlier: an Untrusted Wasm
capsule (launched by CID via request_handler.rs) ran in-process through WasmProvider with
Engine::default() and NO resource limits, so a memory bomb could OOM the host — a gap
against the runtime's OWN Principle 7 ("resource boundaries should apply") and Principle 11
(fail closed). This is part 1 of 2 (CPU/epoch runaway protection is part 2, pending the
service-vs-command model confirmation).

- Every capsule execution's Store now carries StoreLimits (memory_size, table_elements,
  instances=1) via store.limiter(...). A capsule exceeding its budget fails closed (the
  grow/instantiation is denied) instead of exhausting the host.
- Memory budget defaults to a GENEROUS 1 GiB, overridable via ELASTOS_WASM_MEMORY_LIMIT_MB.
  This is a BACKSTOP that bounds the previously-unbounded case — NOT the final design. The
  real design (documented inline + to be tracked): per-capsule manifest-declared,
  capability-GRANTED budgets so game/AI capsules can request more while untrusted capsules
  stay modest (Principle 7: explicit, not ambient), with a host-protective ceiling.
- Share ONE wasmtime Engine across all capsules instead of one per load() (recommended
  pattern; the Engine holds the JIT/compile context, never tenant heap — per-tenant data
  lives in each execution's Store, dropped per run, so multi-tenant clearing is unaffected;
  comment updated to reflect this).

Tests (cargo test -p elastos-compute, 11 pass): over_budget_capsule_fails_closed_not_host_
exhaustion (a module wanting more memory than the cap cannot instantiate, host survives) and
within_budget_capsule_instantiates (the cap never penalizes legitimate use). fmt + clippy
-D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
… follow-ups

So they don't rot: Chunk A landed a backstop global memory cap; the principled
per-capsule capability-granted budget (for games/AI) and the CPU/epoch runaway
protection (Chunk B) are recorded as tracked Lane-A follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…tching the VM path

Refines the Chunk A backstop into the principled design. The manifest already declares
`resources.memory_mb` and the crosvm/VM provider already enforces it (config.rs:270) —
only the in-process WASM provider ignored it. Now WASM honors the per-capsule declared
budget too:

- execute_wasm takes the capsule's `manifest.resources.memory_mb` and enforces
  `min(declared, ceiling)` per Store (PRINCIPLE 7: explicit, granted — not a flat ambient
  cap; PRINCIPLE 11: fail closed). A capsule gets exactly the budget it asked for, and a
  malicious capsule declaring an absurd value is CLAMPED to a host ceiling (8 GiB default,
  override ELASTOS_WASM_MEMORY_CEILING_MB) so in-process WASM can't OOM the host (the VM
  gets this for free — an over-sized VM just fails to launch).
- Replaces Chunk A's global 1 GiB default, which was actually too loose (a 16 MiB capsule
  got 1 GiB of WASM headroom vs its declared 16 MiB in a VM). Audited every WASM capsule:
  all declare memory_mb (16–128 MiB; the gba-emulator game declares 128) and none rely on
  the default, so honoring the declared value breaks nothing and makes WASM consistent
  with the VM.
- Also fixes a doc-comment misplacement Chunk A introduced (struct doc had attached to a
  const).

Tests (cargo test -p elastos-compute, 12 pass): declared_memory_is_honored_and_clamped_to_
ceiling (modest declarations honored exactly; an absurd one clamped) + the existing
fail-closed/within-budget tests. fmt + clippy -D warnings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…ll pending

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…ption)

Before this, a WASM capsule that spun forever permanently held a blocking
thread + CPU and `stop()` could not kill it (the execution runs in a
`spawn_blocking` task that cannot be cancelled). That violated PRINCIPLE 7
(resource boundaries should be enforceable) and left no operator escape hatch.

Wire wasmtime epoch interruption end-to-end:
- Engine is built with `Config::epoch_interruption(true)` (cheap: a load+compare
  at loop backedges).
- Each execution arms an epoch deadline whose callback checks a per-instance
  `should_stop: Arc<AtomicBool>`. With no stop signal it keeps extending the
  deadline, so a legitimate (even long-running) capsule runs untouched.
- `stop()` sets `should_stop` and bumps the engine epoch, so a spinning capsule
  hits its next epoch check and traps. The `Arc` is shared with the running
  task, so the signal reaches the in-flight execution even after the instance is
  removed from the map.

Test `runaway_capsule_is_terminable_via_stop_signal` runs a busy-loop capsule,
signals stop, and asserts it terminates within 5s instead of running forever.
The loop is finite as a CI-hang backstop. Behavior-preserving for every capsule
that is not stopped; the only new effect is that `stop()` now actually halts a
runaway.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
…B2b deferred

B2a (epoch-interruption + stop()-driven termination) is landed and pinned by
runaway_capsule_is_terminable_via_stop_signal. The remaining automatic
no-progress/timeout kill (B2b) stays deferred pending the service-vs-command
policy decision.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
The PRE-2 log_fp test (viewer_open.rs) and the new ddrm_verdicts registry had
long assert!/println! calls that rustfmt wants line-wrapped. Per-crate fmt
during the chunk work checked elastos-compute but not elastos-server, so this
drift would have failed the CI fmt gate. Pure formatting — no logic change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
Records the green-here vs run-live gate boundary (workspace fmt/clippy/test +
verify-capsules passed in-container; carrier/PTY/release-install smokes are
live-only), the four behavior-changing commits to focus the review on (M3, A7,
B1, B2a) with their pins, the audit-ratchet scope-out evidence, the deferred
follow-ups (B2b etc.), and a fast-forward merge sequence into the ddrm
hardening branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VjFQt6DK9ZGnLs4ykUWsuX
The deep-audit A7 fix (edb02ec) makes a quorum-open retry regenerate a
fresh-nonce grant from the cached delegation, so the node's single-use
replay guard no longer rejects a legitimate retry. Update the Carrier
runbook symptom table from "known gap (tracked)" to RESOLVED, pinned to
access_grant::attempt_tests::retry_uses_freshly_regenerated_grant.

Co-authored-by: Cursor <cursoragent@cursor.com>
…route + marketplace contracts

Creator-parity work: substantial creator capsule UI updates (creator.js, index.html), media-provider
changes, the gateway creator route (api/creator.rs), and a marketplace contracts reference doc
(docs/ELACITY_MARKETPLACE_CONTRACTS.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Produce a single MPEG-DASH/CENC-compliant asset (ISO-IEC 23001-7) for every
media (DASH) mint, while keeping the server-decrypt rail's own player working
by down-converting back to a plaintext-looking init at the fetch point.

- ddrm-envelope: shared `pssh` module -- single source of truth for producer,
  runtime decrypt read-path, and playback clients. ELASTOS_PQ_SYSTEM_ID
  (b6e254ef-0dc5-47fe-94e7-0e72ed1dc7b0); build_pssh (v1 box, default-KID +
  opaque .asset.protections JSON) / parse_pssh (v0/1, trailing-moov tolerant).
- ddrm-media: cenc_signal_init() (avc1->encv / mp4a->enca + sinf(frma/schm/tenc)
  + pssh moov child) and strip_cenc_signal() as a byte-exact inverse. Roundtrip
  tests assert strip(signal(x)) == x, no-op on unsignaled, fail-closed on double.
- encrypt-provider: CencSignalInits op -- pure public box surgery (no CEK/secret),
  wraps the runtime-built PSSH envelope and rewrites each per-track init; returns
  transformed inits + pssh_b64 for the MPD.
- creator (producer): after the threshold seal, build the PSSH envelope from
  dkms_protection, CENC-signal each init, and patch stream.mpd with
  <ContentProtection> (mp4protection:2011 + cenc:default_KID + per-system pssh).
- ddrm-media-authority: read_dash_init strips CENC signaling at the fetch point
  so the seal-bound AAD init and the runtime player's served init both match the
  plaintext init the mint sealed (no AAD mismatch).
- Flip on by default: drop the ELASTOS_DDRM_CENC_PSSH gate -- CENC signaling +
  MPD ContentProtection are now standard output. Additive; existing playback
  unchanged.

Squashed from: d012fc4 047d38f 4d26798 d6fb99f 3ac5fdc 4edfd9e
elastos-server 782+95 green; helper 15 green; fmt clean.
…TY-2282)

Stop the dKMS quorum path from wedging and leaking processes under
playback+reload, and make the local test suite pass off the Linux x86_64 gate.

- dkms-authority (Defect A): serve each accepted connection on its own thread
  (serve_unix_listener / serve_tcp_listener) so an idle/slow/leaked client can no
  longer head-of-line-block the daemon in read_frame; revoked_callers becomes
  daemon-lifetime Arc<Mutex> shared state (additive+idempotent); 30s per-conn
  read timeout on both transports. Regression test drives the real Unix accept
  loop (RED pre-fix, GREEN after); 35/35 green.
- key-provider: bound the Unix recover read in establish_dkms_session with the
  same DKMS_TCP_READ_TIMEOUT_MS (5s) the tcp/carrier branches use, so a wedged
  node fails fail-closed within a bounded window. 18/18 green.
- dkms (Defect B): reap leaked quorum helper/provider processes -- add Drop for
  the helper Capsule (kills+reaps key-provider/decrypt-provider children on every
  path), and guard MediaAuthorityProc launch/launch_quorum with a ChildReaper so
  early-return/error paths no longer orphan the raw Child.
- browser: keep the runtime stream socket path within the macOS sun_path limit
  (104) -- fall back to a short "/tmp" base when temp_dir() would overflow, fixing
  the 6 browser-open route tests on macOS arm64 (Linux unaffected).
- test(elastos-server): key component-checksum fixtures by detect_platform() so
  verify/stamp and agent-binary tests run on any host without masking the check.

Squashed from: 50cdc46 0c22718 46a7ba4 d93f673 a9283b5
…view

Address correctness, security, and robustness findings across the DKMS and
encryption/decryption workflows, each with regression tests.

- dkms-authority: revocations now share ONE live Arc<Mutex<HashSet>> across all
  connection threads (was a per-connection snapshot merged only on close), so a
  revoke binds every open connection immediately — "revocation outranks a live
  session" holds under concurrency. Unify the Unix/TCP accept loops into one
  generic serve_accept_loop with a MAX_ACTIVE_CONNECTIONS cap + RAII slot guard,
  bounding the thread/memory-exhaustion (slow-loris) vector on the network node.

- key-provider: distinguish a transport fault from a node rejection
  (NodeRecoverError). A warm pooled connection the node's idle timeout closed is
  re-established and retried ONCE; a genuine rejection still fails closed with no
  retry. Fixes the first open after a >30s idle gap failing below quorum.

- ddrm-media: drive the enca/encv choice off the authoritative hdlr handler type
  (fallback to an expanded audio-4CC allowlist), and make parse_codec_string use
  the same allowlist so the two classifiers can't diverge — an uncommon audio
  codec is no longer mis-signaled as video (non-compliant init + strip missize).

- ddrm-media-authority: read_dash_init propagates strip_cenc_signal errors
  instead of unwrap_or(raw), so a malformed init fails with a precise diagnosis
  rather than an opaque downstream decrypt/quorum failure.

- encrypt-provider: decode_kid16 validates length AND ASCII-hex charset before
  byte-slicing, rejecting a multibyte KID instead of panicking the capsule.

- elastos-server: browser stream sockets use a per-euid dir created 0700 and
  refuse any pre-existing dir not owned by us or group/other-writable, closing
  the world-writable /tmp squatting / socket-hijack vector.
Resolve 25 conflicts across DKMS/DASH/creator + main's authority/provider work.
Key merges: wasm execute_wasm (fuel+epoch+limits), browser-stream socket
hardening on main's path refactor, peer capability allowlist + gossip ops, DID
env-override before cache. Workspace builds; all affected test suites pass.
@irzhywau irzhywau closed this Jul 2, 2026
@irzhywau irzhywau deleted the feat/0.5.0-with-mpeg-dash-compliance branch July 2, 2026 13:10
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.

4 participants