Skip to content

timeline: journal-durable CreateAsset/RemoveAsset commands#6456

Merged
shipyard-local[bot] merged 1 commit into
mainfrom
feature/timeline-createasset-command
Jul 22, 2026
Merged

timeline: journal-durable CreateAsset/RemoveAsset commands#6456
shipyard-local[bot] merged 1 commit into
mainfrom
feature/timeline-createasset-command

Conversation

@shipyard-local

@shipyard-local shipyard-local Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds a journal-durable CreateAsset command (with its RemoveAsset inverse) to the timeline command vocabulary — the foundational slice of Phase-2R item #4 (the journal survives recording). Independent of Phase-2 playback/delivery: pure core/timeline model + journal, built on current main.

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

  • CreateAsset carries the whole MediaAsset by value, including the ContentHash that is the asset's durable identity (assets.hpp: "SHA-256 is the durable identity of media bytes; paths must never participate in identity").
  • The reducer plans the ItemKind::Asset identity exactly like a clip insert (plan_identity_insert) and appends the sealed asset by reference to its hash via Project::append_asset.
  • Replay never re-captures or re-hashes media bytes — the same checkpoint + journal reproduce a byte-identical asset table. Proven by a test that serializes the committed vs replayed documents and asserts the canonical JSON is identical.
  • Journal replay already passes 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/RemoveAsset are the asset-table mirror of InsertClip/RemoveClip, preserving the §1 determinism contract and the reducer's one-inverse-per-command invariant:

  • RemoveAsset fails closed while any clip's MediaRef still references the asset, tombstones the identity like a clip removal, and its inverse re-creates the sealed asset — undo/redo stays whole.
  • Asset validation and identity-mutation application are factored into shared model.cpp helpers so construction, sequence replacement, and asset mutation enforce one sealed-identity and one identity-transition path.
  • An invalid/empty ContentHash is rejected before the asset can enter the document.

Tests (ship with the fix)

  • Reducer append preserves content_hash and consumes one id.
  • Journal replay determinism — committed vs replayed serialize byte-identically.
  • Created asset round-trips through the project schema (serialize → deserialize).
  • Undo/redo cycles a create through its RemoveAsset inverse (tombstone → reactivate).
  • Negative/defensive: duplicate live id → IdentityNotAvailable; empty/invalid ContentHash → fail-closed InvalidContentHash; RemoveAsset of a referenced asset → fail-closed MissingAsset.

Validation

  • Full cmake --build over all targets (incl. GPU): 0 errors (the Command variant grew by two alternatives).
  • ctest -R "timeline|playback": 82/82 pass; the two touched suites add 4066 + 160 assertions.
  • -fno-exceptions -fno-rtti timeline TU builds clean.
  • gates.sh / docs-noise / skill-sync (timeline SKILL.md updated; web-plugins skip trailer) / version-bump (version-at-land) / planning-gitlink: all clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS


🔎 Provenance

Agent claude
Machine m3
Tab Timeline v3
Directory /Volumes/Workshop/Code/pulp-journal2r
Session c66f4a64-25ea-42e5-b184-d48611a066a2

Resume

claude --resume c66f4a64-25ea-42e5-b184-d48611a066a2

Jump to this tab

cmux surface focus C0B3DA54-39AF-4DAA-8089-AA08DAB71968

Relaunch (any agent)

cmux surface resume get --surface C0B3DA54-39AF-4DAA-8089-AA08DAB71968

Restore URLhttps://claude.ai/code/session_01TVQHcnbg3t6TvXsdv6gXhS

stamped 2026-07-22 04:21 UTC

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
@shipyard-local
shipyard-local Bot enabled auto-merge July 22, 2026 04:22
@shipyard-local
shipyard-local Bot disabled auto-merge July 22, 2026 04:25
@shipyard-local
shipyard-local Bot enabled auto-merge July 22, 2026 04:28
@shipyard-local
shipyard-local Bot added this pull request to the merge queue Jul 22, 2026
Merged via the queue into main with commit 5be73f1 Jul 22, 2026
31 of 38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant