render
Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals.
Platform3 / Content / Alpha
The public Content contract is generated from tracked sources. The owning data dictionary defines the value space; locked ITDs explain the architectural decisions.
Load-bearing contract
questiontesttest_spectest_bankarticlevideoaudioimageinteractivemedia_assetexternal_linkscriptOwner: Content Alpha data dictionary. Decision: content-itd-003-content-kind-taxonomy.
Every Content rule must visibly serve at least one of three customer jobs. Each ITD below carries one or more of these tags in its Trace section.
Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals.
Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
One row per asset; references never nest another Content payload. QTI-owned facts remain QTI-backed read views. Content adds only new facts and real relationships through extend-only sidecars.
Content is one row per asset. A Content row references other Content rows through content_ref/content_reference rows and never nests another Content row or stores collection membership.
SQL views over the approved qti.* and platform.* base tables. Rename, cut, and restrict add no storage.
Tables that contain only new Alpha columns plus foreign keys to base rows. No extension table may copy a 1EdTech-sourced field.
Content may read Results-owned public views, but Content never creates, migrates, writes, or backfills those objects.
Writes that create or change QTI-backed core objects are write-through commands to the QTI 1EdTech base surface. Alpha does not own a parallel writable content catalog.
Any CREATE TABLE alpha.content_item, alpha.content_version, alpha.answer_slot, alpha.answer_key, alpha.assessment_component, alpha.scoring_plan, alpha.video, alpha.audio, alpha.image, or alpha.media_asset that stores qti.* columns is a rollback failure.
| Job | Owning primitive | Meaning |
|---|---|---|
| Source/import identity | adapter/source-table context + content_item.source_record_id (source_identifier is provenance/export spelling only) | Accepted complete source identity. Current TimeBack import stores a generic external_content_import source_system and omits source_table from deterministic ids, so same record ids from different tables can collide until scoped identity lands. |
| Discovery provenance and quality | content_catalog_facet_assignment (facet_key=provenance|quality_rating) | Stores governed catalog discovery values and safe assignment provenance without copying the base asset. |
| Validation trust evidence | content_trust_evidence | Links the Content identity to the owning conformance or validation evidence and its checked status. |
| Asset-to-source-asset production lineage | content_reference (relationship_kind=source_file_for|source_script_for) | Relates a produced Content asset to the exact reusable Content asset/version that sourced it. |
Student-independent XP value the platform expects a focused student to earn from this content when speed-running it successfully.
Constraint: Required for every Content row and must be a finite number greater than or equal to 0. The accepted authoring policy sets it by speed-running or an approved calibration workflow, never by averaging student actuals. Current runtime does not store or require calibration proof before publication, and import/seed paths can use policy-derived or default values. This value is Content config; awarded_xp is computed per attempt and stored in the Results XP ledger.
ACCEPTED TARGET (immutable ingest and exact article publication implemented; launch-resolution pending): Restrict Content authoring to immutable versions and pin launch resolution. Friendly QTI-backed drafts and student views are read views over qti.artifact_version.authoring_json and qti.artifact_version.delivery_json. Non-QTI render metadata is stored only as Alpha extension columns keyed to the base artifact/version. Item-metadata PATCH uses If-Match; ordinary version creation uses Idempotency-Key and appends. Governed article publication names and validates the current exact immutable version, refreshes server-owned positive item trust for that validation, and appends one receipt for only that version; a successor version never inherits the prior receipt. TimeBack canonicalizes source-table identity, binds each new exact version UUIDv5 to tenant + source_record_id + canonical content hash, adopts a deployed legacy :v1 row only when its complete immutable projection matches, and appends changed bytes with the next version_number and supersedes_version_id under an item lock. Database guards reject UPDATE and DELETE of stored exact versions; an identical or historical hash replay reuses its exact row and never rolls the served pointer backward. Content copies no qti.artifact_version into Alpha storage. Consumers GET an exact version at /items/{contentId}/versions/{versionId} and read its student_view field; the runtime has no separate /versions/{versionId}/student-view subroute. The accepted target is that, when a Curriculum member references only content_id, Content resolves latest_published_version_id at launch time, returns resolved_content_version_id, and lets Events pin it. Current runtime instead exposes raw latest_version_id and returns version_id; it does not yet implement the named latest-published resolver, resolved_content_version_id, or Events launch pin.
Every Alpha name is a tracked rename/restriction over an upstream source, an explicit Content extension, or a documented cut. Storage is derived from the change type so a consumer can tell a read-view field from an Alpha sidecar.
| Alpha name | Kind | Change | Source | Storage | Meaning | Owner ITD |
|---|---|---|---|---|---|---|
Content module | module | rename | QTI 1EdTech surface | Read view / projection over named source | Public Alpha module name for things students touch. | content-itd-001-module-boundary-and-alias-map |
content_item.content_id | field | rename | qti.artifact.artifact_id | Read view / projection over named source | content-itd-002-shared-qti-truth-no-content-fork | |
content_item.workspace_id | field | rename | platform.tenant.tenant_id | Read view / projection over named source | content-axis-009-tenant-routing | |
content_item.content_kind | field | restrict | View expression over qti.artifact.artifact_kind and qti.package_resource.resource_type, plus extension-only kind for QTI-anchored Content sidecars | Read view / projection over named source | content-itd-003-content-kind-taxonomy | |
content_item.expected_xp | field | extend | alpha.content_item_extension.expected_xp | Content-owned extend-only sidecar | content-itd-015-expected-xp-content-config | |
question | object | rename | qti.artifact where artifact_kind = item | Read view / projection over named source | content-itd-003-content-kind-taxonomy | |
test | object | rename | qti.artifact where artifact_kind = test | Read view / projection over named source | content-itd-003-content-kind-taxonomy | |
article | object | extend | alpha.content_item_extension | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
video | object | extend | alpha.content_item_extension plus alpha.media_asset_extension | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
audio | object | extend | alpha.content_item_extension plus alpha.media_asset_extension | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
image | object | extend | alpha.content_item_extension plus alpha.media_asset_extension | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
interactive | object | extend | alpha.content_item_extension | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
media_asset | object | extend | alpha.media_asset_extension | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
external_link | object | extend | alpha.external_link_extension | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
script | object | extend | qti.artifact base identity plus alpha.content_item_extension content_kind_override=script | Content-owned extend-only sidecar | First-class script Content row; produced audio/video links to it with content_reference relationship_kind=source_script_for. | content-itd-004-non-qti-media-extension |
content_item.title | field | rename | qti.artifact.title | Read view / projection over named source | content-itd-001-module-boundary-and-alias-map | |
content_item.language | field | rename | qti.artifact.language | Read view / projection over named source | content-itd-001-module-boundary-and-alias-map | |
content_item.source_identifier | field | rename | qti.artifact.qti_identifier | Read view / projection over named source | content-itd-001-module-boundary-and-alias-map | |
content_item.latest_version_id | field | rename | qti.artifact.latest_version_id | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.version_id | field | rename | qti.artifact_version.artifact_version_id | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.version_number | field | rename | qti.artifact_version.version_number | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.student_view | field | rename | qti.artifact_version.delivery_json | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.editing_draft | field | rename | qti.artifact_version.authoring_json | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.canonical_xml | field | rename | qti.artifact_version.canonical_xml | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
media_asset.media_id | field | rename | qti.package_file.package_file_id | Read view / projection over named source | content-itd-004-non-qti-media-extension | |
media_asset.media_kind | field | extend | alpha.media_asset_extension.media_kind | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
media_asset.media_type | field | rename | qti.package_file.media_type | Read view / projection over named source | content-itd-004-non-qti-media-extension | |
media_asset.byte_length | field | rename | qti.package_file.byte_length | Read view / projection over named source | content-itd-004-non-qti-media-extension | |
external_link.external_url | field | extend | alpha.external_link_extension.external_url | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
answer_slot.identifier | field | rename | qti.variable_declaration.identifier where variable_kind = response | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
answer_slot.cardinality | field | rename | qti.variable_declaration.cardinality | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
answer_slot.base_type | field | rename | qti.variable_declaration.base_type | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
answer_key.correct_response | field | restrict | qti.variable_declaration.correct_response | Read view / projection over named source | Teacher/authorized read only, never a student answer. | content-itd-008-answer-slots-not-student-answers |
assessment_component.component_role | field | rename + restrict | qti.component.element_name mapped to test/section/part/question/question_ref/distractor/rubric/preauthored_feedback | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_component.display_text | field | rename + restrict | qti.component.text_value | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_test.test_type | field | extend | alpha.assessment_test_extension.test_type | Content-owned extend-only sidecar | content-itd-006-typed-content-and-test-fields | |
assessment_test.is_mastery_gate | field | extend | alpha.assessment_test_extension.is_mastery_gate | Content-owned extend-only sidecar | content-itd-006-typed-content-and-test-fields | |
assessment_test.test_family_id | field | extend | alpha.assessment_test_extension.test_family_id | Content-owned extend-only sidecar | Optional opaque adapter join key under the current API/database contract; no lexical grammar is enforced. It should remain stable across content versions, forms, and retakes and must not contain secrets. | content-itd-010-external-test-family-identity |
content_item.subject_id | field | extend | alpha.content_item_extension.subject_id | Content-owned extend-only sidecar | content-itd-006-typed-content-and-test-fields | |
content_item.target_grade_id | field | extend | alpha.content_item_extension.target_grade_id | Content-owned extend-only sidecar | content-itd-006-typed-content-and-test-fields | |
content_curriculum_link.curriculum_node_id | field | extend | alpha.content_curriculum_link.curriculum_node_id referencing curriculum.node | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_ref.content_id | reference-contract-field | rename | qti.artifact.artifact_id | Read view / projection over named source | content-itd-016-uniform-content-reference-contract | |
content_ref.content_version_id | reference-contract-field | rename | qti.artifact_version.artifact_version_id | Read view / projection over named source | content-itd-016-uniform-content-reference-contract | |
content_ref.content_kind | reference-contract-field | restrict | View expression over qti.artifact.artifact_kind and qti.package_resource.resource_type plus extension sidecar kind | Read view / projection over named source | content-itd-016-uniform-content-reference-contract | |
content_reference.parent_content_id | reference-contract-field | extend | alpha.content_reference.parent_content_artifact_id -> alpha.content_reference_view.parent_content_id | Content-owned extend-only sidecar | The parent half of content_ref is three flat columns: id, optional version id, and kind. | content-itd-016-uniform-content-reference-contract |
content_reference.parent_version_id | reference-contract-field | extend | alpha.content_reference.parent_content_version_id -> alpha.content_reference_view.parent_version_id | Content-owned extend-only sidecar | Nullable exact-version member of the parent content_ref shape. | content-itd-016-uniform-content-reference-contract |
content_reference.parent_content_kind | reference-contract-field | extend | alpha.content_reference.parent_content_kind | Content-owned extend-only sidecar | Governed twelve-kind discriminator in the parent content_ref shape. | content-itd-016-uniform-content-reference-contract |
content_reference.referenced_content_id | reference-contract-field | extend | alpha.content_reference.referenced_content_artifact_id -> alpha.content_reference_view.referenced_content_id | Content-owned extend-only sidecar | The referenced half of content_ref is three flat columns: id, optional version id, and kind. | content-itd-016-uniform-content-reference-contract |
content_reference.referenced_version_id | reference-contract-field | extend | alpha.content_reference.referenced_content_version_id -> alpha.content_reference_view.referenced_version_id | Content-owned extend-only sidecar | Nullable exact-version member of the referenced content_ref shape. | content-itd-016-uniform-content-reference-contract |
content_reference.referenced_content_kind | reference-contract-field | derive | alpha.content_reference_view.referenced_content_kind derived from referenced qti.artifact + alpha.content_item_extension | See named source and owner ITD | Read-only governed twelve-kind discriminator; no alpha.content_reference table column exists. | content-itd-016-uniform-content-reference-contract |
content_reference.relationship_kind | field | extend | alpha.content_reference.relationship_kind | Content-owned extend-only sidecar | content-itd-016-uniform-content-reference-contract | |
content_item.source_record_id | field | extend | alpha.content_item_extension.source_record_id | Content-owned extend-only sidecar | content-itd-018-source-shaped-production-ingest | |
content_item.source_activity_ref | field | extend | alpha.content_item_extension.source_activity_ref | Content-owned extend-only sidecar | content-itd-018-source-shaped-production-ingest | |
catalog_facet.subject | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for subject | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.grade_band | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for grade_band | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.language | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for language | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.accessibility_profile | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for accessibility_profile | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.rights_license | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for rights_license | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.provenance | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for provenance | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.quality_rating | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for quality_rating | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.kc_refs | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for kc_refs | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.standards_framework_refs | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for standards_framework_refs | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.content_kind | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for content_kind | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.expected_xp_band | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for expected_xp_band | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
catalog_facet.modality | catalog-facet | extend | alpha.content_catalog_facet_assignment value/ref for modality | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_item.subtype | field | extend | alpha.content_item_extension.subtype | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
content_item.dok | field | extend | alpha.content_item_extension.dok | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
decodable.phonics_constraint | field | extend | alpha.decodable_extension.phonics_constraint | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
content_reference.form_index | reference-contract-field | extend | alpha.content_reference.form_index where relationship_kind = bank_member | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test.assessment_role | field | extend | alpha.assessment_test_extension.assessment_role | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec | object | extend | qti.artifact where artifact_kind = test, assessment_role = spec, plus alpha.test_spec_extension | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.kc_coverage | field | extend | alpha.test_spec_extension.kc_coverage | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.passing_rule | field | extend | alpha.test_spec_extension.passing_rule | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.min_forms | field | extend | alpha.test_spec_extension.min_forms | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.max_item_overlap | field | extend | alpha.test_spec_extension.max_item_overlap | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_bank | object | extend | qti.artifact where artifact_kind = test, assessment_role = bank, plus alpha.test_bank_extension | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_bank.spec_id | field | extend | alpha.test_bank_extension.spec_id | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
content_effectiveness.validated_by_count | read-dependency-field | restrict | Results-owned alpha.content_effectiveness.data ->> 'validated_by_count' | Read view / projection over named source | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.failed_by_count | read-dependency-field | restrict | Results-owned alpha.content_effectiveness.data ->> 'failed_by_count' | Read view / projection over named source | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.effectiveness_rate | read-dependency-field | restrict | Results-owned alpha.content_effectiveness.data ->> 'effectiveness_rate' | Read view / projection over named source | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.last_recomputed_at | read-dependency-field | restrict | Results-owned alpha.content_effectiveness.data ->> 'last_recomputed_at' | Read view / projection over named source | content-itd-009-content-effectiveness-derived-rollup | |
content_html_body_contract | object | extend | Virtual Content value-space over qti.artifact_version authoring_json/delivery_json; no Alpha table | Content-owned extend-only sidecar | Owns stored tb-* HTML semantics while QTI, Curriculum, renderers, Events, and Results retain their named seams. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.html | field | extend | qti.artifact_version.authoring_json/delivery_json HTML projection | Content-owned extend-only sidecar | No new Content storage table. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.tb_markup_token | field | extend | Content-owned value-space inside the version HTML body | Content-owned extend-only sidecar | Dictionary contract only; no standalone token table. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.catalog_idref | field | extend | Content HTML body data-catalog-idref attribute | Content-owned extend-only sidecar | Stored only inside the immutable version body, including TimeBack import versions. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.catalog_entry_id | field | extend | Content HTML body local catalog entry id | Content-owned extend-only sidecar | Local to one immutable version body, including TimeBack import versions. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.qti_xml_ref | field | extend | Reference from Content HTML catalog to QTI-owned XML/scoring | Content-owned extend-only sidecar | Reference only; QTI remains the storage owner. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.renderer_ref | field | extend | Curriculum course_component_member.renderer_ref | Content-owned extend-only sidecar | Cross-module reference; renderer selection is not stored on Content. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.renderer_message | field | extend | Sanctioned renderer integration runtime protocol | Content-owned extend-only sidecar | Runtime protocol only; not Content storage. | content-itd-021-tb-markup-body-contract |
content_html_body_contract.progress_event_contract | field | extend | Events/Results learner-progress seam consumed by renderers | Content-owned extend-only sidecar | No renderer-private or Content progress store. | content-itd-021-tb-markup-body-contract |
interactive_composition | object | extend | Content publicInteractiveComposition aggregate over active alpha.interactive_composition rows | Content-owned extend-only sidecar | Public API aggregate over the Content-owned extend-only sidecar; exact physical rows live only in the generated Physical DDL section. | content-itd-022-interactive-composition |
interactive_composition.content_id | field | extend | publicInteractiveComposition.content_id from active alpha.interactive_composition.content_artifact_id rows | Content-owned extend-only sidecar | Base Content identity; no copied artifact fields. | content-itd-022-interactive-composition |
interactive_composition.version_id | field | extend | publicInteractiveComposition.version_id from active alpha.interactive_composition.content_version_id rows | Content-owned extend-only sidecar | content-itd-022-interactive-composition | |
interactive_composition.composition_type | field | extend | publicInteractiveComposition.composition_type from active alpha.interactive_composition rows | Content-owned extend-only sidecar | content-itd-022-interactive-composition | |
interactive_composition.media_asset_ref | field | extend | publicInteractiveComposition media_asset_ref built from active alpha.interactive_composition.media_package_file_id rows | Content-owned extend-only sidecar | Reference only; media bytes remain upstream. | content-itd-022-interactive-composition |
interactive_composition.duration_seconds | field | extend | publicInteractiveComposition.duration_seconds from active alpha.interactive_composition rows | Content-owned extend-only sidecar | content-itd-022-interactive-composition | |
interactive_composition.segments | field | extend | publicInteractiveComposition segments aggregate from active row sequence/segment/question-reference fields | Content-owned extend-only sidecar | API aggregate only; row fields remain in exact Physical DDL. | content-itd-022-interactive-composition |
interactive_composition.cues | field | extend | publicInteractiveComposition cues aggregate from active row timestamp/question-reference fields | Content-owned extend-only sidecar | API aggregate only; referenced questions remain independent QTI-backed Content rows. | content-itd-022-interactive-composition |
interactive_composition.validation_contract | field | derive | publicInteractiveComposition derived validation contract from composition_type and content-itd-022 | See named source and owner ITD | Read-only derived projection; no physical JSON validation-contract column. | content-itd-022-interactive-composition |
interactive_composition.updated_at | field | derive | publicInteractiveComposition latest active row created_at or parent Content updated_at | See named source and owner ITD | Read-only aggregate timestamp; not learner state. | content-itd-022-interactive-composition |
content_item | object | restrict | QTI assessment-item and assessment-test identities feed public question, test, test_spec, and test_bank rows. QTI assessmentStimulus contributes question anatomy only and is filtered from public content_item rows. Articles, videos, audio, images, interactives, media assets, external links, and scripts are anchored to QTI/Common-Cartridge-compatible resource rows where one exists; Alpha stores only new classification and lifecycle facts in sidecars. diagram and decodable are governed subtype values. QTI sections and course/unit/chapter/lesson containers route to Curriculum. | Read view / projection over named source | The catalog row for one student-touchable thing, across versions. | content-itd-001-module-boundary-and-alias-map |
content_item.usage_scope | field | extend | alpha.content_item_extension.usage_scope | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
content_item.source_system | field | extend | source_system | Content-owned extend-only sidecar | content-itd-001-module-boundary-and-alias-map | |
content_item.status | field | extend | status | Content-owned extend-only sidecar | content-itd-007-versioned-authoring-and-student-view | |
content_item.created_at | field | rename | qti.artifact.created_at | Read view / projection over named source | content-axis-007-eventing-model | |
content_item.updated_at | field | extend | updated_at | Content-owned extend-only sidecar | content-axis-007-eventing-model | |
content_item.retired_at | field | extend | retired_at | Content-owned extend-only sidecar | content-axis-011-privacy-retention | |
content_version | object | restrict | QTI-backed versions alias qti.artifact_version. Non-QTI authoring and TimeBack source ingest use the same append-only version target while keeping QTI-owned fields in QTI base rows. | Read view / projection over named source | One immutable exact edition of a content item. | content-itd-007-versioned-authoring-and-student-view |
content_version.content_id | field | rename | qti.artifact_version.artifact_id | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.content_hash | field | rename + extend | qti.artifact_version.xml_hash | Content-owned extend-only sidecar | content-axis-004-concurrency-model | |
content_version.supersedes_version_id | field | rename | qti.artifact_version.supersedes_version_id | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.etag | field | extend | etag | Content-owned extend-only sidecar | content-axis-004-concurrency-model | |
content_version.created_at | field | rename | qti.artifact_version.created_at | Read view / projection over named source | content-itd-007-versioned-authoring-and-student-view | |
content_version.created_by | field | rename | qti.artifact_version.created_by | Read view / projection over named source | content-axis-011-privacy-retention | |
media_asset.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
media_asset.version_id | field | extend | version_id | Content-owned extend-only sidecar | content-itd-007-versioned-authoring-and-student-view | |
media_asset.content_hash | field | rename | qti.package_file.content_hash | Read view / projection over named source | content-itd-004-non-qti-media-extension | |
media_asset.storage_path | field | rename | qti.package_file.package_path | Read view / projection over named source | content-itd-004-non-qti-media-extension | |
media_asset.delivery_url | field | extend | delivery_url | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
media_asset.metadata | field | rename | qti.package_file.metadata | Read view / projection over named source | content-axis-011-privacy-retention | |
media_asset.created_at | field | rename | qti.package_file.created_at | Read view / projection over named source | content-itd-004-non-qti-media-extension | |
external_link.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
external_link.display_url | field | extend | display_url | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
external_link.open_policy | field | extend | open_policy | Content-owned extend-only sidecar | content-itd-004-non-qti-media-extension | |
external_link.last_validated_at | field | extend | last_validated_at | Content-owned extend-only sidecar | content-axis-010-conformance-evidence | |
decodable | object | extend | QTI/Common Cartridge can store the article resource, but the phonics constraint set is an Alpha-owned fact needed to render and discover decodable text. | Content-owned extend-only sidecar | Phonics-constraint sidecar for content_item rows where content_kind='article' and subtype='decodable'. | content-itd-019-governed-subtypes-within-ten-kinds |
decodable.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
decodable.constraint_set_id | field | extend | constraint_set_id | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
decodable.policy_version | field | extend | policy_version | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
decodable.updated_at | field | extend | updated_at | Content-owned extend-only sidecar | content-axis-007-eventing-model | |
test.content_id | field | rename | qti.artifact.artifact_id | Read view / projection over named source | content-itd-020-test-spec-and-test-bank | |
test.test_type | field | extend | alpha.assessment_test_extension.test_type | Content-owned extend-only sidecar | content-itd-006-typed-content-and-test-fields | |
assessment_test | object | extend | QTI stores the assessment test XML; Alpha adds the typed fields teachers and LLMs need without parsing names or producer URLs. content-itd-020 adds first-class public test_spec/test_bank kinds with role-specific sidecars over this same QTI shape. | Content-owned extend-only sidecar | Typed Alpha fields for QTI-backed test, test_spec, and test_bank rows. | content-itd-019-governed-subtypes-within-ten-kinds |
assessment_test.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-006-typed-content-and-test-fields | |
assessment_test.assessment_role | field | extend | assessment_role | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
test_spec.spec_id | field | extend | alpha.test_spec_extension.spec_id | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.assessment_role | field | extend | assessment_role | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
test_spec.item_type_mix | field | extend | alpha.test_spec_extension.item_type_mix | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.difficulty_constraints | field | extend | alpha.test_spec_extension.difficulty_constraints | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.enemy_item_rule | field | extend | alpha.test_spec_extension.enemy_item_rule | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_spec.updated_at | field | extend | updated_at | Content-owned extend-only sidecar | content-axis-007-eventing-model | |
test_bank.bank_id | field | extend | alpha.test_bank_extension.bank_id | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_bank.assessment_role | field | extend | assessment_role | Content-owned extend-only sidecar | content-itd-019-governed-subtypes-within-ten-kinds | |
test_bank.membership_rule | field | extend | alpha.test_bank_extension.membership_rule | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
test_bank.updated_at | field | extend | updated_at | Content-owned extend-only sidecar | content-axis-007-eventing-model | |
content_curriculum_link | object | extend | CASE/Content supplies curriculum node identity; Alpha Content owns only the coverage relationship. | Content-owned extend-only sidecar | A real relationship from Content to the shared curriculum graph. | content-itd-005-curriculum-links-are-relationships |
content_curriculum_link.content_curriculum_link_id | field | extend | content_curriculum_link_id | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_curriculum_link.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_curriculum_link.version_id | field | extend | version_id | Content-owned extend-only sidecar | content-itd-007-versioned-authoring-and-student-view | |
content_curriculum_link.link_kind | field | extend | link_kind | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_curriculum_link.active_from | field | extend | active_from | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_curriculum_link.active_until | field | extend | active_until | Content-owned extend-only sidecar | content-axis-011-privacy-retention | |
content_catalog_facet | object | extend | QTI metadata is not used as Alpha extension storage. The facet sidecar follows the tag-vs-column rule: governed, low-cardinality discovery dimensions can live here until they deserve real columns. | Content-owned extend-only sidecar | Governed catalog-facet definition for discovery metadata. | content-itd-014-catalog-facets-sidecar |
content_catalog_facet.facet_key | field | extend | facet_key | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet.label | field | extend | label | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet.value_type | field | extend | value_type | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet.grain | field | extend | grain | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet.allowed_values | field | extend | allowed_values | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet.status | field | extend | status | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet.updated_at | field | extend | updated_at | Content-owned extend-only sidecar | content-axis-007-eventing-model | |
content_catalog_facet_assignment | object | extend | The assignment table is the Content-specific case of the governed tag registry. Relationships remain real columns; discovery-only facets live here until promotion. | Content-owned extend-only sidecar | Assignment of one governed catalog facet value to Content. | content-itd-014-catalog-facets-sidecar |
content_catalog_facet_assignment.facet_assignment_id | field | extend | facet_assignment_id | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet_assignment.facet_key | field | extend | facet_key | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet_assignment.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet_assignment.version_id | field | extend | version_id | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet_assignment.media_id | field | extend | media_id | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet_assignment.facet_value | field | extend | facet_value | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet_assignment.provenance | field | extend | provenance | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_catalog_facet_assignment.retired_at | field | extend | retired_at | Content-owned extend-only sidecar | content-itd-014-catalog-facets-sidecar | |
content_ref | object | extend | Content exposes one reference shape so Curriculum course_component_member.target_ref and catalog/LOR workflows can point at any Content kind without special-casing. | Content-owned extend-only sidecar | The one reference shape for every Content kind. | content-itd-016-uniform-content-reference-contract |
content_reference | object | extend | QTI package resources can depend on other resources, but Alpha pins one relationship row shape for every Content kind so renderers and Common Cartridge export do not special-case question, test, test_spec, test_bank, article, video, audio, image, interactive, media_asset, external_link, or script. | Content-owned extend-only sidecar | A stored relationship from one Content row to another using the uniform content_ref contract. | content-itd-016-uniform-content-reference-contract |
content_reference.content_reference_id | field | extend | content_reference_id | Content-owned extend-only sidecar | content-itd-016-uniform-content-reference-contract | |
content_reference.display_order | field | extend | display_order | Content-owned extend-only sidecar | content-itd-016-uniform-content-reference-contract | |
content_reference.usage_scope | field | extend | alpha.content_reference.usage_scope | Content-owned extend-only sidecar | content-itd-020-test-spec-and-test-bank | |
content_reference.referenced_usage_scope | field | derive | alpha.content_reference_view.referenced_usage_scope | See named source and owner ITD | content-itd-020-test-spec-and-test-bank | |
content_reference.created_at | field | extend | created_at | Content-owned extend-only sidecar | content-itd-016-uniform-content-reference-contract | |
content_reference.retired_at | field | extend | retired_at | Content-owned extend-only sidecar | content-itd-016-uniform-content-reference-contract | |
answer_slot | object | restrict | Only QTI response declarations are exposed as answer slots; outcome/template/context declarations are internal unless used by scoring_plan provenance. | Read view / projection over named source | QTI response declaration exposed in plain language. | content-itd-008-answer-slots-not-student-answers |
answer_slot.answer_slot_id | field | rename | qti.variable_declaration.variable_declaration_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
answer_slot.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
answer_slot.version_id | field | rename | qti.variable_declaration.artifact_version_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
answer_slot.source_trace | field | rename | qti.variable_declaration.source_trace | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
answer_key | object | restrict | QTI correct_response is preserved but restricted to authorized reads. It is not exposed to ordinary student content reads. | Read view / projection over named source | Teacher-only correct response and mapping for an answer slot. | content-itd-008-answer-slots-not-student-answers |
answer_key.answer_slot_id | field | extend | answer_slot_id | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
answer_key.mapping | field | restrict | qti.variable_declaration.mapping | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
answer_key.required_scope | field | extend | required_scope | Content-owned extend-only sidecar | content-axis-006-auth-shape | |
assessment_component | object | restrict | QTI component elements remain the source. Alpha publishes school-language component_role values for the parts of the blank assessment that apps need to render or audit. | Read view / projection over named source | Sections, parts, choices, rubrics, and pre-authored feedback from the QTI component tree. | content-itd-008-answer-slots-not-student-answers |
assessment_component.assessment_component_id | field | rename | qti.component.component_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_component.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
assessment_component.version_id | field | rename | qti.component.artifact_version_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_component.parent_assessment_component_id | field | rename | qti.component.parent_component_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_component.ordinal | field | rename | qti.component.ordinal | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_component.element_name | field | rename | qti.component.element_name | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_component.qti_identifier | field | rename | qti.component.qti_identifier | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
assessment_component.response_identifier | field | extend | response_identifier | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
assessment_component.outcome_identifier | field | extend | outcome_identifier | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
assessment_component.feedback_identifier | field | extend | feedback_identifier | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
assessment_component.show_hide | field | extend | show_hide | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
assessment_component.source_trace | field | rename | qti.component.source_trace | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan | object | restrict | QTI processing rules remain the source. Alpha exposes enough provenance to explain the blank scoring plan without becoming Results. | Read view / projection over named source | QTI processing provenance for scoring a content version. | content-itd-008-answer-slots-not-student-answers |
scoring_plan.scoring_plan_id | field | rename | qti.processing_rule.processing_rule_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.version_id | field | rename | qti.processing_rule.artifact_version_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.parent_scoring_plan_id | field | rename | qti.processing_rule.parent_processing_rule_id | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.rule_scope | field | rename | qti.processing_rule.rule_scope | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.rule_name | field | rename | qti.processing_rule.rule_name | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.sequence_number | field | rename | qti.processing_rule.sequence_number | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.operands | field | rename | qti.processing_rule.operands | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
scoring_plan.source_trace | field | rename | qti.processing_rule.source_trace | Read view / projection over named source | content-itd-008-answer-slots-not-student-answers | |
content_effectiveness | object | derive | Content-effectiveness fields are derived from settled Results evidence. Content exposes them beside the content item as a read dependency, not as Content-owned storage. | See named source and owner ITD | Results-owned derived effectiveness read for one content item. | content-itd-009-content-effectiveness-derived-rollup |
content_effectiveness.content_id | read-dependency-field | derive | alpha.content_effectiveness.data ->> 'content_id' | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.content_version_id | read-dependency-field | derive | alpha.content_effectiveness.data ->> 'content_version_id' | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.kc_id | read-dependency-field | derive | alpha.content_effectiveness.data ->> 'kc_id' | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.subject_id | read-dependency-field | derive | alpha.content_effectiveness.data ->> 'subject_id' | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.evidence_count | read-dependency-field | derive | alpha.content_effectiveness.data ->> 'evidence_count' | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.freshness_status | read-dependency-field | derive | GET /items/{contentId}/effectiveness fallback over alpha.content_effectiveness | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.policy_version | read-dependency-field | derive | alpha.content_effectiveness.data ->> 'policy_version' | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.source_result_refs | read-dependency-field | derive | alpha.content_effectiveness.data -> 'source_result_refs' | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_effectiveness.provenance | read-dependency-field | derive | alpha.content_effectiveness.data | See named source and owner ITD | content-itd-009-content-effectiveness-derived-rollup | |
content_trust_evidence | object | extend | Current runtime exposes generic producer/import trust metadata or unknown/null native trust. The accepted target binds QTI-backed content to an exact QTI conformance run and non-QTI media to a named Content validation check. | Content-owned extend-only sidecar | Read-only trust status and validation provenance for Content. | content-axis-010-conformance-evidence |
content_trust_evidence.content_id | field | extend | content_id | Content-owned extend-only sidecar | content-axis-010-conformance-evidence | |
content_trust_evidence.trust_status | field | extend | trust_status | Content-owned extend-only sidecar | content-axis-010-conformance-evidence | |
content_trust_evidence.evidence_url | field | extend | evidence_url | Content-owned extend-only sidecar | content-axis-010-conformance-evidence | |
content_trust_evidence.checked_at | field | extend | checked_at | Content-owned extend-only sidecar | content-axis-010-conformance-evidence | |
content_trust_evidence.problem_code | field | extend | problem_code | Content-owned extend-only sidecar | content-itd-012-plain-language-problems-and-provenance | |
content_policy | object | restrict | The runtime exposes alpha.content_policy_view where rows exist, while source import executes a bundled JSON policy. A future shared alpha.policy authority must replace—not be inferred from—this current arrangement. | Read view / projection over named source | Current Content policy projection, including module-local rows and the bundled versioned source-adapter policy. | content-itd-009-content-effectiveness-derived-rollup |
content_policy.policy_key | field | extend | policy_key | Content-owned extend-only sidecar | content-itd-009-content-effectiveness-derived-rollup | |
content_policy.value_json | field | extend | value_json | Content-owned extend-only sidecar | content-itd-009-content-effectiveness-derived-rollup | |
content_policy.policy_version | field | extend | policy_version | Content-owned extend-only sidecar | content-itd-009-content-effectiveness-derived-rollup | |
content_policy.updated_at | field | extend | updated_at | Content-owned extend-only sidecar | content-itd-009-content-effectiveness-derived-rollup | |
content_problem | object | restrict | The envelope comes from Platform. Content adds the exact stable code and field contract emitted by implementation/impl/src/errors.mjs. | Read view / projection over named source | Stable problem codes and field errors returned by Content APIs. | content-itd-012-plain-language-problems-and-provenance |
content_problem.type | field | extend | type | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.title | field | extend | title | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.status | field | extend | status | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.code | field | extend | code | Content-owned extend-only sidecar | content-itd-012-plain-language-problems-and-provenance | |
content_problem.detail | field | extend | detail | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.requestId | field | extend | requestId | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.traceId | field | extend | traceId | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.cause | field | extend | cause | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.docUrl | field | extend | docUrl | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_problem.fieldErrors | field | extend | fieldErrors | Content-owned extend-only sidecar | content-axis-008-error-envelope | |
content_kc_tag | object | extend | The Curriculum registry supplies kc_id identity; Content owns only the mapping from its stable Content identity to that registry identity. | Content-owned extend-only sidecar | The canonical weighted many-to-many Q-matrix row from Content to a governed registry KC. | content-itd-005-curriculum-links-are-relationships |
content_kc_tag.tenant_id | field | extend | Resolved from the authenticated route tenant. | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_kc_tag.content_id | field | extend | Taken from the Content item route after tenant-scoped resolution. | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_kc_tag.kc_id | field | extend | Supplied by an authorized Content/KC tagging workflow after registry resolution. | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_kc_tag.weight | field | extend | Asserted by the authorized tagging or review workflow. | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_kc_tag.is_primary | field | extend | Asserted by the authorized tagging or review workflow. | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_kc_tag.provenance | field | extend | Supplied by the authorized tagging workflow. | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_kc_tag.confidence | field | extend | Supplied by the authorized tagging or review workflow. | Content-owned extend-only sidecar | content-itd-005-curriculum-links-are-relationships | |
content_kc_tag.created_at | field | extend | Set by PostgreSQL on initial insert. | Content-owned extend-only sidecar | content-axis-011-privacy-retention | |
content_kc_tag.updated_at | field | extend | Set by PostgreSQL insert/upsert. | Content-owned extend-only sidecar | content-axis-011-privacy-retention | |
content_kc_tag.retired_at | field | extend | Set by a governed retirement workflow; the initial public API writes null. | Content-owned extend-only sidecar | content-axis-011-privacy-retention | |
content_item.source_table | field | extend | alpha.content_item_extension.source_table | Content-owned extend-only sidecar | content-itd-018-source-shaped-production-ingest | |
content_release_eligibility | object | extend | content-itd-023 trust-based exact-version eligibility under owner ruling #2534 (2026-08-05): publication plus integrity makes a version serveable; trust_status=failed is the single post-visibility revocation lever. | Content-owned extend-only sidecar | Current lifecycle and item-revocation answer for one exact Content version. | content-itd-023-trust-based-release-eligibility |
content_release_eligibility.content_id | field | extend | Content base identity. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_release_eligibility.content_version_id | field | extend | Content version identity. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_release_eligibility.content_kind | field | restrict + extend | Content item kind taxonomy. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_release_eligibility.usage_scope | field | extend | Content item routing exposure contract. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_release_eligibility.trust_status | field | extend | Content current item revocation state. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_release_eligibility.serveable | field | extend | Content lifecycle and item revocation state. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_release_eligibility.release_eligible | field | extend | Content lifecycle and item revocation state. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_release_eligibility.problem_code | field | extend | Content problem envelope. | Content-owned extend-only sidecar | content-itd-023-trust-based-release-eligibility | |
content_test_serve_grant | object | extend | content-itd-024-governed-test-serve | Content-owned extend-only sidecar | Soft-revocable demo authorization for one test/synthetic identity. | content-itd-024-governed-test-serve |
content_test_serve_grant.tenant_id | reference-contract-field | restrict | Platform tenant identity. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_grant.test_serve_grant_id | field | extend | Content test-serving policy. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_grant.test_identity_id | reference-contract-field | restrict | People & Orgs identity reference only; no person facts are copied. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_grant.producer_surface | field | extend | Content producer policy. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_grant.authorization_ref | field | extend | Governance provenance. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_grant.expires_at | field | extend | Content test-serving policy. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_grant.revoked_at | field | extend | Content test-serving policy. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_grant.created_at | field | extend | Content audit field. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_authorization | object | extend | content-itd-024-governed-test-serve | Content-owned extend-only sidecar | Immutable exact bank/form test-serve receipt. | content-itd-024-governed-test-serve |
content_test_serve_authorization.tenant_id | reference-contract-field | restrict | Platform tenant identity. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.test_serve_authorization_id | field | extend | Content authorization evidence. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_authorization.test_serve_grant_id | reference-contract-field | restrict | Content grant reference. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.caller_subject | field | extend | Verified JWT subject. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_authorization.test_identity_id | reference-contract-field | restrict | People & Orgs identity reference. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.producer_surface | field | extend | Content producer policy. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_authorization.gate_component_id | reference-contract-field | restrict | Curriculum gate identity. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.bank_content_id | reference-contract-field | restrict | Content bank identity. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.bank_content_version_id | reference-contract-field | restrict | Content version identity. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.form_content_id | reference-contract-field | restrict | Content form identity. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.form_content_version_id | reference-contract-field | restrict | Content version identity. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_authorization.request_id | field | extend | Platform request tracing. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_authorization.trace_id | field | extend | Platform request tracing. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_authorization.authorized_at | field | extend | Content audit field. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_eligibility | object | restrict | content-itd-024-governed-test-serve | Read view / projection over named source | Current fail-closed answer for one exact Content version and one test identity. | content-itd-024-governed-test-serve |
content_test_serve_eligibility.content_id | read-dependency-field | derive | Content identity. | See named source and owner ITD | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.content_version_id | read-dependency-field | derive | Content version identity. | See named source and owner ITD | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.content_kind | read-dependency-field | derive | Content kind. | See named source and owner ITD | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.usage_scope | read-dependency-field | derive | Content usage policy. | See named source and owner ITD | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.producer_surface | field | extend | Content producer policy. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.test_identity_id | read-dependency-field | derive | People & Orgs identity reference. | See named source and owner ITD | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.test_serve_grant_id | reference-contract-field | restrict | Content grant. | Read view / projection over named source | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.test_serve_eligible | field | extend | Content test-serving policy. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve | |
content_test_serve_eligibility.problem_code | field | extend | Content error contract. | Content-owned extend-only sidecar | content-itd-024-governed-test-serve |
This is the complete public Alpha cut list. Every excluded upstream/runtime object has an audience-facing reason and a deep link to the decision that owns the tradeoff.
| Excluded source/object | Change | Audience tradeoff / owning boundary | Owner ITD |
|---|---|---|---|
qti.delivery_session | cut | A delivery session is a learner runtime moment/snapshot. It belongs to Events/Results runtime handling, not Content. | content-itd-011-public-cuts-and-internal-provenance, content-itd-008-answer-slots-not-student-answers |
qti.attempt | cut | Attempts contain student responses and processed outcomes. Content owns the blank answer slots and scoring plan only. | content-itd-011-public-cuts-and-internal-provenance, content-itd-008-answer-slots-not-student-answers |
oneroster.result / oneroster.lineItem | cut | Durable outcomes and gradebook rollups live in Results, not the blank Content module. | content-itd-011-public-cuts-and-internal-provenance, content-itd-008-answer-slots-not-student-answers |
QTI internal artifact kinds: result, usage-data, metadata, response-processing, outcome-declaration, manifest-resource | cut | They remain upstream provenance or generated processing evidence, but they are not ordinary student-touchable Content catalog objects. | content-itd-011-public-cuts-and-internal-provenance, content-itd-003-content-kind-taxonomy |
QTI sections and course/unit/chapter/lesson/lesson_section containers | cut | Instructional containers and sequence structure are Curriculum. Content owns only the student-touchable things referenced from those containers. | content-itd-011-public-cuts-and-internal-provenance, content-itd-003-content-kind-taxonomy |
question bank, playlist, library, course bundle, and collection membership | cut | Collections are Curriculum-side organization of references to Content. Content exposes uniform content_id/version_id references and does not store kind-specific collection tables. | content-itd-011-public-cuts-and-internal-provenance, content-itd-013-lossless-common-cartridge-round-trip |
imscp_v1p1 / Common Cartridge bundle | cut | A bundle is transport output generated on demand from a Curriculum collection plus referenced Content. It is never a stored Content kind. | content-itd-013-lossless-common-cartridge-round-trip, content-itd-011-public-cuts-and-internal-provenance |
student mastery / student knowledge graph | cut | The student's knowledge graph is a Results overlay on Curriculum. Content may carry weighted links to registry KCs but never stores learner mastery state. | content-itd-011-public-cuts-and-internal-provenance, content-itd-005-curriculum-links-are-relationships |
qti.assessmentStimulus as a standalone public Content row | cut | Stimulus remains question anatomy so student/app-builder consumers get one complete question instead of a second asset they must join and launch independently. | content-itd-003-content-kind-taxonomy |
raw QTI package bytes | cut | Raw package bytes remain upstream transport/provenance; Alpha consumers receive governed Content assets and on-demand exports rather than a second stored package object. | content-itd-011-public-cuts-and-internal-provenance, content-itd-013-lossless-common-cartridge-round-trip |
generic qti.component / object-graph browsing | cut | Alpha exposes only the blank assessment-component roles renderers and teachers need; generic graph browsing would force app builders to reconstruct QTI internals and widen unsafe mutation scope. | content-itd-008-answer-slots-not-student-answers, content-itd-011-public-cuts-and-internal-provenance |
standalone qti.variable_declaration / qti.processing_rule row APIs | cut | Authorized consumers receive answer-slot and scoring-plan projections; standalone internals would leak QTI processing vocabulary without improving the blank-instrument job. | content-itd-008-answer-slots-not-student-answers, content-itd-011-public-cuts-and-internal-provenance |
qti.conformance_run mutation and control | cut | The accepted target lets Content consumers read exact-version trust evidence, but current runtime exposes only generic/unknown provenance. Starting or mutating conformance jobs stays with release/QTI tooling to avoid exposing an expensive control surface. | content-axis-010-conformance-evidence, content-itd-011-public-cuts-and-internal-provenance |
submissions, outcome_state, learner processing_trace, AI grading audit, and post-attempt feedback | cut | These are learner-specific runtime/outcome evidence; keeping them in Events/Results gives Content consumers reusable blanks without PII, attempt state, or private gradebook semantics. | content-itd-008-answer-slots-not-student-answers |
Merlot, OER Commons, Khan, IXL, provider library, playlist, and provider question-bank tables | cut | Provider adapters map onto ordinary Content, Curriculum, facets, references, and transport so customers can switch sources without adopting provider-specific platform storage. | content-itd-017-lor-clones-no-specific-storage |
Every fact has exactly one module owner. These six rows explicitly include the four load-bearing boundaries — mastery, knowledge graph, event vs result, and blank instrument vs attempt — plus the Policy/material and roster/enrollment boundaries required by the Alpha consumer job.
| Boundary | Belongs in Content | Routes elsewhere |
|---|---|---|
| Blank instrument vs attempt | Reusable blank questions, tests, specs, banks, student-safe versions, answer slots, and scoring provenance. | Events owns interactions; Results owns attempts, answers, scores, mastery, awarded XP, and settled outcomes. |
| Mastery and gates | Reusable blank mastery tests, specs, banks, and their immutable Content/version identities; TimeBack changed-byte imports append hash-bound exact versions and historical replays never rewrite history or roll back the served pointer. Content owns no learner mastery state, gate threshold, passing criteria, retake cooldown, or on-fail behavior. | Curriculum owns gate placement and member gate fields; versioned alpha.policy owns cutoff and retake formulas; Results owns learner mastery outcomes and state. |
| Knowledge graph and sequence | Item-scoped weighted links from a stable Content identity to active KCs in the Curriculum-owned registry; course placement uses Curriculum content_ref members. | Curriculum owns KCs, standards maps, tracks, courses, components, lessons, ordering, gates, and collections. |
| Event vs result | The immutable asset/version identity that an event or result references. Ordinary authoring and TimeBack source import preserve exact-version history; Events still must pin the resolved version used by the learner. | Events owns timestamped moments; Results owns durable outcomes; Analytics owns derived rollups. |
| Policy vs material | Student-independent expected_xp configuration and policy references attached to the blank asset. | Versioned alpha.policy entries own thresholds and formulas; Results owns awarded/per-attempt XP. |
| Roster and enrollment | Tenant-scoped reusable assets and Content-specific author/publish/read scopes; no school, class, enrollment, or membership rows. The demo-only test-serve grant and immutable receipts store one exact test/synthetic person_id reference, never a profile or real-student runtime fact. | OneRoster is the upstream roster-interchange surface. People & Orgs is the Alpha authority for schools, classes, people, enrollments, memberships, and the live reality/source-status facts Content revalidates for every test-serve authorization; Content stores no copies of those facts. |
Number: 1
ContentObligation: Content uses weighted content_kc_tag relationship rows for registry-KC alignment and typed content_reference rows for content-to-content relationships; content_curriculum_link remains a frozen archive. Typed view/extension columns hold content_kind, expected_xp, subject_id, target_grade_id, media_type, test_type, is_mastery_gate, test_family_id, usage_scope, source_system, and source_record_id. Catalog discovery dimensions live in named governed sidecars and promote to real columns only when they become join, constraint, or computed inputs.
Number: 2
ContentObligation: Content is not roster membership. Authoring and TimeBack ingest use immutable base QTI rows or extension rows keyed to exact versions, and list reads preserve version identity. Content must not expose a 'current only' shortcut that hides which version a student saw; changed source bytes append, and historical hash replay never rolls the served pointer backward.
Number: 3
ContentObligation: Content kind, expected_xp, curated expected_xp_band assignments, subject, grade band, modality, language, test type, mastery gate, usage scope, media kind, media type, source family, source record id, and source activity ref are set on base write-through, source ingest, extension write, or catalog curation and never inferred from titles, URLs, package paths, MIME type, or display text at read time. The documented expected_xp_band= convenience filter is a runtime-owned numeric projection over expected_xp, not evidence of a stored facet assignment.
Number: 4
ContentObligation: Content thresholds and policy lists, including effectiveness freshness, display cutoffs, catalog facet value sets, speed-running calibration rules, and expected_xp banding, live under alpha.policy.* and are not duplicated in apps.
Number: 5
ContentObligation: Content holds blank instruments and media. Events holds interactions. Results holds answers, scores, mastery, and gradebook rollups.
Number: 6
ContentObligation: Content links to Curriculum's shared graph. It never stores the student's knowledge graph or mastery state.
Number: 7
ContentObligation: Content does not carry brand/modality/NWEA account grouping. MAP-related content can carry subject/test family, while district account grouping remains People & Orgs/Results.
Number: 8
ContentObligation: Content metadata, media records, and tags cannot contain student app credentials, raw tokens, or secret values.
Number: 9
ContentObligation: expected_xp is student-independent Content configuration. Speed-running is the accepted calibration policy, but current publication/import paths do not require proof of a speed-run and may persist policy-derived or default values. Awarded XP, repeat-attempt discounts, effort signals, negative XP, and reward triggers live in the Results XP ledger and Policy, never in Content.
Number: 10
ContentObligation: Results owns alpha.content_effectiveness as the derived rollup over settled result evidence. Content exposes a read endpoint that queries the Results-owned public view and safely returns insufficient_data when no Results evidence exists; apps never maintain counters.
Number: 11
ContentObligation: Content owns the TimeBack production content ingest adapter for source-shaped content/activity-reference rows and stores nullable blank test_family_id. Current import does not populate/resolve that family id, Results has no Content family foreign key, and the importer neither appends frozen curriculum-link rows nor mints content_kc_tag rows without an authorized registry resolution workflow. Implemented import failures return HTTP 400 or 422 Problems, never HTTP 200 Problems.
Number: 12
ContentObligation: Content owns the blank spec/bank/forms as the entity home (#72, content-itd-020): a mastery_gate gating member target_ref points at a Content test_bank (curriculum/alpha caitd-013), and 'equivalent form' resolves to the lowest-index unseen bank member whose content_reference.usage_scope and referenced content_item.usage_scope are both practice_eligible; held_out_canary and secure_form remain readable but excluded from routine retries/practice. Gate fields, passing_criteria, on_fail, retake cooldown, and the mastery-cutoff number stay on Curriculum course_component_member and alpha.policy; the spec's passing_rule is referenced, never restated as a number. A grade gate's bank must hold at least the spec's min_forms equivalent forms. Content tests remain reusable blank instruments that may be gated differently in different members.
Number: 13
ContentObligation: 100for100 payouts, league promotions, streak bonuses, penalties, and reward amounts never live on Content rows or catalog facets.
Number: 14
ContentObligation: GOALS reads expected_xp through Content/Curriculum and Results/Policy. Content does not predict hours, calendar dates, RIT targets, or course progress.
Number: 15
ContentObligation: Merlot, OER Commons, Khan Academy library, IXL skill plans, question banks, playlists, and libraries add no clone-specific Content storage; they are recipes over one-row-per-asset Content, content_ref/content_reference rows, Curriculum, catalog facets, and Common Cartridge transport.
Number: 16
ContentObligation: Content items are views or configuration. expected_xp is config. content_effectiveness is a Results-owned derived read. Awarded XP is a Results ledger entry. Course progress is a measure outside Content. Future GOALS output is a prediction outside Content.
Number: 17
ContentObligation: Content computes release_eligible exactly equal to serveable from the owning item lifecycle, item revocation state, and the server-owned QC receipt for the requested immutable version: status=published, retired_at=null, item trust trusted/degraded, and the matching version receipt trusted/degraded. A later version cannot inherit an older receipt. Setting item trust_status=failed is the immediate all-version revocation lever. No producer release evidence, required quality-bar tuple, owner allowlist, or second attestation participates. Curriculum stores only its exact Content reference and asks Content at authoring and launch; consumers never copy or recompute serveable or release_eligible.
Number: 18
ContentObligation: Every active test-bank graph writer budgets its complete post-write result inside the owning transaction under the tenant graph fence: public and repository/direct active embeds and bank_member writes, KC-tag upsert/revival, QTI package import, and TimeBack materialization. A cardinality-neutral active repository/materializer replay still executes this budget; a completed API idempotency replay returns its prior result without executing a graph writer. One write may affect at most 1,024 banks, and every affected bank may have at most 1,024 active bank-member facts and 4,096 joined form/question/active-KC facts. All three ceilings are inclusive. The 1,025th affected bank or active member and the 4,097th joined fact return HTTP 409 content.invalid_test_bank_member_contract; member overflow identifies fieldErrors.name=bank_members, while affected-bank and joined-fact overflow identify fieldErrors.name=form_graph. The rejected transaction leaves no lasting graph, parent timestamp/ETag, or fresh idempotency-ledger effect. Retirement remains available as remediation: retire active member/embed/KC-tag facts or split shared forms/questions/banks to reduce fanout, then retry.
Name: tb_markup_renderer_progress_seam
Owner: Content dictionary with Curriculum renderer_ref and Events/Results progress seams
Tokens: tb-instructional-content; tb-article-container; tb-segment; tb-interaction; tb-qti-assessment-item; tb-applet; tb-catalog; tb-qti-config; tb-applet-config; data-catalog-idref; practice-step; example-practice-container; headers-row; header; step-row; step; example-step; answer-box; tb-glossary-term; tb-glossary-definition; tb-narration; tb-narration-audio
Renderer: {"renderer_ref":"renderer:interactive-lesson","iframe_url":"https://platform3-andymontgomery-9773s-projects.vercel.app/player","playground_url":"https://platform3-andymontgomery-9773s-projects.vercel.app/player?demo=1","implementation_ref":"platform3:first-party-reference-player:/site/player","versioned_iframe_url":"https://platform3-andymontgomery-9773s-projects.vercel.app/player/versions/2026-08-05.issue-2501-article-html/","player_version":"2026-08-05.issue-2501-article-html","capability_matrix_url":"https://platform3-andymontgomery-9773s-projects.vercel.app/player/renderer-capabilities.json","registered_for_routing":true,"verified_rendering":true,"rendering_state":"verified_partial","content_load_fields":["html","qtiXml","testForm","title","contentId","contentRef","gateComponentId","studentId","interactiveComposition","contentState","config"],"host_messages":["CONTENT_LOAD","TTS_GENERATE_RESPONSE"],"renderer_messages":["CONTENT_READY","CONTENT_STATE_CHANGE","CONTENT_RESIZE","CONTENT_ERROR","TTS_GENERATE_REQUEST"],"preview_affordances":["ad_hoc_html","ad_hoc_qti_xml_a1_text_structural","exact_test_form_fixture"]}
Numbered, deep-linked, and status-bearing. Supersession references link directly to the related decision.
| # | Decision | Status | Date | Owner | Supersedes | Superseded by |
|---|---|---|---|---|---|---|
| 1 | content-itd-001-module-boundary-and-alias-mapModule Boundary And Alias Map | Accepted | 2026-06-08 | platform3-loop | none | none |
| 2 | content-itd-002-shared-qti-truth-no-content-forkShared Qti Truth No Content Fork | Accepted | 2026-06-08 | platform3-loop | none | none |
| 3 | content-itd-003-content-kind-taxonomyContent Kind Taxonomy | Accepted | 2026-06-08 | platform3-loop | none | none |
| 4 | content-itd-004-non-qti-media-extensionNon Qti Media Extension | Accepted | 2026-06-08 | platform3-loop | none | none |
| 5 | content-itd-005-curriculum-links-are-relationshipsCurriculum Links Are Relationships | Accepted | 2026-07-16 | andymontgomery-byte | content-itd-016-uniform-content-reference-contract (versioned coverage-link clause); content-itd-017-lor-clones-no-specific-storage (KC-alignment clause); content-itd-022-interactive-composition (content_curriculum_link gating clause) | none |
| 6 | content-itd-006-typed-content-and-test-fieldsTyped Content And Test Fields | Accepted | 2026-06-08 | platform3-loop | none | none |
| 7 | content-itd-007-versioned-authoring-and-student-viewVersioned Authoring And Student View | Accepted Pending Implementation | 2026-06-08 | platform3-loop | none | none |
| 8 | content-itd-008-answer-slots-not-student-answersAnswer Slots Not Student Answers | Accepted | 2026-06-08 | platform3-loop | none | none |
| 9 | content-itd-009-content-effectiveness-derived-rollupContent Effectiveness Derived Rollup | Accepted | 2026-06-08 | platform3-loop | none | none |
| 10 | content-itd-010-external-test-family-identityExternal Test Family Identity | Accepted | 2026-06-08 | platform3-loop | none | none |
| 11 | content-itd-011-public-cuts-and-internal-provenancePublic Cuts And Internal Provenance | Accepted | 2026-06-08 | platform3-loop | none | none |
| 12 | content-itd-012-plain-language-problems-and-provenancePlain Language Problems And Provenance | Accepted | 2026-06-08 | platform3-loop | none | none |
| 13 | content-itd-013-lossless-common-cartridge-round-tripLossless Common Cartridge Round Trip | Accepted Pending Implementation | 2026-06-08 | platform3-loop | none | none |
| 14 | content-itd-014-catalog-facets-sidecarCatalog Facets Sidecar | Accepted | 2026-06-08 | platform3-loop | none | none |
| 15 | content-itd-015-expected-xp-content-configExpected Xp Content Config | Accepted | 2026-06-08 | platform3-loop | none | none |
| 16 | content-itd-016-uniform-content-reference-contractUniform Content Reference Contract | Accepted | 2026-06-08 | platform3-loop | none | content-itd-005-curriculum-links-are-relationships (versioned coverage-link clause only) |
| 17 | content-itd-017-lor-clones-no-specific-storageLor Clones No Specific Storage | Accepted | 2026-06-08 | platform3-loop | none | content-itd-005-curriculum-links-are-relationships (KC-alignment clause only) |
| 18 | content-itd-018-source-shaped-production-ingestSource Shaped Production Ingest | Accepted | 2026-06-08 | platform3-loop | none | none |
| 19 | content-itd-019-governed-subtypes-within-ten-kindsGoverned Subtypes Within Twelve Kinds | Accepted | 2026-06-12 | platform3-loop | none | none |
| 20 | content-itd-020-test-spec-and-test-bankTest Spec And Test Bank | Accepted Pending Implementation | 2026-06-12 | platform3-loop | none | none |
| 21 | content-itd-021-tb-markup-body-contractTb Markup Body Contract | Accepted | 2026-07-03 | platform3-loop | none | content-itd-022-interactive-composition (render-time composition only) |
| 22 | content-itd-022-interactive-compositionInteractive Composition | Accepted | 2026-07-06 | andymontgomery-byte | content-itd-021-tb-markup-body-contract (render-time composition only) | content-itd-005-curriculum-links-are-relationships (content_curriculum_link gating clause only) |
| 23 | content-itd-023-trust-based-release-eligibilityTrust Based Release Eligibility | Accepted | 2026-07-27 | platform3-operator | none | none |
| 24 | content-axis-001-write-granularityContent Api Axis Write Granularity | Accepted | 2026-06-08 | platform3-loop | none | none |
| 25 | content-axis-002-read-shapeContent Api Axis Read Shape | Accepted Pending Implementation | 2026-06-08 | platform3-loop | none | none |
| 26 | content-axis-003-query-modelContent Api Axis Query Model | Accepted | 2026-06-08 | platform3-loop | none | none |
| 27 | content-axis-004-concurrency-modelContent Api Axis Concurrency Model | Accepted | 2026-06-08 | platform3-loop | none | none |
| 28 | content-axis-005-idempotency-modelContent Api Axis Idempotency Model | Accepted | 2026-06-08 | platform3-loop | none | none |
| 29 | content-axis-006-auth-shapeContent Api Axis Auth Shape | Accepted | 2026-06-08 | platform3-loop | none | none |
| 30 | content-axis-007-eventing-modelContent Api Axis Eventing Model | Accepted | 2026-06-08 | platform3-loop | none | none |
| 31 | content-axis-008-error-envelopeContent Api Axis Error Envelope | Accepted | 2026-06-08 | platform3-loop | none | none |
| 32 | content-axis-009-tenant-routingContent Api Axis Tenant Routing | Accepted | 2026-06-08 | platform3-loop | none | none |
| 33 | content-axis-010-conformance-evidenceContent Api Axis Conformance Evidence | Accepted | 2026-06-08 | platform3-loop | none | none |
| 34 | content-axis-011-privacy-retentionContent Api Axis Privacy Retention | Accepted Pending Implementation | 2026-06-08 | platform3-loop | none | none |
| 35 | content-axis-012-list-endpointsContent Api Axis List Endpoints | Accepted | 2026-06-08 | platform3-loop | none | none |
| 36 | content-itd-024-governed-test-serveGoverned Test Serve | Accepted | 2026-07-24 | platform3-doer | none | none |
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Alpha app builders need one plain-language answer to what Content owns, while the approved QTI surface remains the persistence authority for assessment resources and the Brainlift also requires non-assessment media. Without an explicit boundary and alias map, consumers must either learn QTI internals or guess which module owns each student-touchable object.
ACCEPTED: Define Alpha Content as the module for blank things a student interacts with. It is a plain-spoken set of SQL read views over approved QTI assessment tests, items, package resources, files, and selected version projections, extended only through sidecar tables that contain new Alpha columns plus foreign keys to base rows. QTI assessmentStimulus remains internal question anatomy, not a public Content row. Publish a generated alias map for every Alpha Content name and field. The Content storage kinds are question, test, test_spec, test_bank, article, video, audio, image, interactive, media_asset, external_link, and script. Every Content row is one asset, references other Content rows instead of nesting them, carries expected_xp, and uses the named catalog/discovery sidecars pinned by this architecture.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Keep QTI as the public Alpha module | Preserves the upstream assessment vocabulary exactly and requires almost no public projection work. | Makes every Alpha consumer learn QTI internals, leaves non-QTI media without a coherent home, and obscures the Content/Curriculum/Results boundary. |
| Create separate modules for tests, questions, media, and links | Allows each asset family to expose a narrowly specialized API and release cadence. | Fragments identity, catalog discovery, versioning, references, and expected-XP rules across modules that represent the same customer concept. |
One Content boundary gives consumers one identity and catalog while QTI-backed views preserve the approved assessment truth. Extend-only sidecars cover facts QTI genuinely lacks, so the chosen option wins the simplicity of one public module without paying for a second assessment store.
Tracked decision content-itd-001-module-boundary-and-alias-map; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
The QTI 1EdTech deliverable already owns assessment persistence, versioning, and interchange, but Alpha needs friendlier names and a few facts outside QTI. A second Alpha copy of QTI rows would create two authorities for the same test, item, and version at the moment both surfaces must remain interoperable.
ACCEPTED: Restrict QTI-backed Content to SQL read views over the approved 1EdTech QTI persistence model. Rename, cut, and restrict add no Alpha storage. Only extend tables add storage, and each extend table stores only new Alpha columns plus foreign keys to qti.* or platform.* base rows. Core object writes are write-through commands to the QTI 1EdTech base surface.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Fork Alpha content into alpha.content_* tables for every QTI object | Gives Alpha complete schema autonomy and makes all reads appear to come from one namespace. | Duplicates QTI facts, requires bidirectional synchronization, and allows version or conformance truth to diverge between the two stores. |
| Expose the QTI tables unchanged | Guarantees fidelity to the approved upstream schema with no projection layer. | Leaks compliance-oriented names to Alpha consumers and cannot represent new Alpha-only media, catalog, or expected-effort facts cleanly. |
Read views change names and public shape without moving QTI-owned data, while foreign-key sidecars isolate only genuinely new Alpha facts. That combination preserves one authority and still gives Alpha a usable contract, avoiding both synchronization machinery and raw-QTI leakage.
Tracked decision content-itd-002-shared-qti-truth-no-content-fork; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Source systems describe content with incompatible artifact kinds, MIME types, paths, and titles, while Alpha clients need a stable value space for filtering and rendering. The Brainlift also names specs, banks, scripts, and rich media, but QTI stimulus and sections are internal anatomy rather than independently launchable Content.
ACCEPTED: Expose twelve governed Alpha content_kind values: question, test, test_spec, test_bank, article, video, audio, image, interactive, media_asset, external_link, and script. QTI assessmentItem maps to question, and assessmentTest maps to test/test_spec/test_bank with assessment_role kept as the compatibility discriminator over shared QTI storage. QTI assessmentStimulus is a sub-part of a question (question = stimulus + root + options + wrong-answer explanations), not an independently addressable public Content row. QTI sections and curriculum containers are cut from Content and routed to Curriculum. Video, audio, image, test_spec, test_bank, and script are first-class Content kinds; media_type remains the MIME/delivery type and media_asset remains a generic reusable asset kind. QTI internal kinds such as assessmentStimulus, response-processing, outcome-declaration, result, usage-data, metadata, and manifest-resource are cut from the public Content catalog unless surfaced through one of the canonical kinds with explicit provenance. Diagram and decodable remain governed subtypes because they are image/article authoring variants, not collection kinds; see content-itd-019 and content-itd-020.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Pass through qti.artifact.artifact_kind unchanged | Retains exact QTI provenance and avoids a normalization mapping during assessment imports. | Exposes internal QTI kinds, omits Alpha media kinds, and gives consumers no stable cross-source taxonomy. |
| Derive kind from title or package path | Can classify legacy records without adding a typed field or changing their producers. | Makes business meaning depend on mutable strings, produces non-reproducible classifications, and cannot reliably distinguish specs, banks, tests, or embedded anatomy. |
A governed twelve-value enum is the smallest contract every source adapter and consumer can share. It keeps original artifact provenance behind the view while making public routing deterministic and explicitly excluding objects that belong inside questions or Curriculum.
Tracked decision content-itd-003-content-kind-taxonomy; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Articles, video, audio, images, interactives, external links, and scripts are reusable student-touchable assets, yet stock QTI does not express all of their delivery, transcript, thumbnail, or role semantics. Alpha must add that meaning without turning media into Curriculum containers or creating an unrelated asset store.
ACCEPTED: Extend Alpha Content for article, video, audio, image, interactive, media_asset, external_link, and script by anchoring each public item to a base qti.artifact/qti.package_resource row where the QTI resource shape exists, and by storing only new Alpha facts in extension tables where QTI is silent. A standalone article, video, audio, image, interactive, media asset, external link, or script write first creates the base manifest-resource/webcontent-style record through the QTI 1EdTech surface when applicable. media_type remains a renamed base qti.package_file field; Alpha-only sidecars add fields such as content_kind_override, media_kind, delivery_url/reference semantics, transcript/caption references, thumbnail/content references, external_url, or script role/provenance. An exact immutable article version uses the governed publication command after Content validates its stored hash and student-safe body contract. Primary Incept video uses the existing media endpoint with one narrowly allowlisted fetch-once immutable source reference carrying URL, declared video/mp4 type, byte length, and SHA-256. Content disables redirects, rejects private/local resolution, timeouts, overflow, and type/length/hash mismatch, stores and serves the verified bytes in qti.package_file under a server-minted media UUID and unique Content-owned package path, and never persists or returns the signed query token. The governed request cannot select media_id or collide with an existing package file; repeated source provenance across exact versions remains safe because source bucket/path stays metadata rather than package-file identity. Idempotency binds the stable bucket/path/type/length/hash and attachment semantics but excludes the expiring signed URL, so a retry may present a fresh signature without duplicating or refetching completed media. This is not a generic external-URL importer or JSON/base64 upload lane. Course/unit/chapter/lesson/lesson_section containers are not stored as Content.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Force every non-QTI item into a raw QTI package workflow with no Alpha extension | Uses one upstream import path and maximizes Common Cartridge/QTI compatibility by construction. | Burdens simple media authoring with package mechanics and leaves Alpha-specific delivery and accessibility facts untyped or absent. |
| Store media and links only in metadata on tests or curriculum containers | Avoids standalone media records and additional extension relationships. | Makes reusable assets unaddressable, duplicates them across parents, and puts student-touchable material into the wrong module. |
Anchoring each asset to the simplest compatible QTI/package resource preserves one identity and round-trip path; sidecars add only the delivery facts QTI lacks. This is less machinery than a second media store and more truthful than hiding assets in parent metadata.
Tracked decision content-itd-004-non-qti-media-extension; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover, interoperate.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Content atoms must be discoverable and measurable by the governed KCs they address. The historical content_curriculum_link relation is unweighted, mostly targets Curriculum components, lacks a registry foreign key, and remained writable, so it could not serve as the Q-matrix and created a second alignment authority beside the KC registry.
ACCEPTED: Curriculum owns the governed alpha.knowledge_component registry; Content owns only the weighted many-to-many alpha.content_kc_tag relationship from one stable base Content identity to one active same-tenant registry identity. Each active row carries weight in (0,1] as independent relevance strength, one-primary role semantics, provenance, confidence in [0,1], and lifecycle timestamps. Weight is not learner mastery or probability and sibling weights need not sum to one. At most one active row per Content item is primary. Tags are item-scoped and version-invariant: a new Content version does not change them; version-specific alignment requires a distinct Content identity. The relationship never targets case.cf_item or Curriculum components and never copies KC meaning into Content. For governed alignment reads, content_kc_tag and GET /kc-tags are authoritative. The kc_refs facet is a non-authoritative catalog hint, and alpha.content_curriculum_link is only a frozen historical archive: its public POST returns 410 and new authoring uses /kc-tags.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Use free-form tags such as 'fractions' or 'grade-4-math' | Is easy for authors to add and requires no dependency on Curriculum identifiers. | Cannot enforce identity or lifecycle, conflates facets with relationships, and creates spelling-based joins that are not reproducible. |
| Copy the curriculum node text onto each content item | Makes a Content row self-describing even when Curriculum is unavailable. | Creates stale duplicates of Curriculum-owned statements and loses the stable node identity needed for graph queries. |
| Keep content_curriculum_link writable beside content_kc_tag | Avoids breaking callers that still POST unweighted Curriculum-component links during the cutover. | Leaves two writable alignment authorities with incompatible targets and no deterministic reconciliation rule. |
One same-tenant foreign-key relationship to the Curriculum-owned alpha.knowledge_component registry preserves identity while weight, primary role, provenance, and confidence express the minimum governed Q-matrix semantics. GET /kc-tags is the alignment authority; kc_refs remains a non-authoritative catalog hint. Keeping the old rows readable but rejecting new archive writes preserves evidence and removes dual authority.
Tracked decision content-itd-005-curriculum-links-are-relationships; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Alpha filters and policy formulas depend on subject, grade, media role/type, test role, mastery-gate status, and family identity. Those facts currently arrive in inconsistent producer labels, and inferring them from titles or allowing app-local classifications would give the same asset different meaning across consumers.
ACCEPTED: Add typed Content fields at write/import time through views plus extension sidecars: subject_id, target_grade_id, media_kind, media_type, test_type, is_mastery_gate, and test_family_id where applicable. content_kind is a view/restriction over qti.artifact_kind and qti.package_resource.resource_type with extension-only values limited to the canonical Content taxonomy. test_type uses the governed enum end_of_course, end_of_grade, test_out, placement, diagnostic, screener. Placement is a grade-level mastery-test role inside the PowerPath bottom-up workflow; a screener may choose the starting grade but never certifies final placement. is_mastery_gate remains a separate boolean. media_type is the MIME/delivery type on media assets associated with video, audio, image, or interactive Content; external_link exposes URL/open-policy fields instead. media_type never replaces the content_kind enum.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Parse subject, grade, and test type from titles or package paths | Can retrofit legacy content without requiring producers to supply new structured fields. | Binds semantics to mutable naming conventions, misclassifies ambiguous titles, and makes policy results change when presentation text changes. |
| Let every app keep its own classification table | Lets each application optimize labels and categories for its immediate user experience. | Creates multiple value spaces, prevents reliable cross-app filters, and moves platform business meaning into consumers. |
Typed write-time fields give one auditable classification while keeping distinct concepts—especially test_type, is_mastery_gate, content_kind, and media_type—separate. Views and sidecars supply the fields without copying upstream storage.
Tracked decision content-itd-006-typed-content-and-test-fields; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Authors need editable drafts while learners and replay require the exact material originally launched. QTI already supplies immutable artifact versions, but Content must also define exact-version reads and what happens when Curriculum references only a reusable content identity.
ACCEPTED TARGET (immutable ingest and exact article publication implemented; launch-resolution pending): Restrict Content authoring to immutable versions and pin launch resolution. Friendly QTI-backed drafts and student views are read views over qti.artifact_version.authoring_json and qti.artifact_version.delivery_json. Non-QTI render metadata is stored only as Alpha extension columns keyed to the base artifact/version. Item-metadata PATCH uses If-Match; ordinary version creation uses Idempotency-Key and appends. Governed article publication names and validates the current exact immutable version, refreshes server-owned positive item trust for that validation, and appends one receipt for only that version; a successor version never inherits the prior receipt. TimeBack canonicalizes source-table identity, binds each new exact version UUIDv5 to tenant + source_record_id + canonical content hash, adopts a deployed legacy :v1 row only when its complete immutable projection matches, and appends changed bytes with the next version_number and supersedes_version_id under an item lock. Database guards reject UPDATE and DELETE of stored exact versions; an identical or historical hash replay reuses its exact row and never rolls the served pointer backward. Content copies no qti.artifact_version into Alpha storage. Consumers GET an exact version at /items/{contentId}/versions/{versionId} and read its student_view field; the runtime has no separate /versions/{versionId}/student-view subroute. The accepted target is that, when a Curriculum member references only content_id, Content resolves latest_published_version_id at launch time, returns resolved_content_version_id, and lets Events pin it. Current runtime instead exposes raw latest_version_id and returns version_id; it does not yet implement the named latest-published resolver, resolved_content_version_id, or Events launch pin.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Edit rows in place | Simplifies authoring URLs and avoids accumulating historical versions. | Changes delivered material underneath attempts, breaks replay/audit, and creates last-write ambiguity for concurrent authors. |
| Expose raw QTI authoring JSON as the only Alpha editing shape | Avoids maintaining a friendly Alpha projection and preserves every QTI authoring detail. | Forces Alpha clients to understand QTI internals and does not cover non-QTI version metadata or student-safe views. |
Reusing immutable QTI versions gives tests and media one history rule; friendly authoring/student views keep that rule usable. The accepted but incomplete launch-resolution target resolves an unversioned reference only at launch, then pins the resolved version in Events, balancing Curriculum reuse with historical determinism.
Tracked decision content-itd-007-versioned-authoring-and-student-view; generated from architecture/commitments.json as an accepted architecture target; implementation and deployed proof are pending and not deployed. Use cases: render, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
A blank assessment contains interactions, answer shapes, keys, rubrics, and scoring rules, while a learner attempt contains submitted answers, outcomes, feedback, and audit evidence. QTI represents both domains, so Alpha needs a firm module split to prevent Content from becoming a student-result store.
ACCEPTED: Content may expose answer slots, expected shapes, teacher-only answer keys, assessment components, and scoring-plan provenance as SQL views over QTI declarations, component rows, and processing rules. Answer-key reads require read:answer_key; current scoring-plan reads require only read:content and must not be described as having the stricter answer-key scope. assessment_component rows are the blank assessment structure: sections, parts/interactions, question refs, distractors, question-level rubrics, and pre-authored feedback. Content does not own delivery sessions, submissions, attempts, outcome_state, processing_trace as a student result, AI grading audit, post-attempt feedback, or gradebook rollups. It also does not create Alpha answer_slot, answer_key, assessment_component, or scoring_plan storage tables that copy QTI declaration/component/processing columns.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Put attempts and scores beside the question in Content | Makes one API call return the instrument and a learner's state together. | Mixes reusable blank assets with tenant/learner runtime facts, duplicates Results ownership, and complicates retention and authorization. |
| Hide all answer-slot metadata | Eliminates answer-key exposure risk and keeps the public Content shape very small. | Prevents authorized renderers, teachers, and scoring integrations from understanding the blank instrument's expected interactions. |
Expose the blank structure through permissioned QTI-backed views and route every student-specific fact to Events or Results. This preserves renderer and teacher jobs without creating a second attempt or gradebook model in Content.
Tracked decision content-itd-008-answer-slots-not-student-answers; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, interoperate.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Catalog users need effectiveness evidence beside an item, but effectiveness is derived from settled learner outcomes and changes as Results recomputes its population. Persisting counters in Content or recomputing them in each dashboard would introduce another authority and inconsistent denominators.
ACCEPTED: Do not create or write any Content-owned content_effectiveness storage. Treat alpha.content_effectiveness as a Results-owned derived public view that Content reads by content identity. The Content endpoint GET /items/{contentId}/effectiveness returns validated_by_count, failed_by_count, effectiveness_rate, freshness_status, last_recomputed_at, and provenance from the Results-owned view when present. If the Results row or view is absent, Content returns a safe insufficient_data fallback with zero counts, null rate, null recomputed time, and provenance.sourceModule = results; it never inserts a placeholder row.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Let each dashboard count successes and failures from raw results | Allows each consumer to choose its own population and freshness without a shared rollup dependency. | Duplicates sensitive calculation logic, yields incomparable rates, and requires broad raw-results access. |
| Store counters that apps increment | Provides fast reads and simple incremental updates during learner activity. | Is race-prone, cannot correct historical outcomes safely, and makes application traffic the owner of a derived metric. |
| Create Content-owned derived storage beside the item | Keeps catalog reads local to Content and avoids a runtime cross-module dependency. | Copies a Results-owned derivation, creates refresh coordination, and obscures which module defines the denominator. |
A read dependency on the Results-owned view preserves one calculation and gives Content the evidence its catalog needs. A typed insufficient_data fallback is simpler and more honest than placeholder storage when the Results rollup is unavailable.
Tracked decision content-itd-009-content-effectiveness-derived-rollup; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
External assessment producers emit results for forms that belong to a stable test family, but titles, dates, and URLs can change across administrations. Results adapters need a platform identity for reconciliation, while Content—not Results—owns the blank test and its family relationship.
ACCEPTED: Add nullable test_family_id to assessment-test extension storage and source_system to content-item extension storage, both keyed to the base QTI artifact where external results or multiple forms may eventually reconcile. Current test_family_id is unconstrained text: Content has no family resolver, TimeBack production import writes it as null, and Results can persist producer family labels without a Content foreign key. Content therefore owns only the blank storage field today; cross-form/source reconciliation to one Content-owned family identity remains pending.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Let each adapter match by title, date, and producer URL | Avoids creating a platform family identifier and can use fields already present in source exports. | Produces adapter-specific fuzzy matching, breaks when labels or URLs change, and cannot guarantee two forms reconcile to the same family. |
| Make Results invent a test family if Content cannot find one | Lets result ingestion proceed even before the corresponding blank assessment is cataloged. | Moves blank-instrument identity into the outcome module and creates family records with no authoritative Content asset. |
A Content-owned test_family_id is the accepted stable target and preserves the blank-instrument/result boundary. Current runtime has only a nullable unconstrained storage field, however; it does not yet resolve producer labels or forms to that target.
Tracked decision content-itd-010-external-test-family-identity; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover, interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
The upstream QTI/platform model includes package bytes, low-level declarations, processing graphs, conformance controls, sessions, and results. Exposing all of it would make Alpha unsafe and compliance-shaped; hiding all of it would make the friendly projections impossible to audit.
ACCEPTED: Cut raw QTI package bytes, generic component/object-graph browsing, standalone variable-declaration and processing-rule row APIs, conformance runner mutation, delivery-session browsing, attempt browsing, gradebook/result rows, and any public Alpha storage table that copies these rows. Keep them as upstream provenance or route them to the correct module.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Expose every QTI and platform row for maximum power | Gives expert consumers complete flexibility and avoids deciding which low-level capabilities belong in Alpha. | Leaks mutation and learner-runtime surfaces, burdens ordinary consumers with internals, and weakens module ownership. |
| Hide all provenance | Produces the smallest and most approachable public Content API. | Prevents experts from tracing a friendly field to its authority and makes conformance or reconciliation claims unverifiable. |
Cut unsafe or wrong-module objects from the public Alpha contract while keeping links to their upstream provenance. This gives ordinary consumers a small surface and preserves an audit path without turning Content into a generic QTI browser.
Tracked decision content-itd-011-public-cuts-and-internal-provenance; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Content validation can originate in QTI schemas, Schematron, database constraints, or Alpha sidecars, yet school-facing clients need stable, understandable errors and one parsing contract. Returning raw upstream diagnostics leaks internals; simplifying to text alone discards machine actionability and traceability.
ACCEPTED: Rename public Content validation and error messages into plain-language RFC 7807 Problems while preserving the implemented platform problem envelope: type, title, status, stable code, detail, requestId, traceId, optional cause/docUrl, and fieldErrors where present. Current public Problems do not expose a distinct upstream-provenance link field; internal validator provenance remains server-side trace context rather than a promised response member.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Return raw QTI/Schematron/database errors | Preserves maximum diagnostic detail for engineers and requires no translation layer. | Leaks implementation details, varies by validator, and gives teachers or app builders no stable remediation contract. |
| Return plain text only | Is easy for humans to read and simple for the server to emit. | Removes stable codes, field locations, trace identifiers, and provenance needed for programmatic recovery and support. |
The platform RFC 7807 envelope already supplies stable machine fields; Content only translates public detail into school language and keeps internal validator provenance in server-side trace context. That reuses one parser without promising a public provenance member the runtime does not emit.
Tracked decision content-itd-012-plain-language-problems-and-provenance; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Districts and tools exchange assessments and learning resources through QTI/Common Cartridge, while Alpha adds friendlier names and sidecars. Without a round-trip constraint, a custom Alpha model could appear convenient but silently lose upstream fidelity or require unbounded translation logic.
ACCEPTED TARGET (pending round-trip implementation): Require Content storage shape to follow the 1EdTech authoring/resource shape wherever one exists, and require a lossless Common Cartridge export-then-reimport acceptance test with no translation logic. QTI question and test use the approved QTI resource shapes; QTI assessmentStimulus remains embedded question anatomy, not a public Alpha content_kind. Article, video, audio, image, interactive, media_asset, external_link, and script use the simplest QTI/Common-Cartridge-compatible resource shape where available, with Alpha sidecars holding only new facts. Common Cartridge bundles are produced on demand from a Curriculum collection plus referenced Content; bundles are transport output, never stored Content. Current runtime exports and inspects the generated package, but its round_trip_check status does not re-import or compare the package and is not lossless round-trip evidence.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Store Common Cartridge bundles as Content | Makes imported/exportable packages directly addressable and preserves the exact transport artifact. | Treats a collection/transport snapshot as a student-touchable asset, duplicates its member content, and becomes stale as Curriculum changes. |
| Normalize every kind into a custom Alpha-only document model and translate on export/import | Gives Alpha one aesthetically uniform document schema independent of upstream standards. | Creates lossy mappings and permanent translation code whose correctness becomes a second interchange authority. |
Using the sanctioned QTI/resource shape where one exists and generating bundles from Curriculum references makes export then reimport the accepted executable proof. That proof is not complete until runtime actually reimports and compares the package rather than labeling export inspection as a passed round trip.
Tracked decision content-itd-013-lossless-common-cartridge-round-trip; generated from architecture/commitments.json as an accepted architecture target; implementation and deployed proof are pending and not deployed. Use cases: interoperate.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Catalog and LOR-style discovery require a fixed set of subject, grade, language, accessibility, rights, provenance, quality, alignment, kind, effort, and modality facets. QTI metadata is not a governed Alpha value space, while copying whole Content rows into a catalog would create another base store.
ACCEPTED: Extend Content with catalog-facet sidecars beside the QTI-backed base. The 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. Facets store only governed discovery values or references, relationship keys to base content artifact/version rows, lifecycle/provenance, and optional policy references. The Content base view does not copy facet values into QTI fields, and the facet sidecar does not copy QTI title, language, kind, version, package, file, or Curriculum statement text. Generic facet=KEY:VALUE reads require a stored assignment. For expected_xp_band specifically, runtime also applies the governed numeric range to content_item.expected_xp, so a stored assignment outside the row's numeric band is vetoed. The separate expected_xp_band= convenience filter applies only the numeric range and does not require a stored assignment. Callers must choose between hybrid curated-plus-numeric facet truth and numeric-only convenience filtering until semantics are unified.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Put all discovery facets into qti.metadata | Keeps discovery annotations beside the QTI artifact and avoids an Alpha sidecar table. | Hides governed fields in generic metadata, cannot cover non-QTI assets consistently, and weakens typed query/index guarantees. |
| Copy Content base rows into a separate catalog table with facets | Makes discovery reads local and denormalized for straightforward indexing. | Duplicates titles, kinds, versions, and lifecycle, requiring synchronization with the authoritative Content/QTI rows. |
A named sidecar stores only governed discovery values and references the base asset/version. It gives catalogs stable typed fields without copying base facts or burying semantics in an open metadata bag.
Tracked decision content-itd-014-catalog-facets-sidecar; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Curriculum needs reusable expected-effort values for course totals, while Results records what a particular learner actually earns. The Brainlift places student-independent expected XP on every Content row, so the platform needs a reproducible authoring calibration rather than deriving it from learner time or burying it only in course structure.
ACCEPTED: Extend every Content row with expected_xp as student-independent Content configuration. The accepted authoring policy calibrates an exact content version by an approved author, reviewer, or calibration agent speed-running it in its intended renderer at a fluent target pace, using alpha.policy.expected_xp_calibration to convert expected effort into a nonnegative decimal XP value. Current runtime does not require or store proof of that observation before publication: HTTP/database checks enforce only a finite number greater than or equal to 0, import/seed paths can publish policy-derived or default values, and expected_xp is artifact-scoped configuration mutated in place by item PATCH rather than version-scoped evidence. No maximum-per-row policy is implemented. expected_xp lives on Content, applies to every content_kind, and is exposed through Content detail and catalog reads. Awarded XP, bonuses, repeat-attempt discounts, effort adjustments, and negative XP are Results XP-ledger facts, never Content fields.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Compute XP from each student's actual time or accuracy | Adapts the value to real learner behavior and can reward additional effort or performance. | Turns expected effort into a learner outcome, makes catalog values unstable, and duplicates the Results XP ledger. |
| Store XP only on Curriculum course components | Keeps all course planning totals in the module that owns sequence and aggregation. | Assigns different expected effort to the same reusable asset, loses standalone Content calibration, and contradicts Content ownership. |
Calibrating the exact content version at a fluent target pace makes expected_xp a stable property of the blank asset. Results remains free to award different learner XP, and Curriculum can aggregate referenced leaves without owning or copying the base value.
Tracked decision content-itd-015-expected-xp-content-config; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Curriculum members and composite Content must reference any of twelve asset kinds, sometimes at an exact version and sometimes for launch-time resolution. Kind-specific foreign keys or untyped URLs would multiply branches, make new kinds invasive, and allow references that cannot resolve through Content.
ACCEPTED: Restrict all cross-module and content-to-content references to Content to one content_ref contract. The contract identifies the base Content row and optional version, works for question, test, test_spec, test_bank, article, video, audio, image, interactive, media_asset, external_link, and script, and carries no kind-specific foreign keys. A stimulus is embedded question anatomy, not a targetable Content row. Curriculum course_component_member.target_ref uses this contract when target_ref_type=content; renderer selection and gate fields remain on the Curriculum member. Content rows that embed or depend on other Content rows use content_reference rows with the same contract. Content-to-KC alignment is not a content_ref: content-itd-005 owns the item-scoped, version-invariant content_kc_tag relationship. Current Content reference/media storage validates artifact and optional version UUIDs independently; it does not prove that a supplied version belongs to the paired artifact or tenant. Same-item/tenant version validation is a pending integrity guarantee, not current typed resolution.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Create one relationship per content kind | Provides strong database typing for each target table and makes kind-specific constraints explicit. | Creates twelve schemas and API branches, complicates polymorphic collections, and forces every new kind through all consumers. |
| Let target_ref be an untyped string or URL | Can point at any internal or external resource without schema changes. | Cannot enforce tenant, identity, version, or lifecycle integrity and moves resolution rules into every consumer. |
One content_ref shape addresses every Content/Curriculum reference kind through the same base identity and optional version, while content_reference rows express composition without nesting payloads. Content-to-KC alignment is deliberately separate and item-scoped through content_kc_tag. Current reference/media storage validates the artifact and version UUIDs independently rather than proving their pair, so the shape is uniform but same-item/tenant typed resolution remains pending.
Tracked decision content-itd-016-uniform-content-reference-contract; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, interoperate.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Learning-object repositories differ in branding, collections, quality labels, and packaging, but their durable concepts already map to Content assets, Curriculum grouping/alignment, named provenance/quality facets, typed Content references, trust evidence, and Common Cartridge. Provider-specific tables would turn each integration into a new platform primitive.
ACCEPTED: Cut all LOR-specific storage from Alpha Content. Reusable learning objects are ordinary Content rows. Curriculum owns the alpha.knowledge_component registry and standards graph; Content-to-KC alignment uses the weighted content_kc_tag relationship owned by content-itd-005, while standards-framework discovery remains a governed facet/Curriculum reference. Content has no separate lineage table: governed source/import identity uses adapter/source-table ownership plus content_item.source_record_id, while source_identifier remains a nullable provenance/export label rather than the reconciliation key; discovery provenance and quality use content_catalog_facet_assignment with the provenance or quality_rating facet; validation trust uses content_trust_evidence; and asset-to-source-asset production lineage uses content_reference with relationship_kind source_file_for or source_script_for. To preserve deployed IDs, the TimeBack UUID remains tenant + source_record_id, but the first canonical source table is bound into ownership checks: reuse from another source table returns a typed source-identity collision instead of colliding/upserting. Supporting both same-named records concurrently would require a future versioned identity cutover. Bundling and packaging use on-demand Common Cartridge export from Curriculum component subtrees plus referenced Content. No Merlot, OER Commons, Khan, IXL, library, playlist, or question-bank tables are introduced in Content.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Create provider-specific library tables | Preserves each repository's native model and can accelerate one provider integration. | Duplicates common primitives, locks queries to vendors, and creates separate migration and lifecycle rules per source. |
| Store library collections as Content | Keeps repository objects in one module and makes a collection directly addressable through Content APIs. | Conflates student-touchable assets with routing/grouping structures and duplicates Curriculum collection semantics. |
Map reusable objects to ordinary Content, grouping and the governed KC/standards registries to Curriculum, weighted Content-to-KC alignment to content_kc_tag, source identity to content_item, provenance/quality to governed facets, validation to trust evidence, and asset production lineage to typed content_reference rows. This composes named primitives and keeps each provider as an adapter instead of a storage authority.
Tracked decision content-itd-017-lor-clones-no-specific-storage; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover, interoperate.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
TimeBack production exports source-shaped content, activity references, and catalog facts that do not arrive as QTI/Common Cartridge. Requiring migration callers or skill packs to pre-normalize those records would put Content semantics outside the platform and make materialization failures hard to reconcile.
ACCEPTED: SHIP a named TimeBack production ingest adapter at POST /imports/timeback-production. The adapter accepts source-shaped TimeBack content, activity-reference, and catalog records, normalizes them server-side into the canonical Content kinds, creates one Content row per asset, writes content_reference rows for embedded/dependent assets, writes catalog facets and expected_xp sidecars, records source_system/source_record_id provenance, and returns materialized counts plus Content ids. Malformed requests return content.validation_failed with HTTP 400; implemented semantic rejections such as an out-of-scope subject return content.adapter_rejected with HTTP 422, and a response with any Problem code must never use HTTP 200. The curriculum-link relation is a frozen archive: imports write zero curriculum links and infer zero kc_refs discovery assignments from source curriculum hints. Deployed Content identity remains tenant + source_record_id for continuity, while canonical source-table ownership is checked so equal record ids from different source tables return a typed collision rather than aliasing/upserting. Immutable source versions are hash-bound UUIDv5 rows: identical canonical bytes replay, changed bytes append the next version_number and supersedes edge, and legacy deterministic versions are adopted only after complete source/projection ownership checks. A successful import must make rows visible through GET /items and raw-DB views without mutating Curriculum placement or KC identity.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Require migration callers to pre-normalize TimeBack records before calling Content | Keeps the Content API narrow and shifts legacy complexity into one-time migration tooling. | Leaks canonicalization rules into callers, creates ungoverned transformations, and prevents the platform from explaining or replaying normalization. |
| Only import QTI/Common Cartridge packages | Uses standards-backed ingestion with existing validation and round-trip guarantees. | Cannot ingest production records that are not packaged that way without a lossy external conversion step. |
A named server-side adapter accepts the record the source actually has and materializes canonical rows, references, facets, effort, and provenance under Content's dictionary. That keeps consumers thin and gives reconciliation one owned, testable boundary.
Tracked decision content-itd-018-source-shaped-production-ingest; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
The Brainlift uses a broader authoring vocabulary than the original first-class Content kinds: specs, banks, scripts, diagrams, decodables, and stimulus all need explicit treatment. Leaving those words to importer inference would create hidden enums, while promoting every variation would make content_kind unstable and conflate anatomy with assets.
ACCEPTED: Reconcile the Brainlift's broader content vocabulary with the binding first-class content_kind values using two mechanisms. test_spec, test_bank, and script are public content_kind values; test/test_spec/test_bank retain assessment_role (form | spec | bank) as a storage/read compatibility discriminator over QTI assessmentTest. stimulus is question anatomy, not a public content_kind. diagram is an image subtype; decodable is an article subtype carrying its phonics-constraint set. subtype and assessment_role are governed enums validated by the storage layer at write time. content_kind is exactly question, test, test_spec, test_bank, article, video, audio, image, interactive, media_asset, external_link, and script.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Make diagram and decodable new content_kind values | Makes these common authoring forms directly filterable without consulting subtype fields. | Expands a routing enum for presentation variants and encourages every future variant to become a first-class storage kind. |
| Leave them undocumented and let importers infer them from titles, MIME types, or file paths | Avoids new schema fields and lets each source retain its own vocabulary. | Creates inconsistent classifications, string-parsing business rules, and no authoritative answer for consumers. |
First-class spec, bank, and script identities are independently addressable assets, while diagram and decodable are governed image/article variants and stimulus remains question anatomy. Typed subtype and assessment_role fields preserve the vocabulary without destabilizing the twelve-kind route.
Tracked decision content-itd-019-governed-subtypes-within-ten-kinds; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Test generation needs durable blueprints, KC×DOK coverage, constraints, passing-rule references, banks, ordered member forms, and a public create seam that returns the Content-owned identity. A read-only sidecar plus an opaque generic item contract leaves AlphaTest unable to discover the write and pressures it toward forbidden shadow storage.
ACCEPTED TARGET (pending semantic validation): Give specs and banks validated first-class Content storage. A test_spec is a Content row with content_kind=test_spec (assessment_role=spec for QTI compatibility) whose blueprint lives in alpha.test_spec_extension: kc_coverage (KC × DOK grid with target counts and instructional weights), item_type_mix, difficulty_constraints, passing_rule, min_forms, max_item_overlap, and enemy_item_rule. POST /tenants/{tenantId}/alpha/content/items with content_kind=test_spec is the canonical create-only authoring seam: all seven blueprint fields are required, the response returns the native content_id, and GET /tenants/{tenantId}/alpha/content/items/{contentId}/blueprint returns that same identity as spec_id. The Content identity, assessment_role row, complete sidecar, initial version, and completed idempotency replay response commit atomically; identical completed replays return the same identity and changed-body key reuse returns content.idempotency_conflict. AlphaTest and other consumers must not persist a shadow blueprint. The accepted target additionally validates kc_coverage against active identities in the Curriculum-owned alpha.knowledge_component registry at write time and requires every item and coverage cell to carry a governed DOK (Webb) tag. A test_bank is a Content row with content_kind=test_bank (assessment_role=bank for QTI compatibility) that references its spec by spec_id (alpha.test_bank_extension) and lists fixed-form members or PP100 adaptive shards through content_reference rows (relationship_kind=bank_member, ascending form_index); create and PATCH require that spec_id resolve in the routed tenant to an unretired, non-archived, non-status-retired content_kind=test_spec, assessment_role=spec item with a readable sidecar. Existing same-key item-create ledger state is arbitrated first; a fresh test_bank create takes the tenant graph fence and resolves that target before claim or item mutation, so an invalid target leaves no item/ledger row and a corrected body can reuse the unclaimed key. Sparse bank-definition PATCH preserves omitted fields and rejects explicit-null spec_id or membership_rule. An actual definition change is allowed only for an unretired draft (owner ruling #2534: a trust verdict never locks the draft lane); active bank_member rows—fixed forms or PP100 adaptive shards—forbid a spec_id swap or switch to qti_adaptive_selection. Typed rejection preserves the item, sidecar, graph, timestamp, and ETag. Current runtime does not resolve active registry KCs or validate member forms against the spec blueprint. Where stock QTI already pools (adaptive/CAT), the pool persists as the QTI section's qti-selection/qti-adaptive-selection structure inside the one adaptive test, never a parallel collection table. The spec's passing_rule is canonical for every form in its banks; the cutoff number and the per-KC false-pass/false-fail loss stay in alpha.policy.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Keep specs as free-form editing_draft JSONB and banks as an unbacked test_family_id string (the status quo that filed #72) | Requires minimal migration and allows authors to evolve blueprint shapes without schema changes. | Cannot validate KC/DOK coverage or membership, provides no addressable spec/bank identity, and leaves downstream gates dependent on opaque blobs. |
| Hide specs/banks as test rows under only assessment_role | Reuses QTI assessmentTest rows and avoids expanding the public Content kind taxonomy. | Makes API routing and discovery depend on a secondary discriminator and under-specifies blueprint and membership storage QTI lacks. |
Address specs and banks as Content identities backed by QTI test storage, then publish the existing item POST as the create-only test_spec seam and commit its identity, complete sidecar, initial version, and replay response together. Existing references, discovery, Curriculum gates, and interchange keep working without a shadow blueprint or parallel adaptive pool.
Tracked decision content-itd-020-test-spec-and-test-bank; generated from architecture/commitments.json as an accepted architecture target; implementation and deployed proof are pending and not deployed. Use cases: render, discover, interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals.
Stored instructional HTML contains tb-* semantic tokens used by external renderers, but leaving the vocabulary only in a renderer document would make an application the semantic owner of persisted Content. At the same time, renderer selection, QTI scoring, and learner progress belong to Curriculum, QTI, Events, and Results respectively.
ACCEPTED: Content owns and actively validates the governed tb-* markup vocabulary for stored instructional HTML bodies when an article exact version is published. The Content data dictionary defines tb-instructional-content, tb-article-container, tb-segment, tb-interaction, tb-qti-assessment-item, tb-applet, tb-catalog, tb-qti-config, tb-applet-config, data-catalog-idref, practice-step, example-practice-container, headers-row, header, step-row, step, example-step, answer-box, tb-glossary-term, tb-glossary-definition, tb-narration, and tb-narration-audio as the value-space of Content version HTML bodies. Publication rejects unknown tb-* tokens, duplicate local ids, unresolved same-body data-catalog-idref values, and student-forbidden answer/scoring/teacher/mastery/progress/credential fields; ordinary presentational classes remain nonsemantic. Curriculum still selects the renderer per member through course_component_member.renderer_ref; renderer implementations remain external Applications capabilities. QTI XML, response processing, scoring, outcomes, and answer semantics remain QTI-owned. Durable learner progress flows back through Caliper Events/Results; renderer contentState and host state must not become private progress stores.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Leave the markup vocabulary only in the external renderer implementation document | Lets the renderer evolve its markup without Content schema/documentation changes. | Makes persisted body meaning depend on an external application and leaves Platform3 unable to validate or explain stored tokens. |
| Move renderer selection or gate fields onto assessment Content rows | Makes an asset self-contained for launch and reduces Curriculum lookups. | Couples reusable material to one course context and puts sequencing/gating decisions in the wrong module. |
| Let renderer contentState become durable learner progress | Allows rich applications to save and resume without integrating the platform event/result seams. | Creates an unaudited private progress store and bypasses Caliper, Results, retention, and cross-app continuity. |
Content's dictionary governs semantic tokens in stored bodies, while sanctioned renderers consume them and contextual/runtime concerns remain with their existing owners. This assigns one owner per fact without forcing QTI to absorb instructional markup.
Tracked decision content-itd-021-tb-markup-body-contract; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals.
Interactive articles and videos need ordered segment/cue-to-question composition that is durable, queryable, and reusable. The earlier markup-only approach covered render-time embedding but did not give cue timestamps and segment bindings a typed platform representation, forcing a decision about whether QTI, Curriculum, or Content owns them.
ACCEPTED: Interactive article and interactive video are first-class Content objects. Content owns the object and its instructional-delivery composition through typed alpha.interactive_composition rows: interactive_article stores ordered segment-to-question bindings, and interactive_video stores media plus strictly increasing cue timestamp-to-question bindings. The question references reuse canonical Content question ids, exact imported question version ids, and optional QTI assessment_component ids; QTI owns assessment items/tests, components, answer keys, and scoring semantics, not article/video instructional-delivery composition. Governed publication fails closed unless every question ref resolves to its exact eligible canonical version; video additionally requires the same item's same-exact-version verified primary media UUID, valid cue timing for the declared duration, and caption or transcript evidence. Curriculum references, sequences, and gates the Content object through course_component_member target_ref/content_ref plus XP policy; content_kc_tag separately records item-scoped relevance to registry KCs, and the frozen content_curriculum_link archive is not a current routing write. Curriculum does not store article/video material or cue/segment bindings. Current PUT accepts per-entry parent version_id values while active-row uniqueness omits version_id, and GET reports the first active row's version_id; rejecting mixed parent versions or defining explicit aggregate semantics remains a documented coherence gap.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Extend QTI to carry video cue timings, article segment order, narration, or instructional-delivery composition | Keeps all question-adjacent structure in one assessment interchange model. | Pollutes QTI with non-assessment delivery semantics and creates nonstandard extensions that weaken interchange. |
| Make interactive article or interactive video a Curriculum type that stores the material | Places sequence and gating beside course structure and can model the experience as a learning path. | Makes reusable student-touchable material course-owned and duplicates Content versioning, references, and rendering. |
| Keep cue/segment question composition only in tb-* markup parsed at render time | Avoids new typed rows and keeps authoring/rendering in one document body. | Makes relationships opaque to queries and validation, and leaves ordering/timestamps dependent on parser behavior. |
Typed Content composition rows represent instructional delivery over reusable assets while continuing to reference QTI-owned questions/components. This supersedes only the render-time-composition part of ITD-021 and preserves Curriculum's narrower sequencing/gating role through course_component_member target_ref/content_ref. Weighted KC relevance is the separate content_kc_tag concern; the frozen archive is not routing authority.
Tracked decision content-itd-022-interactive-composition; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Content import/QC sets trust for QTI-backed questions and forms, but first-class non-QTI test_spec, test_bank, and article authoring previously had no normal Content-owned operation that could compute a publishable QC verdict. Requiring a second signed producer evidence row duplicated verdicts across a repository boundary, while bespoke owner migrations made ordinary course publication operationally exceptional. Curriculum and launch consumers need one Content-owned exact-version answer that fails closed immediately when trust is revoked.
ACCEPTED: Content exact-version release eligibility is exactly Content serveability, and under owner ruling #2534 (2026-08-05) approval never gates student visibility. For the requested exact content_id/content_version_id, serveable=true and release_eligible=true exactly when the owning item has status=published, retired_at=null, and item trust is not failed; positive trust and per-version QC receipts are recorded provenance, never launch preconditions, so unknown or missing trust serves. Content writes receipts through one of two computations: QTI import QC for imported questions/forms, or POST /items/{contentId}/publish for an exact immutable test_spec, test_bank, article, or video. The publication command accepts only content_version_id, requires a service/reviewer/operator principal with publish:content, validates the exact current version and its kind-owned graph/body/media/accessibility/exact-dependency contract, and atomically records the server-computed publication, fresh positive item trust for that validation, and exact receipt; callers never submit trust_status or a verdict. A video cannot publish until Content owns and re-serves the verified primary bytes and its exact composition passes duration, cue, accessibility, and exact QTI dependency checks. Each passing version records its own receipt as append-only provenance; receipts never gate release. A published test_spec or test_bank cannot be patched or receive another version because its blueprint and membership contract are item-grain; a revision is a new Content identity that must pass publication independently. An article or video remains append-only-versioned and each successor version must pass publication independently. Setting item trust_status=failed remains the immediate all-version revocation lever and is never overwritten by publication. No producer release-evidence append, required quality-bar tuple, producer verdict copy, owner-acceptance tuple, or exact-version owner allowlist participates. The existing producer_surface query remains only for consumer compatibility and validation; it does not select a policy lane. A same-tenant version owned by another item returns content.release_wrong_version; draft, retired, or failed item trust returns content.version_not_serveable. Curriculum stores no copied verdict: at authoring and launch it asks Content whether the exact resolved version is serveable and release-eligible.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Require a second producer attestation in Content | Keeps a separate immutable producer decision and quality-bar trail beside Content trust. | Duplicates the QC verdict, requires expiring cross-repository credentials, and can leave trusted published content unservable. |
| Maintain an exact-version owner-acceptance allowlist | Can grandfather a bounded set of legacy versions without changing producer evidence. | Creates a second eligibility lane and permanent policy machinery for a one-time ruling. |
| Expose a generic trust-status mutation or let authors submit their own validation verdict | Makes every artifact easy to unblock through one small write. | Turns evidence-derived trust into a caller assertion and gives ordinary authoring automation a release bypass. |
| Let each consumer recompute lifecycle and trust | Avoids a dedicated Content release-eligibility read. | Produces contradictory answers and makes every consumer duplicate Content-owned semantics. |
| Copy release_eligible into Curriculum | Avoids the launch-time Content read. | Goes stale when lifecycle or trust changes and prevents immediate fail-closed revocation. |
Content owns lifecycle, item-level revocation, exact-version QC receipts, and the graph or body it can objectively validate. Defining release_eligible exactly equal to serveable removes credentialed re-attestation, while a narrow server-owned publication command closes the test_spec/test_bank/article authoring gap without a generic trust switch or owner migration. trust_status=failed revokes every version immediately on the next read; no consumer copies or recomputes the verdict.
Tracked decision content-itd-023-trust-based-release-eligibility; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, discover, interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Content writes span QTI-backed base objects, immutable versions, Alpha-only sidecars, relationships, retire semantics, and source migrations. A single generic bulk endpoint would obscure ownership and atomicity, while migration callers still need named source-shaped ingestion rather than hundreds of hand-orchestrated resource calls.
ACCEPTED: SHIP workflow-specific write-through commands plus named ingest adapters: import QTI package, import TimeBack production source-shaped records, create/update base content through the QTI 1EdTech surface, create ordinary immutable base versions through QTI versioning, attach Alpha-only media policy, and author weighted Content-to-KC attribution. Curriculum alone authors current lesson/member placement; Content's curriculum-link relation is a frozen read-only archive. TimeBack imports now preserve immutable exact-version history: identical canonical bytes replay, changed bytes append a hash-bound UUIDv5 exact version with the next version_number and supersedes edge, and a deployed legacy :v1 row is adopted only after complete-projection ownership checks. A coherent public retirement command/readback remains pending under API Axis 11. DEFER arbitrary bulk authoring until three independent tenants request same-file multi-object authoring within 90 days.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Bulk-only | Optimizes high-volume imports and can commit a whole package or catalog batch in one operation. | Makes ordinary authoring cumbersome, obscures per-resource validation/idempotency, and increases partial-failure blast radius. |
| Both arbitrary bulk and per-resource immediately | Covers every authoring and migration pattern from the first release. | Doubles contracts, authorization/audit paths, and atomicity semantics before tenants prove arbitrary bulk is needed. |
Workflow-specific write-through commands make authority and versioning explicit, while named package/source adapters cover legitimate multi-object ingestion. Deferring arbitrary bulk keeps one auditable operation per customer job without blocking production migration.
Tracked decision content-axis-001-write-granularity; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Content is both a catalog and a launchable asset surface: consumers must find items, inspect details, resolve versions, obtain student-safe representations, and follow narrow relationships. Copying upstream internals into one giant response or shipping list-only reads would block either rendering or discovery.
ACCEPTED TARGET (pending launch-resolution completion): SHIP list + detail + narrow sub-collections as Alpha read views over QTI base rows plus extension sidecars. Current public reads include catalog list, content detail with expected_xp, version list, exact version detail containing student_view, the unversioned /student-view route, media, content references, curriculum links, catalog facets, assessment components, answer slots, and effectiveness. There is no separate exact-version /student-view subroute. The accepted target also adds latest-published student-view resolution for new launches with an explicit resolved_content_version_id; current runtime returns version_id from raw latest_version_id instead. Raw QTI internals remain cut.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| List-only | Provides the minimum catalog discovery surface with a small API and predictable response shape. | Cannot retrieve exact versions, student views, components, references, facets, or evidence needed to render and integrate an item. |
| Write-and-remember | Avoids maintaining list/query APIs and lets authors retain ids returned by writes. | Makes existing content undiscoverable, breaks reconciliation, and prevents new consumers from enumerating tenant state. |
List, detail, and narrow sub-collections match the distinct jobs while keeping raw QTI internals cut. Current exact version detail includes student_view, while the unversioned student-view route covers default launch reads; the accepted latest-published launch-resolution response remains incomplete.
Tracked decision content-axis-002-read-shape; generated from architecture/commitments.json as an accepted architecture target; implementation and deployed proof are pending and not deployed. Use cases: render, discover.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Tenant catalogs need deterministic filtering, server-defined ordering, paging, reconciliation, and incremental item sync, but Content semantics require typed columns and governed facets rather than arbitrary SQL or ad hoc parameters. The first release must scale beyond full-list scans without exposing storage internals or claiming a caller-selected sort parameter the API does not implement.
ACCEPTED: SHIP documented filters, deterministic server-defined ordering, limit-based paging with opaque cursors, source_system/source_record_id/source_activity_ref reconciliation filters, and modifiedSince on the item list. The current API does not accept a caller-selected sort parameter. Query fields are limited to typed view columns, extension columns, governed tag definitions, or source-ingest provenance fields; arbitrary SQL-style filters are deferred until three tenants exceed 100,000 Content items and file the same blocked query job within 90 days.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| No query model | Keeps list endpoints and server implementation extremely small. | Forces clients to download entire catalogs, cannot resume sync, and makes source reconciliation impractical at tenant scale. |
| Ad hoc filter parameters | Lets each endpoint add the exact filters requested without designing a shared contract. | Creates inconsistent semantics, undocumented combinations, and string-parsing rules that bypass typed dictionary ownership. |
A bounded common query contract covers the proven catalog and item-sync jobs using fields the dictionary already owns. Opaque cursors and deterministic server-defined ordering preserve paging, while the demand gate prevents premature arbitrary-query machinery.
Tracked decision content-axis-003-query-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Multiple authors can update editable Content item metadata while delivered versions remain immutable. The API exposes ETag/If-Match on item-metadata PATCH and serializes the compare and mutation under database parent-row locks; graph-bearing metadata first acquires the tenant graph fence. Version creation and child-resource writes use Idempotency-Key rather than resource-wide optimistic concurrency.
ACCEPTED: SHIP ETag/If-Match on editable item-metadata PATCH. Item reads return an ETag, missing PATCH precondition returns 428, and a stale If-Match returns 409. Each PATCH transaction locks the item's parent graph rows before rereading and comparing the current representation, so two simultaneous requests from one ETag serialize to one success and one typed conflict instead of losing an update. Graph-bearing metadata also acquires the tenant graph fence before the parent lock. Version creation and child-resource writes use Idempotency-Key, not If-Match; broader per-resource optimistic concurrency is not claimed.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Last-write-wins | Requires no client precondition handling and makes every write appear to succeed. | Silently overwrites concurrent work and cannot explain which representation a client edited. |
| Database locks exposed to clients | Can prevent concurrent edits before they happen and model long authoring sessions explicitly. | Leaks storage mechanics, creates stale-lock recovery, and performs poorly across stateless clients and serverless requests. |
ETag/If-Match is the smallest standard HTTP precondition that identifies the item representation a metadata editor saw. Locking the parent rows before the authoritative reread keeps the comparison, mutation, and ETag advance in one transaction without exposing database locks to clients.
Tracked decision content-axis-004-concurrency-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Imports, item/asset/version creation, KC-tag/reference/facet writes, composition replacement, and content-reference retirement are retried across networks and serverless timeouts. Retrying without a ledger can duplicate or repeat durable mutations, but Content must not create a module-local mechanism when the platform already owns idempotency. The legacy curriculum-link POST is a deterministic 410 and never claims the ledger. Coherent Content-item retirement semantics are pending separately and are not advertised as a current lifecycle command.
ACCEPTED: SHIP Idempotency-Key on all fourteen Content writes that currently use the platform ledger: QTI package import, TimeBack production import, non-QTI content creation, exact test_spec/test_bank/article/video publication, media attach, immutable version creation, weighted KC-tag upsert, weighted KC-tag retirement, catalog-facet definition creation, catalog-facet assignment creation, interactive-composition replacement, content-reference creation, content-reference retirement, and external-link creation. The frozen curriculum-link archive has no current write; its legacy POST returns 410 before any ledger claim. Use platform.idempotency_key as the retry ledger; Content must not create its own. QTI package import, TimeBack production import, non-QTI content creation, exact test_spec/test_bank/article/video publication, immutable version creation, weighted KC-tag upsert, weighted KC-tag retirement, content-reference creation, and content-reference retirement finalize their durable effects and completed replay responses atomically. The other five operations mutate first and complete the ledger separately; a crash in that window can leave the effect committed while the key later fails terminally with 503 rather than replaying the result. Their normal completed retries replay, but exactly-once crash recovery is not claimed. Item-metadata PATCH instead uses If-Match. No coherent public Content-item lifecycle command is currently advertised; item retirement remains pending under API Axis 11.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| No idempotency | Avoids request-ledger storage and lets each command execute independently. | Turns harmless client retries into duplicate content, versions, links, or lifecycle transitions. |
| Content-local idempotency table | Allows Content to tune retention and payload semantics for its own commands. | Duplicates the platform primitive, creates inconsistent replay behavior, and adds another tenant-scoped operational store. |
Name all fourteen ledger-backed Content workflows but reuse platform.idempotency_key for reservation and replay. This preserves one cross-module retry contract and avoids both duplicate effects and a second ledger; item-metadata PATCH remains the separate If-Match workflow.
Tracked decision content-axis-005-idempotency-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Content readers, authors, KC taggers, answer-key readers, and publication reviewers need different capabilities, while tenant and school/class authority already live in platform JWT and People & Orgs. The runtime enforces read:content, author:content, link:kc, read:answer_key, publish:content, and content:*. Trust-based release eligibility is read-only. Automated publication additionally requires service, reviewer, or operator role. link:curriculum is retired with the archive write.
ACCEPTED: SHIP inherited HS256 Bearer JWT tenant routing with roles and the enforced Content scopes read:content, author:content, link:kc, read:answer_key, and publish:content, plus the content:* wildcard. Release eligibility is a read:content operation. link:kc guards weighted /kc-tags writes. publish:content is necessary but not sufficient for the automated publication command: the principal must also carry service, reviewer, or operator role, so writer/demo/teacher tokens cannot publish or cause trust. The retired link:curriculum name is not a current authoring scope because curriculum-link POST is 410. School/class membership authority is inherited from People & Orgs, not invented in Content.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Role-only | Is simple for clients and administrators to understand and reduces token claim complexity. | Cannot separate sensitive answer keys, publishing, linking, and ordinary reads without proliferating broad roles. |
| Content-owned school/class claims | Lets Content authorize contextual reads without depending on People & Orgs at decision time. | Creates a second roster and membership truth that can disagree with platform tenant and enrollment authority. |
Inherit the platform JWT/tenant primitive and add Content-specific scopes for the real capability boundaries. Release eligibility uses read:content; requesting server-owned exact-graph publication requires both publish:content and a privileged operational role. No producer-specific verdict authority exists.
Tracked decision content-axis-006-auth-shape; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Catalog integrations need to observe changed Content items, but learner activity eventing already belongs to Events and no tenant demand yet justifies webhook delivery infrastructure. The current runtime exposes modifiedSince over content_item.updated_at only; independent facet, link, reference, trust, and effectiveness changes do not advance that item cursor unless the parent item is also touched.
ACCEPTED: SHIP poll + modifiedSince for content_item representation changes surfaced by content_item.updated_at. The current item-list cursor does not include independent subcollection changes such as facet assignments, curriculum links, references, trust, or effectiveness unless the parent item itself is updated; broader catalog-change polling remains pending a parent-touch or versioned change-feed implementation. DEFER webhooks until three tenants with production integrations request push delivery for Content changes within 90 days. Learner activity eventing belongs to Events, not Content.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| No eventing or modifiedSince | Avoids change-feed semantics and keeps Content reads stateless. | Forces full catalog polling, increases load, and cannot bound incremental reconciliation windows. |
| Webhooks now | Provides low-latency push and avoids consumer polling. | Adds subscription, signing, retry, ordering, dead-letter, and delivery-observability machinery before proven customer need. |
Stable modifiedSince polling solves the current item-row sync job with the existing read/query model. Naming the subcollection limitation avoids promising a complete catalog change feed. Deferring webhooks avoids a large operational surface, and routing learner actions to Events preserves module semantics.
Tracked decision content-axis-007-eventing-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Content combines QTI validation, platform storage, adapters, and Alpha sidecars, so failures can originate at several layers. Clients need one stable machine envelope and school-language detail rather than knowing which validator or database produced the error.
ACCEPTED: SHIP inherited typed RFC 7807 Problem Details with Content-specific stable codes, requestId, traceId, optional cause/docUrl, and fieldErrors where present. Public detail text is school-language; machine fields remain platform-stable. Current public Problems do not expose a distinct upstream-provenance response field.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Raw QTI or database errors | Preserves full low-level diagnostics and requires no public error mapping. | Leaks internals, changes when dependencies change, and cannot support one client remediation flow. |
| Plain text errors | Is compact and readable without a structured error schema. | Cannot carry stable codes, field errors, trace ids, or upstream provenance for automated recovery. |
Reuse the platform RFC 7807 primitive and specialize only stable Content codes and public wording. This gives every client one parser while keeping technical traceability and human remediation together.
Tracked decision content-axis-008-error-envelope; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Content base and sidecar rows are tenant-scoped, and the platform already requires both a tenant URL segment and a matching JWT claim. Choosing only one signal would either permit ambiguous routing or make accidental cross-tenant URLs harder to detect.
ACCEPTED: SHIP tenant in URL plus JWT tenant claim match against platform.tenant. QTI base rows and Alpha extension rows carry tenant_id as foreign-key scope, and public Content views expose workspace_id from that base scope. Demo uses tenantId=demo on the same deployment; real tenants use operator-minted JWTs. Current media, content-reference, curriculum-link, and interactive-composition sidecars can accept an optional version UUID independently of the paired artifact/tenant identity; route/JWT isolation remains active, but same-item/tenant version integrity for those sidecars is pending.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Tenant only in JWT | Keeps resource URLs stable and derives all tenancy from authenticated context. | Makes logs/caches/routes less explicit and cannot detect a caller targeting the wrong tenant URL because none is present. |
| Tenant only in URL | Makes routing visible and straightforward for clients and infrastructure. | Treats a user-controlled path as authority and cannot prove the caller is entitled to that tenant. |
The URL supplies explicit routing and the JWT supplies authority; requiring equality turns duplication into a safety check. Content inherits the platform tenant and workspace projection rather than inventing another partition key.
Tracked decision content-axis-009-tenant-routing; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Consumers need to know whether QTI-backed assets passed sanctioned conformance, whether first-class test specs/banks have a complete exact dependency graph, and whether an authored article exact version satisfies its stored hash and student-safe body contract. A generic mutable conformance runner or trust setter would combine catalog reads with caller-controlled release authority; the missing product need is only one deterministic Content-owned publication computation.
ACCEPTED: SHIP safe trust/provenance reads plus one narrow server-owned publication computation for first-class test_spec/test_bank/article/video artifacts. POST /items/{contentId}/publish accepts only the exact content_version_id, never a trust field; checks current-version identity and the complete kind-owned blueprint, graph, body, verified media, accessibility, cue, and exact-dependency contract; then atomically publishes and appends the internal QC receipt for that exact immutable version. QTI questions/forms earn exact receipts only through QTI import QC. Item trust is the compatible summary and immediate all-version revocation switch, never positive authority for a later version. This is not a generic conformance runner or public trust mutation. A named external run receipt may enrich evidence later but is not a release prerequisite.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| No conformance evidence | Keeps the Content contract focused on assets and avoids coupling to release tooling. | Forces consumers to trust compatibility claims without a traceable run, version, or validation result. |
| Public generic conformance runner | Lets any consumer validate an asset on demand and obtain fresh evidence. | Adds expensive mutable control, abuse risk, and a second execution path for checks owned by release/QTI tooling. |
| Per-course owner-ruling trust migrations | Can release a reviewed cohort without adding an API. | Makes normal course publication exceptional, slow, and dependent on bespoke database changes. |
Content exposes compatible item trust reads and one deterministic publish command that validates only state it owns. Release is lifecycle plus the failed-trust revocation lever (owner ruling #2534); each passing version records an append-only server-owned receipt as provenance. That closes ordinary spec/bank/article/video publication without making Content a generic job runner or letting a caller choose trust. Video uses the existing media and publication endpoints after Content fetches, verifies, stores, and re-serves the narrowly allowlisted first-party bytes and validates its exact composition, accessibility, and QTI dependencies.
Tracked decision content-axis-010-conformance-evidence; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.
Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Reusable Content is referenced by historical Events and Results, while learner runtime data, secrets, and credentials have different retention and deletion duties. The storage model can retain rows and the API exposes no DELETE route, but current PATCH retirement combinations are incoherent: retired_at mutation reads back as not-found, while status='retired' alone remains readable.
ACCEPTED TARGET (pending coherent retirement API): Preserve content referenced by Events or Results in storage rather than hard-deleting it, and keep learner-data deletion in Events/Results/QTI runtime rather than Content. Content views and extension rows contain no direct student runtime data, secrets, app credentials, raw tokens, or student PII. Current Content exposes no DELETE route. Its PATCH retirement combinations are not yet a coherent public contract: setting retired_at persists the mutation but current readback returns not-found, while setting only status='retired' leaves the row readable. A tested retirement command with deterministic response, discovery hiding, and authorized historical exact-version readback is required before this decision becomes active.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Hard-delete content immediately | Removes obsolete assets and storage promptly with a simple lifecycle. | Breaks historical event/result references, replay, audits, and package provenance before retention obligations expire. |
| Keep learner data on content rows | Makes an asset response self-contained for resume, progress, and personalization. | Mixes PII/runtime facts into reusable material, duplicates Events/Results, and makes access/deletion far more dangerous. |
The accepted target preserves referenced blank assets while keeping learner data in its owning runtime modules. Marking the decision pending prevents the storage-retention intention from being mistaken for a working public retirement API.
Tracked decision content-axis-011-privacy-retention; generated from architecture/commitments.json as an accepted architecture target; implementation and deployed proof are pending and not deployed. Use cases: interoperate.
Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.
Content's core customer job is catalog discovery and relationship inspection, so callers cannot rely only on ids remembered from writes. At the same time, generic lists for every QTI table would recreate the expert upstream surface Alpha intentionally cuts.
ACCEPTED: SHIP one documented list endpoint per public Content view/collection: content items, versions under one item, media under one item, content references under one item, weighted KC tags under one item, the frozen curriculum-link archive under one item, catalog facets under one item, and trust/effectiveness reads. GET /kc-tags is authoritative for governed Content-to-KC alignment; kc_refs is only a discovery hint and curriculum-links is historical reconciliation evidence. DEFER generic raw qti.* list endpoints until an approved expert Content sub-surface is scheduled in loop state or three tenant integrations request raw QTI row browsing within 90 days despite the published provenance links.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Write-and-remember | Avoids list endpoint design and works for a single authoring client that retains every returned id. | Blocks cold integrations, migration reconciliation, audits, and recovery when ids were not retained. |
| Generic list for every table | Maximizes discoverability and supports unforeseen expert queries without adding endpoints later. | Leaks raw QTI/storage internals, multiplies auth/query contracts, and undermines the bounded Alpha module. |
Document one list per public Content collection that supports a real discover or interoperate job, and defer raw QTI browsing behind a clear demand gate. This makes the catalog complete without turning Alpha into a database explorer.
Tracked decision content-axis-012-list-endpoints; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover.
Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals. Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.
Incept must exercise the complete mastery-gate learner loop before go-live, but ordinary Content release eligibility correctly rejects draft or untrusted banks and forms. Publishing/trusting merely for testing would collapse the boundary between validation and production governance.
Content owns a separate, fail-closed test-serve authorization lane for the demo tenant. Its routes require a trusted service, demo, or operator role. One soft-revocable grant names an exact People & Orgs identity and producer surface; the identity must independently resolve to active enabled reality=test or reality=synthetic. The lane may authorize exact draft or published practice-eligible test_bank and test versions when trust has not failed. Until a separately guarded decoupling ships, the exceptional lane also requires a matching legacy producer-surface requirement row and treats the latest rejected/revoked producer verdict or blocked/revoked legacy release status as an additional deny condition. Those retained legacy reads never gate or select ordinary trust-based release eligibility. The lane never changes lifecycle, trust, ordinary serveability, or ordinary release eligibility. Content atomically revalidates the exact bank/form membership and appends an immutable authorization receipt before Curriculum returns a test-served form. Real identities and every non-demo tenant remain on the ordinary release path.
| Alternative | Benefit | Cost / reason rejected |
|---|---|---|
| Publish and trust draft banks only to exercise them | Reuses the existing launch path with no new runtime contract. | Turns a test into a production-governance mutation and can expose unfinished material to real learners. |
| Honor a caller-supplied preview header or flag | Easy for a producer UI to enable on demand. | A bearer or client bug could turn the flag into an exposure bypass; the identity and scope would not be independently governed. |
| Let Curriculum keep its own test-student allowlist | Keeps gate launch decisions in one runtime. | Duplicates identity and Content state authority, and lets a consumer override Content lifecycle/trust without a Content audit. |
Content can retain final authority over the exact blank assets while People & Orgs independently proves the identity is synthetic/test. The demo-only, soft-revocable grant does not alter ordinary release semantics, and final bank-membership revalidation plus an append-only receipt makes the exceptional serve both bounded and reviewable.
Tracked decision content-itd-024-governed-test-serve; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render, interoperate.
| Axis | Decision | Dictionary coverage |
|---|---|---|
| Write granularity | Per-resource writes plus named QTI package and TimeBack imports; no arbitrary bulk mutation. Every active test-bank graph write is bounded by its complete post-write fanout. | Public and repository/direct active embeds and bank_member writes, KC-tag upsert/revival, QTI package import, and TimeBack materialization budget even cardinality-neutral active repository/materializer replays. One write may affect at most 1,024 banks; every affected bank may contain at most 1,024 active members and 4,096 joined form/question/active-KC facts. The ceilings are inclusive, so 1,025 affected banks or members and 4,097 joined facts reject. A completed API idempotency replay returns its prior result without executing a graph writer. |
| Read shape | List + detail + narrow sub-collections for the public Content catalog. | Each public object lists its collection/detail/sub-collection reads; raw QTI internals are isolated in Cuts and routing. |
| Query model | Typed filters, deterministic server-defined ordering, cursor paging, and modifiedSince on content_item.updated_at; no caller-selected sort parameter. | Supported Query Fields lists every allowed parameter, source field, value set, and behavior, including governed catalog-facet filters that must promote to real columns before they become joins or computed inputs. |
| Concurrency model | Atomic ETag/If-Match compare-and-update on editable item-metadata PATCH; test-bank graph writers additionally serialize through a tenant graph fence and budget the post-write graph in the owning transaction. | content_version.etag supplies the token used by PATCH /items/{contentId}; missing If-Match returns 428 and a stale value returns 409. The PATCH transaction locks parent rows before its authoritative reread and comparison, so simultaneous same-ETag requests produce one success and one typed conflict. Active graph writers first acquire the tenant graph fence, which covers affected-bank discovery, the inclusive 1,024 affected-bank and member ceilings, the inclusive 4,096 joined-fact ceiling, mutation, parent touch, and idempotency finalization. A rejected conflict or cap-plus-one write rolls back with no lasting graph, timestamp/ETag, or fresh ledger effect; retirement remains available to reduce an over-limit graph. |
| Idempotency model | Idempotency-Key on fourteen Content writes through the shared platform ledger; atomic effect-plus-replay finalization exists for QTI import, TimeBack production import, non-QTI content creation, exact test_spec/test_bank/article/video publication, immutable version creation, weighted KC-tag upsert, weighted KC-tag retirement, content-reference creation, and content-reference retirement. | Write examples call out Idempotency-Key and content_problem includes conflict behavior. Publication and the graph-bearing writes use atomic claim+effect+finalization. Completed requests replay; the other five operations mutate before separately completing the ledger, so their crash window can leave a committed effect and terminal 503 claim rather than an exactly-once replay. |
| Auth shape | Platform JWT tenant routing with current scopes read:content, author:content, link:kc, read:answer_key, publish:content, and content:*. Release eligibility is read-only. link:kc guards weighted KC-tag writes; publish:content plus service/reviewer/operator role guards automatic spec/bank/article/video publication; the retired curriculum-link write always returns 410. | Every field row records publicAccess/sensitivity; answer_key.required_scope names read:answer_key. Exact-version release eligibility is a read over Content lifecycle and item revocation state; failed trust is the single revocation lever (owner ruling #2534). The publication recipe documents the dual role+scope guard and caller-verdict prohibition. |
| Eventing model | Poll content_item representation changes with modifiedSince; Content is not the learner activity stream or a complete subcollection change feed. | content_item.updated_at is the documented item-list polling input. Independent facet, link, reference, trust, and effectiveness changes require separate reads until a parent-touch or versioned change feed is implemented. |
| Error envelope | Typed RFC 7807 Problem Details with Content-specific codes. | content_problem and all problem_code mirrors define identical code values and meanings. Test-bank graph resource overflow returns HTTP 409 content.invalid_test_bank_member_contract: fieldErrors.name=bank_members for a 1,025th active member, and fieldErrors.name=form_graph for a 1,025th affected bank or 4,097th joined form/question/active-KC fact. No rejected transaction leaves a lasting graph, parent timestamp/ETag, or fresh idempotency-ledger effect. Retire active member/embed/KC-tag facts or split shared forms/questions/banks, then retry. |
| Tenant routing | Workspace/tenant comes from platform routing and JWT, not caller-supplied row bodies. | content_item.workspace_id and related constraints require route tenant/JWT alignment. |
| Conformance evidence | Expose trust evidence reads and one narrow server-owned exact-version publication computation for test_spec/test_bank/article; do not expose a generic trust mutation or caller-selected conformance verdict. Video fails closed until its kind-owned validator is complete. | content_trust_evidence defines item-level revocation state; recipe-content-publication defines the exact receipt, automatic checks, auth guard, atomic effect, and fail-closed response. |
| Privacy / retention | Accepted target: retain referenced content instead of hard deleting it; coherent public retirement write/readback is pending. | Current storage has status/retired_at and the API exposes no DELETE route, but PATCH retirement combinations do not yet provide deterministic success, discovery hiding, and authorized historical readback. Treat retirement as pending, not a working command. |
| List endpoints | One documented list endpoint per public Content collection; no generic raw qti.* row browsing. | Public collections, object query examples, catalog-facet registry/assignment objects, and Cuts and routing separate catalog lists from internal QTI provenance. |
data_dictionary/source/content-alpha-data-dictionary.json.architecture/commitments.json.build-content-docs.mjs generates the deliverable pages, the Integration dictionary mirror, and their byte-identical protected-root site/content/alpha publication mirrors without live input.content-doc-conformance.mjs rejects generated drift, missing/extra kinds, or a public stimulus.