---
name: timeback-content-alpha
description: Use when building apps, reports, QA probes, or deliverables against the TimeBack platform3 Content Alpha surface. The skill teaches agents to browse, launch, author, package, and report on student-touchable Content kinds: question, test, test_spec, test_bank, article, video, audio, image, interactive, media_asset, external_link, and script, with expected_xp, named catalog facets, assessment components, typed interactive composition, media, the frozen curriculum-link archive, content-to-content references, student-safe views, trust evidence, policy reads, QTI package imports, Common Cartridge exports, and content-effectiveness rollups by calling the live surface only. A stimulus is question anatomy, not a public Content kind. Do not re-implement QTI parsing, assessment-component reconstruction, interactive cue/segment reconstruction, scoring, answer-key extraction, media URL rewriting, content-kind inference, catalog facet logic, Common Cartridge packaging, effectiveness math, mastery/results/events logic, runtime/downstream copied enum tables, or business rules.
---

# TimeBack Content Alpha

Use this skill when the user asks for a working app, report, integration proof, API probe, or customer deliverable that uses the platform3 Content Alpha surface.

Do not use it for the 1EdTech QTI surface, generic QTI research, Results, Events, Curriculum graph authoring, roster work, or database work outside the public API.

## Install

Install the whole `pack/` directory as one skill. Do not split the reference files away from `SKILL.md`.

Hosted one-command installs:

Claude Code:

```bash
curl -fsSLL https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/skill_pack/install.sh | bash -s -- claude
```

Codex:

```bash
curl -fsSLL https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/skill_pack/install.sh | bash -s -- codex
```

Perplexity Computer or another file-backed agent:

```bash
curl -fsSLL https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/skill_pack/install.sh | bash -s -- perplexity ./skills/timeback-content-alpha
```

Local workspace install, when you already have this `OUT_DIR` checked out:

```bash
# Claude Code
mkdir -p ~/.claude/skills/timeback-content-alpha
cp -R pack/* ~/.claude/skills/timeback-content-alpha/

# Codex
mkdir -p ~/.codex/skills/timeback-content-alpha
cp -R pack/* ~/.codex/skills/timeback-content-alpha/

# Perplexity Computer or another file-backed agent
mkdir -p ./skills/timeback-content-alpha
cp -R pack/* ./skills/timeback-content-alpha/
```

The hosted installer fetches `SKILL.md`, all agent adapters, all references, the verifier, and the latest evidence. Set a custom install directory with the optional second argument, for example `bash -s -- codex /tmp/timeback-content-alpha`.

## Alpha Content In 60 Seconds

Content is the stuff a student can open or touch. The canonical Content storage kinds are `question`, `test`, `test_spec`, `test_bank`, `article`, `video`, `audio`, `image`, `interactive`, `media_asset`, `external_link`, and `script`. A stimulus is a sub-part of a `question`, not an independently addressable Content row. `test` is a renderable form, `test_spec` is the blueprint/table of specifications, `test_bank` is a stored bank of equivalent forms, and `script` is authored text used by audio/video or as its own student-touchable asset. Content stores the blank instrument, assessment components, typed interactive article/video composition, student-safe launch projection, student-independent `expected_xp`, media/external-link/script facts, content-to-content references, and catalog discovery sidecars. It does not store a student's answer, score, event stream, mastery state, gradebook row, report card, course, unit, chapter, lesson, lesson section, playlist, question bank, LOR/library clone, or Common Cartridge bundle.

Every Content row has typed fields. Use them directly; never parse a title, URL, filename, XML package, or media type to infer meaning.

Core objects:

- `content_item`: the catalog row. Key fields are `content_id`, `content_kind`, `title`, `subject_id`, `target_grade_id`, `expected_xp`, `latest_version_id`, `source_system`, and `status`.
- `content_version`: append-only versioned launch material. It returns `student_view`, `editing_draft`, `etag`, and provenance. HTML student views may carry the Content-owned `tb-*` body contract. TimeBack identical bytes replay, while changed canonical bytes append a hash-bound UUIDv5 version with the next ordinal and supersedes edge.
- `student-view`: the blank student-safe projection. It must have `answerKeysIncluded: false` for student launchers.
- `test_spec`: first-class test blueprint row with KC/DOK coverage, item-type mix, difficulty constraints, passing rule, form count, overlap limit, and enemy-item rules.
- Create a `test_spec` through the authenticated `POST /tenants/{tenantId}/alpha/content/items` route with `content_kind=test_spec`, `Idempotency-Key`, and all seven blueprint fields. The response's native `content_id` is the `spec_id` returned by `GET /items/{contentId}/blueprint`; never persist an app-local blueprint row.
- `test_bank`: first-class equivalent-form bank row linked to one `test_spec`; fixed forms are `content_reference` rows with `relationship_kind=bank_member`.
- On `test_bank` item create, reuse a stable `Idempotency-Key`. Existing completed/in-progress/conflicting same-key state is handled first. A fresh request acquires the tenant graph fence and resolves its active same-tenant `test_spec` before claiming the key or writing the item. If the target is invalid, fix the body and reuse that same still-unclaimed key; do not rotate it.
- To change a `test_bank` definition, `PATCH /items/{bankId}` with the last ETag and only the new `spec_id` and/or `membership_rule`. Never send either required field as `null`. The `spec_id` must resolve in the routed tenant to an unretired, non-archived, non-status-retired `content_kind=test_spec`, `assessment_role=spec` item with a readable blueprint sidecar. An actual change is allowed only for an unretired draft with unknown trust. Active bank-member rows—fixed forms or PP100 adaptive shards—forbid both a blueprint swap and a switch to `qti_adaptive_selection`; retire those members first. A rejected `400 content.validation_failed`, `409 content.bank_membership_frozen`, or `409 content.invalid_test_bank_member_contract` preserves the item, bank sidecar, graph, and ETag. Full member-form blueprint comparability and active-KC coverage validation remain pending.
- `script`: first-class authored script row. Link produced audio/video back to it with `content_reference.relationship_kind=source_script_for`; do not store it as an article subtype.
- `assessment_component`: blank QTI-backed structure rows for sections, parts/interactions, question refs, distractors, rubrics, and pre-authored feedback. Read them from `/assessment-components`; do not parse raw QTI.
- `interactive_composition`: typed Content-owned delivery structure for interactive articles/videos. Read and write it at `/interactive-composition`; it stores article segment order or video cue timestamps to QTI-backed question refs. Current active-row uniqueness is item-scoped, so an aggregate can mix versions and reports the first active row's `version_id`; do not claim member-version coherence without checking it. Do not put cue timing or segment order in QTI or Curriculum, and do not parse `tb-*` markup as canonical composition when this endpoint is available.
- `media_asset`: reusable or supporting Content row, plus first-party media attached to a version. Fetch `delivery_url` exactly as returned when the media endpoint returns one.
- `external_link`: governed outside URL facts for `content_kind=external_link`; never store credentials or secret-bearing URLs.
- `curriculum_link`: a frozen historical Content-to-Curriculum relationship archive. Use archive rows only for reconciliation; existing rows have no automatic TTL. Current lesson membership has one ordered use per Content item: one primary object may use the lesson target once, while a many-item lesson on the deployed CASE adapter uses one targeted child `practice`/`review` component per item. Repeatedly PATCHing the lesson target replaces the prior item. Curriculum remains authoritative for placement and order. For thin-client item discovery, `GET /items?curriculum_node_id={lessonId}` read-only projects both historical archive matches and active Content targets on that Curriculum component and its active descendants; it writes no placement rows.
- `content_reference`: a real relationship row from one flat Content row to another. Use it for embeds, dependencies, captions, transcripts, thumbnails, source files, alternatives, and related material; never nest referenced content inside the parent payload. Caller-supplied optional version UUIDs are not currently proven to belong to the paired item or tenant, so resolve them before writing.
- Active reference, KC-tag, QTI-import, and TimeBack-import graph writes share one tenant fence and a bounded validation budget. One write may affect at most 1,024 active test banks; each affected bank may contain at most 1,024 active members and at most 4,096 joined question/KC facts. Those limits are inclusive and apply even to revivals, cardinality-neutral upserts, and exact active repository replays. A cap-plus-one write returns typed `409 content.invalid_test_bank_member_contract` with `fieldErrors.name=bank_members` for member overflow or `form_graph` for affected-bank/joined-fact overflow, and leaves the graph, item timestamp/ETag, fresh idempotency claim, and completed ledger unchanged. Retire or split the graph, then retry. An already-completed HTTP idempotency replay is mutation- and budget-free after ledger arbitration.
- `content_catalog_facet`: governed discovery facet definition. The twelve named facets are `subject`, `grade_band`, `language`, `accessibility_profile`, `rights_license`, `provenance`, `quality_rating`, `kc_refs`, `standards_framework_refs`, `content_kind`, `expected_xp_band`, and `modality`.
- `content_catalog_facet_assignment`: sidecar assignment at content, version, or media grain. Use it for LOR/library/catalog discovery; do not recompute it from titles or related rows.
- `content_effectiveness`: read-only rollup from Results. Display counts/rate/freshness exactly as returned.
- `content_trust_evidence`: safety/validation status and evidence link.
- `content_policy`: current policy projection. Read `/policies` where the tenant is provisioned, but remember that the source adapter currently executes a versioned bundled policy and real-tenant policy rows may be absent.
- `content_release_eligibility`: Content's current exact-version lifecycle and revocation answer. `release_eligible` is exactly `serveable`: true for a published, unretired item whose item trust is not `failed` (owner ruling #2534: approval never gates visibility, so `unknown` trust serves). Read it before serving or placing output; never recompute or copy the verdict into Curriculum.

Positive exact-version QC is recorded by Content import/QC or the governed publication command as provenance; it is never a launch precondition. Item `trust_status=failed` revokes every version on the next eligibility read. There is no producer append, caller verdict, quality-bar requirement, owner allowlist, or second attestation.

Renderer path:

- For interactive articles/videos, fetch typed composition from `/interactive-composition` and pass it to the player as `CONTENT_LOAD.interactiveComposition`; use `question_ref.content_id` and optional `question_ref.assessment_component_id` provenance from that structure for Events/Results wiring.
- For stored Content HTML bodies using `tb-*` markup, the sanctioned display path is Curriculum member `renderer_ref=renderer:interactive-lesson`, resolved through the platform renderer registry. Treat the markup as body/render compatibility, not the canonical source for cue-to-question or segment-to-question composition when typed composition exists.
- Governed articles may use nonempty text blocks or exact `type=html` blocks. The current player sends HTML blocks through the same centralized sanitizer as top-level `student_view.html`; metadata-only, image-only, or otherwise invisible blocks are not a renderable article contract.
- Host the Platform3 first-party player in an iframe at `https://platform3-andymontgomery-9773s-projects.vercel.app/player`. The playground/readback demo is `https://platform3-andymontgomery-9773s-projects.vercel.app/player?demo=1`. Automated preview/QC lanes that need deliberate renderer upgrades can pin the current immutable build at `https://platform3-andymontgomery-9773s-projects.vercel.app/player/versions/2026-08-05.issue-2501-article-html/`.
- Before claiming renderer coverage, read `https://platform3-andymontgomery-9773s-projects.vercel.app/player/renderer-capabilities.json`. A Curriculum registry row means `registered_for_routing`, not full rendering. Current evidence includes governed article HTML blocks through the centralized sanitizer and exactly 8/21 QTI item interactions, including the bounded graphicGapMatch text-label/rect-target subset; `renderer:qti-test` verifies exact forms and Curriculum-selected test-bank forms only when every member stays within that subset. Unsupported or incomplete members fail visibly.
- For `graphicGapMatch`, import a packaged image with nonblank alt plus dimensions, `qti-gap-text` labels, rectangular associable hotspots wholly inside the image, bounded associations, and a `multiple`/`directedPair` response. Content materializes the image through its existing authenticated media route and exposes only render data in the exact student view; correct pairs remain in server-side answer/scoring sidecars. Never synthesize missing media or geometry and never fall back to free text.
- In the host message handler, reject messages where `event.source !== iframe.contentWindow` and reject messages whose `event.origin` is not the player origin. The player iframe intentionally posts outbound with `postMessage(..., '*')`; do not hardcode a parent origin inside the iframe.
- Wait for `CONTENT_READY`, then post `CONTENT_LOAD` with `{ html, contentId, interactiveComposition?, contentState?, config? }` for stored HTML or `{ qtiXml, title?, contentId?, config? }` for ad hoc A1 text-structural QTI preview before ingestion. Listen for `CONTENT_STATE_CHANGE`, `CONTENT_RESIZE`, and `CONTENT_ERROR`; handle `TTS_GENERATE_REQUEST` only if your host provides TTS, and echo `payload.requestId` in `TTS_GENERATE_RESPONSE`.
- The implementation pointer is `platform3:first-party-reference-player:/site/player`. The private Trilogy QTI renderer is inventory input only, not the sanctioned Platform3 display path.
- Content owns the stored HTML vocabulary: `tb-segment`, `tb-interaction`, `tb-qti-assessment-item`, `tb-catalog`, `data-catalog-idref`, `practice-step`, `example-practice-container`, glossary/narration tokens, and related `tb-*` config tokens. Content also owns typed interactive composition. QTI XML, question items, answer keys, and scoring stay QTI-owned. Durable learner progress flows through Caliper Events/Results, not renderer or host private state.

