Decision commit

How Cenelira binds approval to the version reviewers actually saw.

Every Cenelira external review link captures a SHA-256 fingerprint of the schedule's content and delivery settings at issue time. That fingerprint is the schedule's current reviewVersion. The reviewer's decision is committed inside a transaction whose WHERE clause also matches the current reviewVersion.

If the schedule was edited after the link was issued, the fingerprint changed. The decision update matches zero rows, the link is reclassified as SUPERSEDED, and the external decision endpoint returns HTTP 410 with a typed supersessionReason. This page describes that binding, names the code that enforces it, and maps it to the proof fields that survive the subscription.

Definition

What is version-bound approval?

A reviewer's decision on a Cenelira external review link is bound to the SHA-256 fingerprint of the schedule's content and delivery settings at the moment the link was issued.

Mechanism

Where is the binding enforced?

Inside the decision transaction. The link carries the captured scheduleVersion. The update targets the schedule row with a WHERE clause that also matches the current reviewVersion. If the edit bumped reviewVersion, the update matches zero rows and the link is reclassified SUPERSEDED.

Limit

What is out of scope?

External reviewer identity is self-declared, not authenticated. Decisions that were already committed against a matching earlier version remain committed; this page is about rejecting new decisions whose captured version has since drifted.

Who this is for

Built for operators who have been burned by a stale approval.

Version-bound approval earns its rent on teams where an approval that means "yes to the version I read" is different from an approval that means "yes in general". It adds less value when the content rarely changes between approval and publish.

For
  • Agency operators whose clients approve by external link and whose content moves between approval and publish as copy feedback lands.
  • Brand teams who need a record that the reviewer approved the exact version that shipped, including caption, first comment, destination, and per-platform settings.
  • Ops leads who want the commit refusal enforced in the decision transaction rather than in a review SOP.
Not for
  • Teams that approve once and never edit. The binding fires only on edits between link issue and decision commit.
  • Workflows where any later edit should silently inherit the earlier approval. This page describes the opposite choice.
  • Teams relying on external-reviewer identity as a legal signature. Reviewer identity on these links is self-declared, not authenticated.

Failure scenario

The stale-approval trap, step by step.

The failure mode on a tool without version-bound approval is that "yes" sticks to the schedule, not to the version. The reviewer clicks approve on Monday's draft. An operator tweaks the caption on Tuesday. On Wednesday the tweaked draft publishes under Monday's approval record. The reviewer never saw the copy that shipped.

  1. A scheduler issues an external review link. The link captures the schedule's current reviewVersion as scheduleVersion and writes that value onto the link artifact.
  2. The reviewer opens the link, reads the caption and media, and approves. The link token resolves server-side to the link row.
  3. Before the decision commits, an operator edits the caption, swaps a media file, or changes destination. refreshScheduleReviewVersion in src/lib/schedules/reviewVersion.ts recomputes the SHA-256 fingerprint and writes a new reviewVersion onto the schedule row.
  4. The decision transaction runs an updateMany whose WHERE clause matches the schedule id, the item state, and reviewVersion: item.scheduleVersion. Because the current reviewVersion has changed, the update matches zero rows.
  5. The link is reclassified SUPERSEDED with supersessionReason: 'review_version_changed'. The external decision API returns HTTP 410 Gone. The reviewer sees a terminal card titled "Content changed" with the badge "Superseded".
  6. A fresh review link has to be issued against the new version. No decision is ever recorded against a fingerprint that did not match.

Mechanism

Three version-bound supersession reasons. HTTP 410 Gone on every one.

The fingerprint is computed by computeScheduleReviewVersion and written by refreshScheduleReviewVersion in src/lib/schedules/reviewVersion.ts. The review link service carries the captured scheduleVersion on the link artifact. At commit time, the decision transaction refuses to update any row whose current reviewVersion does not equal the captured one. The ScheduleReviewLinkSupersessionReason union has eight values; the three below are the version-bound subset.

  • review_version_changed

    At external decision commit, the link-captured scheduleVersion does not equal the schedule row's current reviewVersion. The update is rejected before a decision is recorded.

    HTTP 410 link_superseded
  • review_version_missing

    The schedule has no reviewVersion at decision commit time, so the link cannot verify the captured fingerprint against the current one. The decision is refused.

    HTTP 410 link_superseded
  • delivery_set_changed

    The delivery set captured on the link no longer matches the schedule's current scope. A delivery was added or removed after the link was issued.

    HTTP 410 link_superseded

Proof artifact

The proof record names the version and the decider.

The decision binding is not a private server detail. Five fields in the signed 17-field proof record carry the version and the approval state that survives export.

