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:
baseline/budgets.tsevaluates deterministic build artifacts such as route count and client JavaScript.operations/usageBudgets.tsevaluates storage, publication, course-document, plugin, and personal-retention meters.
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:
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:
- 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.
- 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 operationsproduces a sourced live-store artifact without writing a snapshot by default.npm run operations:verifyandnpm run checkpass with no blocking meter.npm run test:integrationpasses 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.