`GET /` returns governed value sets including `content_kind=question`, `test`, `test_spec`, `test_bank`, `article`, `video`, `audio`, `image`, `interactive`, `media_asset`, `external_link`, and `script`; `interactive_composition_type=interactive_article`, `interactive_video`; `assessment_component_role=test`, `section`, `part`, `question`, `question_ref`, `distractor`, `rubric`, and `preauthored_feedback`; the generated authoring `subject_id` registry in `references/authoring-subject-registry.json`; grade ids `pre_k`, `k`, `1` through `12`, `postsecondary`, `ungraded`; `expected_xp_band=0_to_10`, `10_to_20`, `20_to_45`, `45_plus`; media roles; content reference relationship kinds; curriculum link kinds; and the twelve named catalog facet keys. Treat those as descriptor-returned values, not a table to copy into an app. Economics, history, geography, and civics remain course-grain disciplines under `social_studies`, not separate subject ids. If you see `stimulus`, `lesson`, `lesson_section`, `playlist`, `file`, `passage`, a generic `test` row standing in for a blueprint/bank, or `subtype=script` as a Content kind, treat that as stale or out-of-module wording. Video, audio, image, and script are first-class Content kinds when they are the primary student-touchable asset; captions, transcripts, thumbnails, source files, and supporting files are `media_asset` rows or media/reference sidecars.

## First Actions

1. Fetch or open the canonical docs before making claims:
   - Data dictionary: https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/data_dictionary/
   - Architecture decisions: https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/architecture/
   - Generated current customer contract: https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/customer_website/
   - Content Surface-QC and Integration proof-app routes remain withdrawn status pages, not current validation evidence.
2. Use `CONTENT_BASE_URL` if credentials supply it. Otherwise use:
   `https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/implementation/api`
3. Choose the credential path:
   - Demo: call `POST $CONTENT_BASE_URL/dev/mint?tenantId=demo`, then use the returned bearer token and `tenantId`.
   - Real/reviewer tenant: use the operator-supplied bearer token such as `CONTENT_REVIEWER_JWT`; use the supplied tenant id or the token's `tenantId` claim.
4. Read `GET /` and keep its `allowedFilters`, `allowedValues`, docs links, and behavior contracts next to you.
5. For the teacher request "browse Articles by curriculum topic", run the worked example in `references/worked-example.md`.
6. For catalog/library/LOR-style jobs, use `facet=KEY:VALUE` and `/catalog-facets` for stored sidecar truth. For `facet=expected_xp_band:10_to_20` only, runtime also applies the numeric band and vetoes a mismatched stored assignment. The `expected_xp_band=10_to_20` shortcut applies only numeric bounds to `expected_xp` and can match without a stored assignment.
7. Before final output, run `node checks/verify-skill-pack.mjs` and include the pass/fail evidence in your answer or build log.