versionFingerprint is resolved by collectWorkspaceProofRecords in src/lib/exports/workspaceProofExport.ts as the first non-empty value in this order: the SCHEDULE_HANDOFF_COMPLETED event's reviewVersion, the latest REVIEW_APPROVED or EXTERNAL_REVIEW_APPROVED event's reviewVersion, or the current schedule row's reviewVersion. When none of those sources carry a value, the field is null.

approverIdentity stores the label of who decided, read from the latest approval event. For internal decisions that is the workspace user label. For external decisions it is the self-declared reviewer label captured on the link.

approverSource records whether the decision came from an internal workspace approval or from an external review link. It defaults to internal for REVIEW_APPROVED events and external_self_declared for EXTERNAL_REVIEW_APPROVED events, so the row is never ambiguous on provenance.

approvalTimestamp records when the decision was committed, taken from the approval event's createdAt. Paired with versionFingerprint, it answers "which version was approved, and when" from a single row.

invalidationReason is derived from a REVIEW_LINK_EXPIRED or REVIEW_LINK_SUPERSEDED event for the same schedule. Expired links write expired; superseded links write the supersession reason recorded on the event. The field is null when no such event exists for the schedule. The signed PDF and CSV twin both carry these columns. See the proof record schema for the full 17-field inventory.

What this does not claim

Honest limits on the binding.

Version-bound approval is one gate in the publish path. This page is explicit about where the gate stops.

  • External reviewer identity on the review link is self-declared. The binding refuses stale decisions; it does not verify the human who clicked approve.
  • A decision that was already committed against a matching earlier reviewVersion remains committed. The later edit does not erase the prior approval from the proof record.
  • The binding runs at external decision commit. Status changes and other publish-path refusals have their own typed supersession reasons outside the version-bound subset.
  • This page describes how Cenelira enforces the binding at decision commit time. It makes no claim about what other approval tools store in their own version history.
  • The fingerprint covers the fields in ScheduleReviewVersionPayload. Edits to fields outside that payload do not trigger supersession on their own.

FAQ

Short answers for operators.

What counts as "an edit" for version-bound approval?

Anything that changes the schedule's reviewVersion fingerprint: caption, first comment, publishAt, destination, connected account, media bundle order, per-platform settings (for example TikTok privacy level and YouTube upload kind), and composition signatures. These are the fields in ScheduleReviewVersionPayload that the SHA-256 fingerprint is computed from.

What does the external reviewer see on a stale link?

The review page replaces the content surface with a terminal card. The card carries the badge "Superseded" and the title "Content changed". The body tells the reviewer a newer link is required to review the current version. Below the card the page shows the 8-character link fingerprint, the target account label if one was recorded, the decision timestamp if one was recorded, the supersession reason with underscores replaced by spaces, and a prompt to contact the team that sent the link. The original caption, media, and version are no longer rendered on the link; it is a terminal receipt, not a snapshot of the content the reviewer first saw.

Does this invalidate approvals that were already recorded?

No. A decision that was already committed against a matching reviewVersion stays committed. Version-bound approval rejects new decisions whose captured version no longer matches. The subsequent edit does not erase the prior approval from the proof record.

Where does the version fingerprint show up on the proof record?

In the signed 17-field proof record, versionFingerprint is resolved by collectWorkspaceProofRecords in src/lib/exports/workspaceProofExport.ts as the first non-empty value across the SCHEDULE_HANDOFF_COMPLETED event's reviewVersion, the latest REVIEW_APPROVED or EXTERNAL_REVIEW_APPROVED event's reviewVersion, or the current schedule row's reviewVersion. approverIdentity, approverSource, and approvalTimestamp are read from the latest approval event. approverSource defaults to internal for REVIEW_APPROVED events and external_self_declared for EXTERNAL_REVIEW_APPROVED events. invalidationReason is derived from a REVIEW_LINK_EXPIRED or REVIEW_LINK_SUPERSEDED event for the same schedule: expired links write 'expired'; superseded links write the supersession reason. The field is null when no such event exists.

Is this different from version history in other tools?

This page describes how Cenelira enforces version-binding at decision commit time. It does not make claims about what other approval tools store in their own version history. The testable difference is that the decision transaction refuses to commit when the captured fingerprint does not equal the current one.

Last reviewed 2026-04-24

Get notified when the approval integrity checklist ships.

A short checklist for teams whose clients approve by link, covering what should invalidate an approval, how to read the proof fields that record the decision, and what an external reviewer should expect on a stale link. Leave an email and we will send it when the checklist is ready.

We use this only to tell you when the relevant update ships. Privacy.

Version-bound approval · Cenelira – Cenelira