Disciplines · Runbooks

Runbook — Yemaya study workspace: rights expiry and the retention sweep

A grant lapsing and its bytes being removed are separate events, and confusing them is how a workspace either keeps material it no longer has rights to, or destroys material it was still allowed to hold.

9sections4 minread

On this page

Checklist: YSD-22058 (expiry) · drill kind expiry · YSD-3062 Decision owner role: the rights owner for the study workspace Machinery: apps/yemaya/svc-study-workspace/src/persistence/expiry-enforcement.integration.spec.ts

This one had no nearest guidance at all — not a shortfall in another document, nothing. It is also the procedure most likely to be run under someone else's deadline, because a lapsed grant is a rights question before it is an operational one.

Running this document IS the drill. Record it in procedure-exercise-register.json.

The two boundaries, which are not the same day#

A grant lapsing and its bytes being removed are separate events, and confusing them is how a workspace either keeps material it no longer has rights to, or destroys material it was still allowed to hold.

Boundary What happens Exercised on every change
The lapse Every surface suppresses the work with no reprojection yes
End of the retention window The sweep removes bytes and schedules derivative cleanup yes

"With no reprojection" is the load-bearing phrase. Suppression is decided at read time against the live grant, not by rewriting projections at the boundary. So there is no reprojection job to watch, and a work still appearing after its lapse is not a stale index — it is a read path that did not ask. That is the finding, and it is a defect rather than a backlog.

Before the lapse#

While licensed, the work surfaces everywhere and its media is embedded. Nothing to do.

At the lapse#

  1. Confirm suppression on the read surfaces, not in the database. The database still holds the work — that is correct and is not a leak.
  2. Confirm queued embedding work enqueued before the lapse is CANCELLED at drain time, not failed. Cancellation is the correct outcome: the job was legitimate when queued and is not permitted now, and a failure would page somebody about a system working properly.
  3. If any surface still shows the work, that read path does not consult the grant. Treat it as a rights incident, not a cache problem.

At the end of the retention window#

  1. The sweep removes bytes and schedules derivative cleanup. Derivatives (proxy, caption, thumbnail, crop, waveform, stem, analysis-artifact) are purged with the original they came from.
  2. Confirm the original is gone from object storage and that its derivatives were scheduled — a derivative that outlives its original outlives the rights basis it was made under.

The objective#

Expiry time objective: 45 minutes, from the lapse being noticed to "Done means" being satisfied. Set 2026-08-15.

The five steps above are not budgeted individually because four of them are the same act — confirming one more read surface — and a per-surface budget would invite skipping the last one to make the total.

Measured, and the measurement is a floor. scripts/time-expiry.ts crosses both boundaries against a running deployment in real time: it writes a grant with a validUntil twenty seconds out and a zero-day retention window, waits for wall-clock time to pass it, and then reads every surface. No clock is moved and no record is edited to look expired, which is the stronger version of this drill's own verification. Against the deployment on 2026-08-15: the five read surfaces answered in 0.03 seconds and the sweep took 0.05.

Forty-five minutes is therefore for the operator: confirming the suppression on each surface rather than one, and deciding — at step 3 — whether what they are looking at is a rights incident. That decision is the procedure.

What the run found, and it is the good news: at the lapse, with no sweep run, playback refused, allowed-actions dropped playback, and search returned zero hits while naming the work in excludedWorks with the reason grant … expired at …. That is the read path consulting the live grant, which is what "with no reprojection" means, and it is worth writing down that the honest refusal carries the work's id — a check written as "the work id does not appear in the response" fails against a system that is behaving correctly, and would report a rights leak where there is a reason being given.

The library is not one of the suppressed surfaces, and should not be. GET /sources is an inventory: an operator whose grant lapsed has to see the work in order to renew it. It carries a rights summary, and after the lapse that summary reads { active: 0, expired: 1 }. Confirm the summary, not the absence.

Detection#

The sweep is scheduled, so the failure mode is silence rather than an alert: a sweep that did not run leaves expired material in place and says nothing. Check that the sweep ran within its interval before concluding that nothing was due.

Communication#

Audience Within
The rights owner, when a grant lapses on material in active use 24 hours
The learner, when their in-flight work referenced the lapsed source 24 hours

Done means#

  • Every read surface suppresses the work.
  • Queued work is cancelled, not failed.
  • After the window: bytes gone, derivatives scheduled, tombstone in place.
  • Recorded in the procedure exercise register.

Known shortfall#

Both boundaries are driven against real stores by the integration suite. What no drill has done is cross them on material somebody was actually using at the time — the lapse mid-session case, where a learner has the work open. The suite crosses the boundary; a person has not.