## The App Builder's Happy Path

For a student-facing app that lets a student browse articles by curriculum topic:

```bash
export CONTENT_BASE_URL="${CONTENT_BASE_URL:-https://platform3-andymontgomery-9773s-projects.vercel.app/content/alpha/implementation/api}"
export CONTENT_TENANT="${CONTENT_TENANT:-demo}"
export CONTENT_TOKEN="${CONTENT_TOKEN:-$(curl -fsS -X POST "$CONTENT_BASE_URL/dev/mint?tenantId=demo" | jq -r '.token')}"
export KC_REF="${KC_REF:-case:math:7.ee.b.4}"

curl -fsS "$CONTENT_BASE_URL/tenants/$CONTENT_TENANT/alpha/content/items?content_kind=article&facet=kc_refs:$KC_REF&limit=10" \
  -H "Authorization: Bearer $CONTENT_TOKEN"
```

Then pick `data[0].content_id` and call:

- `GET /tenants/{tenantId}/alpha/content/items/{contentId}` for the catalog detail.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/student-view` for renderable article blocks.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/media` for attachments.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/references` for embedded or supporting Content rows.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/kc-tags` for weighted links to governed registry KCs.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/curriculum-links` only when reconciling the frozen historical archive; never author through it. Its rows and list filter have no automatic TTL, but receive no new placements.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/catalog-facets` to show discovery sidecars such as `expected_xp_band`, `quality_rating`, and `kc_refs`.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/trust` for trust status.
- `GET /tenants/{tenantId}/alpha/content/items/{contentId}/effectiveness` only when the UI needs Results-owned validation counts exposed read-only through Content.

Response samples for those calls are in `references/response-samples.md`.

For a library/LOR browse flow, use the same list endpoint with named facet filters:

```bash
curl -fsS "$CONTENT_BASE_URL/tenants/$CONTENT_TENANT/alpha/content/items?content_kind=question&facet=subject:math&facet=grade_band:middle_school&facet=kc_refs:case%3Amath%3A7.ee.b.4&facet=expected_xp_band:10_to_20&limit=10" \
  -H "Authorization: Bearer $CONTENT_TOKEN"
```

## API Perimeter

The public Content Alpha surface exposes these route families:

```text
GET  /
POST /dev/mint?tenantId=demo

GET  /tenants/{tenantId}/alpha/content/items
POST /tenants/{tenantId}/alpha/content/items
GET  /tenants/{tenantId}/alpha/content/items/{contentId}
PATCH /tenants/{tenantId}/alpha/content/items/{contentId}

GET  /tenants/{tenantId}/alpha/content/items/{contentId}/versions
POST /tenants/{tenantId}/alpha/content/items/{contentId}/versions
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/versions/{versionId}
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/versions/{versionId}/release-eligibility?producer_surface={module/surface}
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/student-view
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/answer-slots
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/answer-keys
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/assessment-components
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/blueprint
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/bank-members
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/interactive-composition
PUT  /tenants/{tenantId}/alpha/content/items/{contentId}/interactive-composition
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/scoring-plan

GET  /tenants/{tenantId}/alpha/content/items/{contentId}/media
POST /tenants/{tenantId}/alpha/content/items/{contentId}/media
GET  /tenants/{tenantId}/alpha/content/items/{contentLocator}/media/{mediaLocator}
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/references
POST /tenants/{tenantId}/alpha/content/items/{contentId}/references
DELETE /tenants/{tenantId}/alpha/content/items/{contentId}/references/{referenceId}
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/external-link
POST /tenants/{tenantId}/alpha/content/items/{contentId}/external-link
GET  /tenants/{tenantId}/alpha/content/catalog-facets
POST /tenants/{tenantId}/alpha/content/catalog-facets
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/catalog-facets
POST /tenants/{tenantId}/alpha/content/items/{contentId}/catalog-facets

