Skip to content

Platform operations

Production runs on the OVH VPS documented in VPS deployment, using the filesystem PublicationStore. It is the only store: the hosted-provider driver and its usage dashboard were removed once the VPS became production.

Contract

Operations must answer who owns every stored byte and billed operation without making cleanup a data-loss path. Build output and the running platform use separate budget registries:

Every remaining meter is measured from the store and the publication audit ledgers, so the report needs no external observation. The envelopes those meters are judged against were inherited from the retired provider's free-tier allowances and still need re-deriving from the VPS disk and its backup window; until then they read as conservative placeholders rather than facts about this host.

A usage budget may be blocking only when its envelope is published or observed with a source and date. To promote an advisory budget, record the dated envelope, preserve its provenance, change enforcement, and add a test proving the new boundary. Never turn a guessed allowance into CI.

Every storage key is classified by operations/classify.ts. Unknown keys remain visible as unattributedBytes and are never deleted. Plugin keys are reclaimable only after the installed plugin provider completes successfully; provider failure retains them all.

Reporting and budgets

The report reads the configured store:

npm run operations

It loads .env.local when present and needs PUBLICATION_FS_ROOT and PUBLICATION_PUBLIC_BASE_URL; npm run operations -- --fixture runs without a store. It writes operations.generated.json. Add -- --write-snapshot only when an immutable daily growth snapshot should be written. Growth requires at least two snapshots.

npm run operations:verify is network-free. It reads the committed report, prints advisory warnings, and fails on blocking; it is part of npm run check and CI.

Reviewable cleanup

collect.ts implements two phases:

  1. A dry run lists and classifies the store, resolves live course and installed plugin revisions, and returns the proposed deletions plus a SHA-256 digest over pathnames and ETags.
  2. Confirmation recomputes the plan. Any changed digest returns 409 plan-changed; matching plans delete at most 200 objects, each conditional on its observed ETag.

Pointers, the course catalog, global-index shards, operations snapshots, and unknown keys are protected. Every readable complete course revision is retained as restore history, not just the current pointer; incomplete revisions and unreferenced content remain reclaimable. An unreadable retained manifest stops cleanup for its course. Missing or failing plugin ownership information retains every plugin key.

From an authenticated owner session, use the same origin and mutation headers as publication:

curl "$ORIGIN/api/course-operations/collect" \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'X-StudyHub-Mutation: 1' \
  -H "Origin: $ORIGIN" \
  -H "Cookie: $OWNER_COOKIE" \
  --data '{"mode":"dry-run"}'

curl "$ORIGIN/api/course-operations/collect" \
  -X POST \
  -H 'Content-Type: application/json' \
  -H 'Sec-Fetch-Site: same-origin' \
  -H 'X-StudyHub-Mutation: 1' \
  -H "Origin: $ORIGIN" \
  -H "Cookie: $OWNER_COOKIE" \
  --data '{"mode":"confirm","planDigest":"<digest from dry run>"}'

Review the full deletion list and reclaimed bytes before confirming. A changed plan is a request to review again, never a reason to retry unconditionally.

Recovery

Run npm run test:integration for the recovery drills. The Goal 9 suites prove:

  • a restore publishes old content forward as a new sequence;
  • quota exhaustion leaves the current pointer and previous bytes intact;
  • plugin-provider failure retains every plugin key;
  • installed prior plugin revisions remain available for rollback while uninstalled revisions become eligible;
  • both supported course schemas remain readable by a one-deployment rollback;
  • live cleanup deletes exactly its reviewed orphan, preserves pointer, manifest, referenced content, catalog, and global index, and refuses a stale digest.

Personal backup/restore is deliberately separate. services/backup.ts and Settings export browser-local progress, review history, notes, drawings, preferences, and course-identity state. Course content recovery restores an immutable published revision forward; a personal backup import never changes public course content.

Adding courses and extensions

A new course moves stored-byte, largest-course-document, advanced-operation, simple-operation, and transfer meters. Initial publication uploads every distinct document and asset; later publications reuse content-addressed objects and normally write only changed objects, the new manifest, and the mutable discovery documents. Follow Adding a course, publish once, then regenerate the operations report before accepting the growth.

For extensions, follow Developing a .tuesh extension and the Plugin SDK. Installed package revisions are reported through OperationsResourceProvider; keep all revisions needed by disable, update, rollback, or course pins in the registry. Extending the SDK must preserve capability negotiation and must not broaden existing grants.

VPS storage

The production VPS deployment is documented in VPS deployment. The provider-neutral boundaries are PublicationStore, documents/urls.ts, and repository environment configuration. PUBLICATION_STORE_DRIVER=filesystem stores content-addressed bodies and an atomic index under PUBLICATION_FS_ROOT, and it is the default when the variable is unset. Any store added later must provide immutable object creation, ETag-conditional mutable writes and deletes, bounded listing, public/cookie-free asset delivery, and equivalent cache policy. Course documents, indexes, identities, plugin packages, and personal backup formats do not change.

Verification

  • npm run operations produces a sourced live-store artifact without writing a snapshot by default.
  • npm run operations:verify and npm run check pass with no blocking meter.
  • npm run test:integration passes the cleanup and recovery drills.
  • Settings makes non-OK budgets visible only to the owner and still works when the report fails.
  • A dry run changes no objects; a confirmed matching plan deletes only its listed conditional keys.