timeline: journal-durable CreateAsset/RemoveAsset commands#6456
Merged
Conversation
Assets could only enter a timeline document at construction or deserialize via ProjectInput.assets; there was no asset-mutating command, so a recording or import could not be captured as a replayable journal entry. This adds a CreateAsset command (and its RemoveAsset inverse) that seals a media asset as a pinned project input. CreateAsset carries the whole MediaAsset by value, including the ContentHash that is the asset's durable identity. The reducer plans the ItemKind::Asset identity exactly like a clip insert and appends the sealed asset by reference to its hash; replay never re-captures or re-hashes media bytes, so the same checkpoint plus journal reproduce a byte-identical asset table. RemoveAsset is the coherent inverse (mirroring InsertClip/RemoveClip): it fails closed when any clip still references the asset, tombstones the identity, and its own inverse re-creates the sealed asset, keeping undo/redo whole. Asset validation and identity-mutation application are factored into shared helpers so project construction, sequence replacement, and asset mutation all enforce one sealed-identity and one identity-transition path. An asset with an invalid or empty ContentHash is rejected before it can enter the document. Tests: reducer append preserves the content_hash; journal replay reproduces a created asset byte-identically (serialized JSON compared); the asset round-trips through the project schema; undo/redo cycles a create through its RemoveAsset inverse; a duplicate live id and an invalid content_hash are both rejected fail-closed; RemoveAsset is rejected while a clip references the asset. Skill-Update: skip skill=web-plugins reason="timeline model/journal change; no web ABI, worklet, or wasm-runtime surface touched" Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a journal-durable
CreateAssetcommand (with itsRemoveAssetinverse) to the timeline command vocabulary — the foundational slice of Phase-2R item #4 (the journal survives recording). Independent of Phase-2 playback/delivery: purecore/timelinemodel + journal, built on currentmain.Until now assets could only enter a document at construction / deserialize via
ProjectInput.assets; there was no asset-mutating command, so a recording or import could not be captured as a replayable journal entry.The sealed-content / determinism contract
CreateAssetcarries the wholeMediaAssetby value, including theContentHashthat is the asset's durable identity (assets.hpp: "SHA-256 is the durable identity of media bytes; paths must never participate in identity").ItemKind::Assetidentity exactly like a clip insert (plan_identity_insert) and appends the sealed asset by reference to its hash viaProject::append_asset.allow_tombstone_restore = (entry.kind == History)and asserts the replayed dirty set matches the recorded one; the new commands' deterministic dirty items satisfy that.Shape (clean extension, not a break)
CreateAsset/RemoveAssetare the asset-table mirror ofInsertClip/RemoveClip, preserving the §1 determinism contract and the reducer's one-inverse-per-command invariant:RemoveAssetfails closed while any clip'sMediaRefstill references the asset, tombstones the identity like a clip removal, and its inverse re-creates the sealed asset — undo/redo stays whole.model.cpphelpers so construction, sequence replacement, and asset mutation enforce one sealed-identity and one identity-transition path.ContentHashis rejected before the asset can enter the document.Tests (ship with the fix)
content_hashand consumes one id.RemoveAssetinverse (tombstone → reactivate).IdentityNotAvailable; empty/invalidContentHash→ fail-closedInvalidContentHash;RemoveAssetof a referenced asset → fail-closedMissingAsset.Validation
cmake --buildover all targets (incl. GPU): 0 errors (theCommandvariant grew by two alternatives).ctest -R "timeline|playback": 82/82 pass; the two touched suites add 4066 + 160 assertions.-fno-exceptions -fno-rttitimeline TU builds clean.web-pluginsskip trailer) / version-bump (version-at-land) / planning-gitlink: all clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS
🔎 Provenance
claudem3/Volumes/Workshop/Code/pulp-journal2rc66f4a64-25ea-42e5-b184-d48611a066a2Resume
Jump to this tab
Relaunch (any agent)
Restore URL — https://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS
stamped 2026-07-22 04:21 UTC