GET  /tenants/{tenantId}/alpha/content/items/{contentId}/kc-tags
POST /tenants/{tenantId}/alpha/content/items/{contentId}/kc-tags
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/curriculum-links
POST /tenants/{tenantId}/alpha/content/items/{contentId}/curriculum-links  # always 410; frozen archive
POST /tenants/{tenantId}/alpha/content/exports/common-cartridge
GET  /tenants/{tenantId}/alpha/content/exports/common-cartridge/{exportId}/download
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/effectiveness
GET  /tenants/{tenantId}/alpha/content/items/{contentId}/trust

POST /tenants/{tenantId}/alpha/content/imports/timeback-production
POST /tenants/{tenantId}/alpha/content/imports/qti-package
GET  /tenants/{tenantId}/alpha/content/trust-status
GET  /tenants/{tenantId}/alpha/content/policies
```

Use this skill pack for the operating workflow, response shapes, and leak gate. Use the generated data dictionary for field-level provenance, the generated architecture for approved decisions, and the descriptor-generated customer contract for the complete current operation catalog. Do not treat Content Surface-QC or Integration proof-app routes as current evidence.

Article bodies are authored as versions: create or find the metadata item, then `POST /tenants/{tenantId}/alpha/content/items/{contentId}/versions` with renderable `student_view.blocks`. Do not send top-level `blocks`, `body`, or `composition` fields to `POST/PATCH /items`; item writes are metadata-only and reject them.

`POST /imports/qti-package` resolves referenced QTI `qti-assessment-stimulus` / `assessmentStimulus` resources into each question's `student_view.stimuli[]` and prompt HTML, and attaches referenced images as question media delivery URLs. It creates no public `stimulus` or workaround `article` row. Players render the question student view directly; they do not stitch Content-reference rows. A stimulus-only package is rejected because it contains no assessment item/test; authored article bodies still belong in `student_view.blocks`.

## Error Handling

Ordinary authentication, authorization, validation, resource, and server failures are Problem JSON. Preserve `status`, `code`, `detail`, `requestId`, `traceId`, and `fieldErrors` in diagnostics. Idempotency claim state is the explicit exception: an active claim returns a `202` status envelope and an expired unfinished claim returns a terminal `503` status envelope; neither envelope is Problem JSON or carries a stable Content problem code.

Common examples:

- No token: `401` with `code: "content.authentication_required"`.
- Route tenant and token tenant disagree: `403` with `code: "content.tenant_mismatch"`.
- Unsupported query parameter: `400` with `code: "content.unsupported_filter"` and a `fieldErrors[]` entry naming the parameter.
- Malformed `facet=KEY:VALUE`, unknown catalog facet key, or invalid facet value: `400` with `code: "content.validation_failed"` and a `fieldErrors[]` entry naming the invalid field.
- Student token asking for answer keys: `403` with `code: "content.answer_key_forbidden"`.
- Missing row: `404` with a Content not-found code.
- Stale `If-Match`: `409` with the stale-version/problem code.
- Exact version is not release-eligible: `409 content.version_not_serveable`.
- A graph write would exceed 1,024 affected banks, 1,024 active members in one bank, or 4,096 joined question/KC facts: `409 content.invalid_test_bank_member_contract`. Preserve `fieldErrors`; retire or split the graph before retrying.

Do not hide Problem JSON or idempotency status envelopes behind broad retry loops. Retry only idempotent network failures or the fourteen ledger-backed writes listed in the canonical-surface reference, using the same `Idempotency-Key` and request. QTI import, TimeBack production import, non-QTI item creation, exact test_spec/test_bank/article/video publication, content-version creation, weighted KC-tag upsert, weighted KC-tag retirement, content-reference creation, and content-reference retirement atomically commit their effects with replay results. Media attach, catalog-facet definition and assignment creation, interactive-composition replacement, and external-link creation can commit the effect and then leave same-key retries terminally failing with the non-Problem `503` status envelope after a crash, so inspect the resource before escalating and never rotate keys blindly. Item metadata `PATCH` uses `If-Match` instead: the server locks and rereads the item in the mutation transaction, so two simultaneous writers using one ETag produce one success and one typed `409` conflict. Re-read after a conflict before deciding whether to retry.

## Non-Negotiables

Treat these as binary rules:

- Content holds blank instruments, media, assessment components, answer slots, scoring-plan provenance, curriculum links, trust evidence, and effectiveness reads.
- Content holds typed interactive article/video composition. QTI continues to own QTI assessment items/tests, components, answer keys, and scoring semantics; it does not own the Content composition. Curriculum references and gates the Content object.
- Content rows reference other Content rows through `/references`; referenced content is never nested or copied into the parent row.
- Active graph writes are bounded and fenced: at most 1,024 affected active banks per write, 1,024 active members per bank, and 4,096 joined question/KC facts per bank. Treat the bounds as inclusive; do not bypass a typed cap-plus-one `409`, and do not rotate an idempotency key after rejection.
- Content alone answers exact-version release eligibility from lifecycle and item revocation; `trust_status=failed` is the single kill switch and the server-owned QC receipt for that exact immutable version is provenance, not a gate (owner ruling #2534). Consume `/release-eligibility`; never duplicate or recompute the verdict in Curriculum.
- Every Content row carries `expected_xp`; display it as student-independent item-scoped Content configuration. Speed-run calibration is the accepted authoring policy, but current runtime mutates the field in place and accepts/publishes finite nonnegative, policy-derived, or default values without version-scoped calibration proof. Awarded XP is Results ledger data.
- Catalog discovery uses the twelve named sidecar facets. Read `/catalog-facets` and use `facet=KEY:VALUE` for stored assignments; do not recompute them in the app. `facet=expected_xp_band:VALUE` is hybrid: it requires the assignment and the numeric `expected_xp` match. `expected_xp_band=VALUE` is numeric-only and is not proof of a sidecar row.
- LOR/library/catalog clones use Content + Curriculum only: learning objects are Content rows, KC/standards alignment comes from Curriculum refs, provenance/quality comes from lineage and facets, and packaging is Common Cartridge export from a Curriculum subtree.
- Student answers, attempts, results, events, mastery, report cards, and gradebook rollups are outside this module.
- Do not infer `content_kind`, subject, grade, test type, mastery gate, media kind, or source family from names, URLs, XML, or file extensions. Read typed fields from the API.
- Do not compute or maintain effectiveness counts or rates. Read `content_effectiveness` from the API.
- Do not extract answer keys for a student-facing app. Only request answer keys for an explicit teacher/authorized job with a token that has `read:answer_key`. The scoring-plan route currently requires only `read:content`; do not treat it as having the same teacher-only authorization boundary.
- Do not parse QTI packages locally. Send real QTI ZIP package bytes to the QTI import endpoint and read the returned Content rows, answer slots, teacher-only `/answer-keys`, assessment components, and trust evidence. Assessment-item resources become `question` rows and assessment-test resources become `test` rows. Send explicit governed `X-Content-Subject-Id`, `X-Content-Target-Grade-Id`, and calibrated `X-Content-Expected-Xp` headers on every import; package metadata is not authority, omission is invalid, and `ungraded` is the target-grade value when Content is intentionally not tied to one grade. Same-key retries are request-exact, including those three values, and QTI writes finalize atomically with the completed replay response: completed imports replay; active imports return `202 status=in_progress` with `Retry-After`; and expired unfinished claims return terminal `503` and never re-run. Reusing the key for a different package/request returns `409`. Recovery never matches Content by source name, title, or time window. A new `Idempotency-Key` creates new Content rows rather than source-identity upserting. A package with neither assessment-item nor assessment-test XML returns a 400 source-package Problem.
- Do not parse `tb-*` HTML to reconstruct interactive cue timestamps, article segment order, or question bindings when `/interactive-composition` exists. Read the typed Content structure instead.
- Do not pre-normalize TimeBack production import rows. Send source-shaped rows to `/imports/timeback-production`; validation, references, and facet materialization are surface-owned. After import, create one Curriculum-owned use for every returned Content identity. On the deployed adapter, every additional item is one targeted child `practice`/`review` component; after verified cutover, each use is one member target. Discover the identities from Curriculum. Deployed UUIDs remain tenant + source_record_id, but the importer binds the canonical source-table owner and returns a typed collision on cross-table reuse; changed canonical bytes append an immutable exact version. It never writes the frozen curriculum-link archive or infers `kc_refs` discovery assignments from source hints. Current kind folding and expected-XP calibration execute a versioned policy bundled with the implementation; a real tenant's `/policies` response may be empty until policy provisioning exists, so do not claim that endpoint is the runtime authority for the adapter.
- Do not treat `test_family_id` as reconciled family identity. It is nullable unconstrained storage, TimeBack test imports leave it null, and Results producer labels have no Content foreign key.
- Do not generate Common Cartridge bundles locally. Request the transport export endpoint and use the returned `download_url` exactly.
- Do not rewrite `media_asset.delivery_url`. Fetch the exact first-party URL returned by the surface with the same bearer token.
- Do not store bearer tokens, student app credentials, or secret values in Content metadata or tags.
- Do not copy enum/value tables into the app. Read allowed values from the service descriptor or hosted data dictionary when needed.
- Do not invent search, result submission, event logging, score calculation, mastery, or gradebook behavior on this surface.

If the user job needs one of those missing primitives, stop and report a surface gap. Do not work around the gap with private code.

## Common Build Pattern

For a Content app or report:

1. Mint or accept credentials.
2. Read the descriptor and canonical docs.
3. List Content with documented typed filters.
4. Read detail, versions, student view, media, curriculum links, effectiveness, trust, and policies as needed.
5. Read `/interactive-composition` for interactive article/video cue or segment bindings; do not reconstruct them from HTML or QTI.
6. Read `/references` when a row embeds or depends on another Content row; do not hydrate references by parsing the parent payload.
7. For catalog browse or LOR-style reports, use named facet filters and `/catalog-facets`; do not maintain a local catalog index.
8. For authoring/publication, set required `expected_xp`, use `Idempotency-Key` on the fourteen ledger-backed operations enumerated in `references/canonical-surface.md`, serialize item metadata writers using `If-Match`, and verify that optional version ids belong to the paired item and tenant before child-resource writes.
9. For QTI-backed material, call the import endpoint with package bytes and read the returned Content/trust response.
10. For source-shaped TimeBack production import jobs, call `/imports/timeback-production`; do not precompute platform-owned normalization or copy the `alpha.policy.content.source_adapter` mappings into the app. After import, verify the frozen curriculum-link archive and `kc_refs` discovery assignments were unchanged, create placement in Curriculum, preserve typed cross-table source-record collisions, and treat changed-byte re-import as immutable exact-version append.
11. For packaging, call Common Cartridge export and use the returned `download_url`; do not inspect or rebuild package internals unless the user is explicitly testing the transport bytes.
12. Present only facts returned by the surface, with links to the data dictionary or architecture for interpretation.
13. Run `node checks/verify-skill-pack.mjs` or the manual leak checklist before final output.

## Reference Files

- Read `references/worked-example.md` for the article-by-curriculum app flow and QTI question probe.
- Read `references/response-samples.md` for live demo JSON response shapes.
- Read `references/canonical-surface.md` for endpoint, credential, header, scope, status, boundary, and field details.
- Read `references/app-build-protocol.md` when building a student launcher, teacher authoring tool, trust dashboard, or QA probe.
- Read `references/leak-check.md` before shipping an app, report, or deliverable.
- Run `checks/verify-skill-pack.mjs` to prove the pack still passes static no-leak checks and live surface checks.

## Expected Output Quality

Every output should make platform3 more useful than a generic agent:

- It should run against the live Content API, not a mock.
- It should include exact API calls and response evidence.
- It should name which canonical docs it used.
- It should preserve Problem JSON when calls fail.
- It should disclose any missing surface primitive instead of hiding it.
