Platform3 / Content / Alpha

TimeBack Platform Content Alpha Architecture

The public Content contract is generated from tracked sources. The owning data dictionary defines the value space; locked ITDs explain the architectural decisions.

Generated from tracked source36 decisions12 public kinds

Load-bearing contract

Exactly twelve public Content kinds

questiontesttest_spectest_bankarticlevideoaudioimageinteractivemedia_assetexternal_linkscript
Stimulus is question anatomy. QTI assessmentStimulus is question anatomy (stimulus + root + options + wrong-answer explanations), not an independently addressable Content row.

Owner: Content Alpha data dictionary. Decision: content-itd-003-content-kind-taxonomy.

Architecture contract

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.

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.

Binding storage model

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 asset identity

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.

  • one video row
  • one transcript media_asset row
  • content_reference relationship_kind=transcript_for
  • course/unit/playlist membership lives in Curriculum

Alpha read resources

SQL views over the approved qti.* and platform.* base tables. Rename, cut, and restrict add no storage.

  • alpha.content_item_view
  • alpha.content_version_view
  • alpha.media_asset_view
  • alpha.answer_slot_view
  • alpha.assessment_component_view
  • alpha.scoring_plan_view

Alpha extension storage

Tables that contain only new Alpha columns plus foreign keys to base rows. No extension table may copy a 1EdTech-sourced field.

  • alpha.content_item_extension.expected_xp
  • alpha.assessment_test_extension
  • alpha.media_asset_extension
  • alpha.content_reference
  • alpha.content_kc_tag
  • alpha.content_curriculum_link (frozen archive)
  • alpha.content_catalog_facet
  • alpha.content_catalog_facet_assignment
  • alpha.content_trust_evidence
  • alpha.content_test_serve_grant
  • alpha.content_test_serve_authorization

Results-owned read dependencies

Content may read Results-owned public views, but Content never creates, migrates, writes, or backfills those objects.

  • alpha.content_effectiveness public Results view with tenant_id, object_id, and data JSONB

Core writes

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.

  • QTI package import
  • QTI artifact/version authoring save
  • manifest-resource/webcontent creation

Forbidden downstream output

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.

  • Duplicated title/language/latest_version_id
  • Copied delivery_json/authoring_json
  • Copied correct_response/processing_rule

Provenance and lineage contract

No hidden lineage store. Content has no separate content_lineage table or generic lineage object. The complete contract is the four named dictionary primitives below; importers and consumers must not invent a fifth store.
JobOwning primitiveMeaning
Source/import identityadapter/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 qualitycontent_catalog_facet_assignment (facet_key=provenance|quality_rating)Stores governed catalog discovery values and safe assignment provenance without copying the base asset.
Validation trust evidencecontent_trust_evidenceLinks the Content identity to the owning conformance or validation evidence and its checked status.
Asset-to-source-asset production lineagecontent_reference (relationship_kind=source_file_for|source_script_for)Relates a produced Content asset to the exact reusable Content asset/version that sourced it.

Expected XP calibration

Fieldexpected_xp
TypeNUMERIC(10,2)
Requiredtrue
StorageAlpha extension/config column in alpha.content_item_extension; no 1EdTech source field is copied.

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.

Version launch semantics

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.

Generated alias map

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 nameKindChangeSourceStorageMeaningOwner ITD
Content modulemodulerenameQTI 1EdTech surfaceRead view / projection over named sourcePublic Alpha module name for things students touch.content-itd-001-module-boundary-and-alias-map
content_item.content_idfieldrenameqti.artifact.artifact_idRead view / projection over named sourcecontent-itd-002-shared-qti-truth-no-content-fork
content_item.workspace_idfieldrenameplatform.tenant.tenant_idRead view / projection over named sourcecontent-axis-009-tenant-routing
content_item.content_kindfieldrestrictView expression over qti.artifact.artifact_kind and qti.package_resource.resource_type, plus extension-only kind for QTI-anchored Content sidecarsRead view / projection over named sourcecontent-itd-003-content-kind-taxonomy
content_item.expected_xpfieldextendalpha.content_item_extension.expected_xpContent-owned extend-only sidecarcontent-itd-015-expected-xp-content-config
questionobjectrenameqti.artifact where artifact_kind = itemRead view / projection over named sourcecontent-itd-003-content-kind-taxonomy
testobjectrenameqti.artifact where artifact_kind = testRead view / projection over named sourcecontent-itd-003-content-kind-taxonomy
articleobjectextendalpha.content_item_extensionContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
videoobjectextendalpha.content_item_extension plus alpha.media_asset_extensionContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
audioobjectextendalpha.content_item_extension plus alpha.media_asset_extensionContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
imageobjectextendalpha.content_item_extension plus alpha.media_asset_extensionContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
interactiveobjectextendalpha.content_item_extensionContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
media_assetobjectextendalpha.media_asset_extensionContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
external_linkobjectextendalpha.external_link_extensionContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
scriptobjectextendqti.artifact base identity plus alpha.content_item_extension content_kind_override=scriptContent-owned extend-only sidecarFirst-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.titlefieldrenameqti.artifact.titleRead view / projection over named sourcecontent-itd-001-module-boundary-and-alias-map
content_item.languagefieldrenameqti.artifact.languageRead view / projection over named sourcecontent-itd-001-module-boundary-and-alias-map
content_item.source_identifierfieldrenameqti.artifact.qti_identifierRead view / projection over named sourcecontent-itd-001-module-boundary-and-alias-map
content_item.latest_version_idfieldrenameqti.artifact.latest_version_idRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.version_idfieldrenameqti.artifact_version.artifact_version_idRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.version_numberfieldrenameqti.artifact_version.version_numberRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.student_viewfieldrenameqti.artifact_version.delivery_jsonRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.editing_draftfieldrenameqti.artifact_version.authoring_jsonRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.canonical_xmlfieldrenameqti.artifact_version.canonical_xmlRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
media_asset.media_idfieldrenameqti.package_file.package_file_idRead view / projection over named sourcecontent-itd-004-non-qti-media-extension
media_asset.media_kindfieldextendalpha.media_asset_extension.media_kindContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
media_asset.media_typefieldrenameqti.package_file.media_typeRead view / projection over named sourcecontent-itd-004-non-qti-media-extension
media_asset.byte_lengthfieldrenameqti.package_file.byte_lengthRead view / projection over named sourcecontent-itd-004-non-qti-media-extension
external_link.external_urlfieldextendalpha.external_link_extension.external_urlContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
answer_slot.identifierfieldrenameqti.variable_declaration.identifier where variable_kind = responseRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
answer_slot.cardinalityfieldrenameqti.variable_declaration.cardinalityRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
answer_slot.base_typefieldrenameqti.variable_declaration.base_typeRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
answer_key.correct_responsefieldrestrictqti.variable_declaration.correct_responseRead view / projection over named sourceTeacher/authorized read only, never a student answer.content-itd-008-answer-slots-not-student-answers
assessment_component.component_rolefieldrename + restrictqti.component.element_name mapped to test/section/part/question/question_ref/distractor/rubric/preauthored_feedbackRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_component.display_textfieldrename + restrictqti.component.text_valueRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_test.test_typefieldextendalpha.assessment_test_extension.test_typeContent-owned extend-only sidecarcontent-itd-006-typed-content-and-test-fields
assessment_test.is_mastery_gatefieldextendalpha.assessment_test_extension.is_mastery_gateContent-owned extend-only sidecarcontent-itd-006-typed-content-and-test-fields
assessment_test.test_family_idfieldextendalpha.assessment_test_extension.test_family_idContent-owned extend-only sidecarOptional 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_idfieldextendalpha.content_item_extension.subject_idContent-owned extend-only sidecarcontent-itd-006-typed-content-and-test-fields
content_item.target_grade_idfieldextendalpha.content_item_extension.target_grade_idContent-owned extend-only sidecarcontent-itd-006-typed-content-and-test-fields
content_curriculum_link.curriculum_node_idfieldextendalpha.content_curriculum_link.curriculum_node_id referencing curriculum.nodeContent-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_ref.content_idreference-contract-fieldrenameqti.artifact.artifact_idRead view / projection over named sourcecontent-itd-016-uniform-content-reference-contract
content_ref.content_version_idreference-contract-fieldrenameqti.artifact_version.artifact_version_idRead view / projection over named sourcecontent-itd-016-uniform-content-reference-contract
content_ref.content_kindreference-contract-fieldrestrictView expression over qti.artifact.artifact_kind and qti.package_resource.resource_type plus extension sidecar kindRead view / projection over named sourcecontent-itd-016-uniform-content-reference-contract
content_reference.parent_content_idreference-contract-fieldextendalpha.content_reference.parent_content_artifact_id -> alpha.content_reference_view.parent_content_idContent-owned extend-only sidecarThe 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_idreference-contract-fieldextendalpha.content_reference.parent_content_version_id -> alpha.content_reference_view.parent_version_idContent-owned extend-only sidecarNullable exact-version member of the parent content_ref shape.content-itd-016-uniform-content-reference-contract
content_reference.parent_content_kindreference-contract-fieldextendalpha.content_reference.parent_content_kindContent-owned extend-only sidecarGoverned twelve-kind discriminator in the parent content_ref shape.content-itd-016-uniform-content-reference-contract
content_reference.referenced_content_idreference-contract-fieldextendalpha.content_reference.referenced_content_artifact_id -> alpha.content_reference_view.referenced_content_idContent-owned extend-only sidecarThe 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_idreference-contract-fieldextendalpha.content_reference.referenced_content_version_id -> alpha.content_reference_view.referenced_version_idContent-owned extend-only sidecarNullable exact-version member of the referenced content_ref shape.content-itd-016-uniform-content-reference-contract
content_reference.referenced_content_kindreference-contract-fieldderivealpha.content_reference_view.referenced_content_kind derived from referenced qti.artifact + alpha.content_item_extensionSee named source and owner ITDRead-only governed twelve-kind discriminator; no alpha.content_reference table column exists.content-itd-016-uniform-content-reference-contract
content_reference.relationship_kindfieldextendalpha.content_reference.relationship_kindContent-owned extend-only sidecarcontent-itd-016-uniform-content-reference-contract
content_item.source_record_idfieldextendalpha.content_item_extension.source_record_idContent-owned extend-only sidecarcontent-itd-018-source-shaped-production-ingest
content_item.source_activity_reffieldextendalpha.content_item_extension.source_activity_refContent-owned extend-only sidecarcontent-itd-018-source-shaped-production-ingest
catalog_facet.subjectcatalog-facetextendalpha.content_catalog_facet_assignment value/ref for subjectContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.grade_bandcatalog-facetextendalpha.content_catalog_facet_assignment value/ref for grade_bandContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.languagecatalog-facetextendalpha.content_catalog_facet_assignment value/ref for languageContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.accessibility_profilecatalog-facetextendalpha.content_catalog_facet_assignment value/ref for accessibility_profileContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.rights_licensecatalog-facetextendalpha.content_catalog_facet_assignment value/ref for rights_licenseContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.provenancecatalog-facetextendalpha.content_catalog_facet_assignment value/ref for provenanceContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.quality_ratingcatalog-facetextendalpha.content_catalog_facet_assignment value/ref for quality_ratingContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.kc_refscatalog-facetextendalpha.content_catalog_facet_assignment value/ref for kc_refsContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.standards_framework_refscatalog-facetextendalpha.content_catalog_facet_assignment value/ref for standards_framework_refsContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.content_kindcatalog-facetextendalpha.content_catalog_facet_assignment value/ref for content_kindContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.expected_xp_bandcatalog-facetextendalpha.content_catalog_facet_assignment value/ref for expected_xp_bandContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
catalog_facet.modalitycatalog-facetextendalpha.content_catalog_facet_assignment value/ref for modalityContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_item.subtypefieldextendalpha.content_item_extension.subtypeContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
content_item.dokfieldextendalpha.content_item_extension.dokContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
decodable.phonics_constraintfieldextendalpha.decodable_extension.phonics_constraintContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
content_reference.form_indexreference-contract-fieldextendalpha.content_reference.form_index where relationship_kind = bank_memberContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test.assessment_rolefieldextendalpha.assessment_test_extension.assessment_roleContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_specobjectextendqti.artifact where artifact_kind = test, assessment_role = spec, plus alpha.test_spec_extensionContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.kc_coveragefieldextendalpha.test_spec_extension.kc_coverageContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.passing_rulefieldextendalpha.test_spec_extension.passing_ruleContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.min_formsfieldextendalpha.test_spec_extension.min_formsContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.max_item_overlapfieldextendalpha.test_spec_extension.max_item_overlapContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_bankobjectextendqti.artifact where artifact_kind = test, assessment_role = bank, plus alpha.test_bank_extensionContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_bank.spec_idfieldextendalpha.test_bank_extension.spec_idContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
content_effectiveness.validated_by_countread-dependency-fieldrestrictResults-owned alpha.content_effectiveness.data ->> 'validated_by_count'Read view / projection over named sourcecontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.failed_by_countread-dependency-fieldrestrictResults-owned alpha.content_effectiveness.data ->> 'failed_by_count'Read view / projection over named sourcecontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.effectiveness_rateread-dependency-fieldrestrictResults-owned alpha.content_effectiveness.data ->> 'effectiveness_rate'Read view / projection over named sourcecontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.last_recomputed_atread-dependency-fieldrestrictResults-owned alpha.content_effectiveness.data ->> 'last_recomputed_at'Read view / projection over named sourcecontent-itd-009-content-effectiveness-derived-rollup
content_html_body_contractobjectextendVirtual Content value-space over qti.artifact_version authoring_json/delivery_json; no Alpha tableContent-owned extend-only sidecarOwns 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.htmlfieldextendqti.artifact_version.authoring_json/delivery_json HTML projectionContent-owned extend-only sidecarNo new Content storage table.content-itd-021-tb-markup-body-contract
content_html_body_contract.tb_markup_tokenfieldextendContent-owned value-space inside the version HTML bodyContent-owned extend-only sidecarDictionary contract only; no standalone token table.content-itd-021-tb-markup-body-contract
content_html_body_contract.catalog_idreffieldextendContent HTML body data-catalog-idref attributeContent-owned extend-only sidecarStored only inside the immutable version body, including TimeBack import versions.content-itd-021-tb-markup-body-contract
content_html_body_contract.catalog_entry_idfieldextendContent HTML body local catalog entry idContent-owned extend-only sidecarLocal to one immutable version body, including TimeBack import versions.content-itd-021-tb-markup-body-contract
content_html_body_contract.qti_xml_reffieldextendReference from Content HTML catalog to QTI-owned XML/scoringContent-owned extend-only sidecarReference only; QTI remains the storage owner.content-itd-021-tb-markup-body-contract
content_html_body_contract.renderer_reffieldextendCurriculum course_component_member.renderer_refContent-owned extend-only sidecarCross-module reference; renderer selection is not stored on Content.content-itd-021-tb-markup-body-contract
content_html_body_contract.renderer_messagefieldextendSanctioned renderer integration runtime protocolContent-owned extend-only sidecarRuntime protocol only; not Content storage.content-itd-021-tb-markup-body-contract
content_html_body_contract.progress_event_contractfieldextendEvents/Results learner-progress seam consumed by renderersContent-owned extend-only sidecarNo renderer-private or Content progress store.content-itd-021-tb-markup-body-contract
interactive_compositionobjectextendContent publicInteractiveComposition aggregate over active alpha.interactive_composition rowsContent-owned extend-only sidecarPublic 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_idfieldextendpublicInteractiveComposition.content_id from active alpha.interactive_composition.content_artifact_id rowsContent-owned extend-only sidecarBase Content identity; no copied artifact fields.content-itd-022-interactive-composition
interactive_composition.version_idfieldextendpublicInteractiveComposition.version_id from active alpha.interactive_composition.content_version_id rowsContent-owned extend-only sidecarcontent-itd-022-interactive-composition
interactive_composition.composition_typefieldextendpublicInteractiveComposition.composition_type from active alpha.interactive_composition rowsContent-owned extend-only sidecarcontent-itd-022-interactive-composition
interactive_composition.media_asset_reffieldextendpublicInteractiveComposition media_asset_ref built from active alpha.interactive_composition.media_package_file_id rowsContent-owned extend-only sidecarReference only; media bytes remain upstream.content-itd-022-interactive-composition
interactive_composition.duration_secondsfieldextendpublicInteractiveComposition.duration_seconds from active alpha.interactive_composition rowsContent-owned extend-only sidecarcontent-itd-022-interactive-composition
interactive_composition.segmentsfieldextendpublicInteractiveComposition segments aggregate from active row sequence/segment/question-reference fieldsContent-owned extend-only sidecarAPI aggregate only; row fields remain in exact Physical DDL.content-itd-022-interactive-composition
interactive_composition.cuesfieldextendpublicInteractiveComposition cues aggregate from active row timestamp/question-reference fieldsContent-owned extend-only sidecarAPI aggregate only; referenced questions remain independent QTI-backed Content rows.content-itd-022-interactive-composition
interactive_composition.validation_contractfieldderivepublicInteractiveComposition derived validation contract from composition_type and content-itd-022See named source and owner ITDRead-only derived projection; no physical JSON validation-contract column.content-itd-022-interactive-composition
interactive_composition.updated_atfieldderivepublicInteractiveComposition latest active row created_at or parent Content updated_atSee named source and owner ITDRead-only aggregate timestamp; not learner state.content-itd-022-interactive-composition
content_itemobjectrestrictQTI 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 sourceThe catalog row for one student-touchable thing, across versions.content-itd-001-module-boundary-and-alias-map
content_item.usage_scopefieldextendalpha.content_item_extension.usage_scopeContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
content_item.source_systemfieldextendsource_systemContent-owned extend-only sidecarcontent-itd-001-module-boundary-and-alias-map
content_item.statusfieldextendstatusContent-owned extend-only sidecarcontent-itd-007-versioned-authoring-and-student-view
content_item.created_atfieldrenameqti.artifact.created_atRead view / projection over named sourcecontent-axis-007-eventing-model
content_item.updated_atfieldextendupdated_atContent-owned extend-only sidecarcontent-axis-007-eventing-model
content_item.retired_atfieldextendretired_atContent-owned extend-only sidecarcontent-axis-011-privacy-retention
content_versionobjectrestrictQTI-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 sourceOne immutable exact edition of a content item.content-itd-007-versioned-authoring-and-student-view
content_version.content_idfieldrenameqti.artifact_version.artifact_idRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.content_hashfieldrename + extendqti.artifact_version.xml_hashContent-owned extend-only sidecarcontent-axis-004-concurrency-model
content_version.supersedes_version_idfieldrenameqti.artifact_version.supersedes_version_idRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.etagfieldextendetagContent-owned extend-only sidecarcontent-axis-004-concurrency-model
content_version.created_atfieldrenameqti.artifact_version.created_atRead view / projection over named sourcecontent-itd-007-versioned-authoring-and-student-view
content_version.created_byfieldrenameqti.artifact_version.created_byRead view / projection over named sourcecontent-axis-011-privacy-retention
media_asset.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
media_asset.version_idfieldextendversion_idContent-owned extend-only sidecarcontent-itd-007-versioned-authoring-and-student-view
media_asset.content_hashfieldrenameqti.package_file.content_hashRead view / projection over named sourcecontent-itd-004-non-qti-media-extension
media_asset.storage_pathfieldrenameqti.package_file.package_pathRead view / projection over named sourcecontent-itd-004-non-qti-media-extension
media_asset.delivery_urlfieldextenddelivery_urlContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
media_asset.metadatafieldrenameqti.package_file.metadataRead view / projection over named sourcecontent-axis-011-privacy-retention
media_asset.created_atfieldrenameqti.package_file.created_atRead view / projection over named sourcecontent-itd-004-non-qti-media-extension
external_link.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
external_link.display_urlfieldextenddisplay_urlContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
external_link.open_policyfieldextendopen_policyContent-owned extend-only sidecarcontent-itd-004-non-qti-media-extension
external_link.last_validated_atfieldextendlast_validated_atContent-owned extend-only sidecarcontent-axis-010-conformance-evidence
decodableobjectextendQTI/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 sidecarPhonics-constraint sidecar for content_item rows where content_kind='article' and subtype='decodable'.content-itd-019-governed-subtypes-within-ten-kinds
decodable.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
decodable.constraint_set_idfieldextendconstraint_set_idContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
decodable.policy_versionfieldextendpolicy_versionContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
decodable.updated_atfieldextendupdated_atContent-owned extend-only sidecarcontent-axis-007-eventing-model
test.content_idfieldrenameqti.artifact.artifact_idRead view / projection over named sourcecontent-itd-020-test-spec-and-test-bank
test.test_typefieldextendalpha.assessment_test_extension.test_typeContent-owned extend-only sidecarcontent-itd-006-typed-content-and-test-fields
assessment_testobjectextendQTI 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 sidecarTyped Alpha fields for QTI-backed test, test_spec, and test_bank rows.content-itd-019-governed-subtypes-within-ten-kinds
assessment_test.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-006-typed-content-and-test-fields
assessment_test.assessment_rolefieldextendassessment_roleContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
test_spec.spec_idfieldextendalpha.test_spec_extension.spec_idContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.assessment_rolefieldextendassessment_roleContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
test_spec.item_type_mixfieldextendalpha.test_spec_extension.item_type_mixContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.difficulty_constraintsfieldextendalpha.test_spec_extension.difficulty_constraintsContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.enemy_item_rulefieldextendalpha.test_spec_extension.enemy_item_ruleContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_spec.updated_atfieldextendupdated_atContent-owned extend-only sidecarcontent-axis-007-eventing-model
test_bank.bank_idfieldextendalpha.test_bank_extension.bank_idContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_bank.assessment_rolefieldextendassessment_roleContent-owned extend-only sidecarcontent-itd-019-governed-subtypes-within-ten-kinds
test_bank.membership_rulefieldextendalpha.test_bank_extension.membership_ruleContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
test_bank.updated_atfieldextendupdated_atContent-owned extend-only sidecarcontent-axis-007-eventing-model
content_curriculum_linkobjectextendCASE/Content supplies curriculum node identity; Alpha Content owns only the coverage relationship.Content-owned extend-only sidecarA real relationship from Content to the shared curriculum graph.content-itd-005-curriculum-links-are-relationships
content_curriculum_link.content_curriculum_link_idfieldextendcontent_curriculum_link_idContent-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_curriculum_link.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_curriculum_link.version_idfieldextendversion_idContent-owned extend-only sidecarcontent-itd-007-versioned-authoring-and-student-view
content_curriculum_link.link_kindfieldextendlink_kindContent-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_curriculum_link.active_fromfieldextendactive_fromContent-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_curriculum_link.active_untilfieldextendactive_untilContent-owned extend-only sidecarcontent-axis-011-privacy-retention
content_catalog_facetobjectextendQTI 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 sidecarGoverned catalog-facet definition for discovery metadata.content-itd-014-catalog-facets-sidecar
content_catalog_facet.facet_keyfieldextendfacet_keyContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet.labelfieldextendlabelContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet.value_typefieldextendvalue_typeContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet.grainfieldextendgrainContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet.allowed_valuesfieldextendallowed_valuesContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet.statusfieldextendstatusContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet.updated_atfieldextendupdated_atContent-owned extend-only sidecarcontent-axis-007-eventing-model
content_catalog_facet_assignmentobjectextendThe 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 sidecarAssignment of one governed catalog facet value to Content.content-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.facet_assignment_idfieldextendfacet_assignment_idContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.facet_keyfieldextendfacet_keyContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.version_idfieldextendversion_idContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.media_idfieldextendmedia_idContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.facet_valuefieldextendfacet_valueContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.provenancefieldextendprovenanceContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_catalog_facet_assignment.retired_atfieldextendretired_atContent-owned extend-only sidecarcontent-itd-014-catalog-facets-sidecar
content_refobjectextendContent 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 sidecarThe one reference shape for every Content kind.content-itd-016-uniform-content-reference-contract
content_referenceobjectextendQTI 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 sidecarA 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_idfieldextendcontent_reference_idContent-owned extend-only sidecarcontent-itd-016-uniform-content-reference-contract
content_reference.display_orderfieldextenddisplay_orderContent-owned extend-only sidecarcontent-itd-016-uniform-content-reference-contract
content_reference.usage_scopefieldextendalpha.content_reference.usage_scopeContent-owned extend-only sidecarcontent-itd-020-test-spec-and-test-bank
content_reference.referenced_usage_scopefieldderivealpha.content_reference_view.referenced_usage_scopeSee named source and owner ITDcontent-itd-020-test-spec-and-test-bank
content_reference.created_atfieldextendcreated_atContent-owned extend-only sidecarcontent-itd-016-uniform-content-reference-contract
content_reference.retired_atfieldextendretired_atContent-owned extend-only sidecarcontent-itd-016-uniform-content-reference-contract
answer_slotobjectrestrictOnly 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 sourceQTI response declaration exposed in plain language.content-itd-008-answer-slots-not-student-answers
answer_slot.answer_slot_idfieldrenameqti.variable_declaration.variable_declaration_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
answer_slot.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
answer_slot.version_idfieldrenameqti.variable_declaration.artifact_version_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
answer_slot.source_tracefieldrenameqti.variable_declaration.source_traceRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
answer_keyobjectrestrictQTI correct_response is preserved but restricted to authorized reads. It is not exposed to ordinary student content reads.Read view / projection over named sourceTeacher-only correct response and mapping for an answer slot.content-itd-008-answer-slots-not-student-answers
answer_key.answer_slot_idfieldextendanswer_slot_idContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
answer_key.mappingfieldrestrictqti.variable_declaration.mappingRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
answer_key.required_scopefieldextendrequired_scopeContent-owned extend-only sidecarcontent-axis-006-auth-shape
assessment_componentobjectrestrictQTI 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 sourceSections, parts, choices, rubrics, and pre-authored feedback from the QTI component tree.content-itd-008-answer-slots-not-student-answers
assessment_component.assessment_component_idfieldrenameqti.component.component_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_component.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
assessment_component.version_idfieldrenameqti.component.artifact_version_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_component.parent_assessment_component_idfieldrenameqti.component.parent_component_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_component.ordinalfieldrenameqti.component.ordinalRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_component.element_namefieldrenameqti.component.element_nameRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_component.qti_identifierfieldrenameqti.component.qti_identifierRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
assessment_component.response_identifierfieldextendresponse_identifierContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
assessment_component.outcome_identifierfieldextendoutcome_identifierContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
assessment_component.feedback_identifierfieldextendfeedback_identifierContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
assessment_component.show_hidefieldextendshow_hideContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
assessment_component.source_tracefieldrenameqti.component.source_traceRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_planobjectrestrictQTI processing rules remain the source. Alpha exposes enough provenance to explain the blank scoring plan without becoming Results.Read view / projection over named sourceQTI processing provenance for scoring a content version.content-itd-008-answer-slots-not-student-answers
scoring_plan.scoring_plan_idfieldrenameqti.processing_rule.processing_rule_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_plan.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-itd-008-answer-slots-not-student-answers
scoring_plan.version_idfieldrenameqti.processing_rule.artifact_version_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_plan.parent_scoring_plan_idfieldrenameqti.processing_rule.parent_processing_rule_idRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_plan.rule_scopefieldrenameqti.processing_rule.rule_scopeRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_plan.rule_namefieldrenameqti.processing_rule.rule_nameRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_plan.sequence_numberfieldrenameqti.processing_rule.sequence_numberRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_plan.operandsfieldrenameqti.processing_rule.operandsRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
scoring_plan.source_tracefieldrenameqti.processing_rule.source_traceRead view / projection over named sourcecontent-itd-008-answer-slots-not-student-answers
content_effectivenessobjectderiveContent-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 ITDResults-owned derived effectiveness read for one content item.content-itd-009-content-effectiveness-derived-rollup
content_effectiveness.content_idread-dependency-fieldderivealpha.content_effectiveness.data ->> 'content_id'See named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.content_version_idread-dependency-fieldderivealpha.content_effectiveness.data ->> 'content_version_id'See named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.kc_idread-dependency-fieldderivealpha.content_effectiveness.data ->> 'kc_id'See named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.subject_idread-dependency-fieldderivealpha.content_effectiveness.data ->> 'subject_id'See named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.evidence_countread-dependency-fieldderivealpha.content_effectiveness.data ->> 'evidence_count'See named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.freshness_statusread-dependency-fieldderiveGET /items/{contentId}/effectiveness fallback over alpha.content_effectivenessSee named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.policy_versionread-dependency-fieldderivealpha.content_effectiveness.data ->> 'policy_version'See named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.source_result_refsread-dependency-fieldderivealpha.content_effectiveness.data -> 'source_result_refs'See named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_effectiveness.provenanceread-dependency-fieldderivealpha.content_effectiveness.dataSee named source and owner ITDcontent-itd-009-content-effectiveness-derived-rollup
content_trust_evidenceobjectextendCurrent 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 sidecarRead-only trust status and validation provenance for Content.content-axis-010-conformance-evidence
content_trust_evidence.content_idfieldextendcontent_idContent-owned extend-only sidecarcontent-axis-010-conformance-evidence
content_trust_evidence.trust_statusfieldextendtrust_statusContent-owned extend-only sidecarcontent-axis-010-conformance-evidence
content_trust_evidence.evidence_urlfieldextendevidence_urlContent-owned extend-only sidecarcontent-axis-010-conformance-evidence
content_trust_evidence.checked_atfieldextendchecked_atContent-owned extend-only sidecarcontent-axis-010-conformance-evidence
content_trust_evidence.problem_codefieldextendproblem_codeContent-owned extend-only sidecarcontent-itd-012-plain-language-problems-and-provenance
content_policyobjectrestrictThe 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 sourceCurrent Content policy projection, including module-local rows and the bundled versioned source-adapter policy.content-itd-009-content-effectiveness-derived-rollup
content_policy.policy_keyfieldextendpolicy_keyContent-owned extend-only sidecarcontent-itd-009-content-effectiveness-derived-rollup
content_policy.value_jsonfieldextendvalue_jsonContent-owned extend-only sidecarcontent-itd-009-content-effectiveness-derived-rollup
content_policy.policy_versionfieldextendpolicy_versionContent-owned extend-only sidecarcontent-itd-009-content-effectiveness-derived-rollup
content_policy.updated_atfieldextendupdated_atContent-owned extend-only sidecarcontent-itd-009-content-effectiveness-derived-rollup
content_problemobjectrestrictThe 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 sourceStable problem codes and field errors returned by Content APIs.content-itd-012-plain-language-problems-and-provenance
content_problem.typefieldextendtypeContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.titlefieldextendtitleContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.statusfieldextendstatusContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.codefieldextendcodeContent-owned extend-only sidecarcontent-itd-012-plain-language-problems-and-provenance
content_problem.detailfieldextenddetailContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.requestIdfieldextendrequestIdContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.traceIdfieldextendtraceIdContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.causefieldextendcauseContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.docUrlfieldextenddocUrlContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_problem.fieldErrorsfieldextendfieldErrorsContent-owned extend-only sidecarcontent-axis-008-error-envelope
content_kc_tagobjectextendThe Curriculum registry supplies kc_id identity; Content owns only the mapping from its stable Content identity to that registry identity.Content-owned extend-only sidecarThe 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_idfieldextendResolved from the authenticated route tenant.Content-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_kc_tag.content_idfieldextendTaken from the Content item route after tenant-scoped resolution.Content-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_kc_tag.kc_idfieldextendSupplied by an authorized Content/KC tagging workflow after registry resolution.Content-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_kc_tag.weightfieldextendAsserted by the authorized tagging or review workflow.Content-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_kc_tag.is_primaryfieldextendAsserted by the authorized tagging or review workflow.Content-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_kc_tag.provenancefieldextendSupplied by the authorized tagging workflow.Content-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_kc_tag.confidencefieldextendSupplied by the authorized tagging or review workflow.Content-owned extend-only sidecarcontent-itd-005-curriculum-links-are-relationships
content_kc_tag.created_atfieldextendSet by PostgreSQL on initial insert.Content-owned extend-only sidecarcontent-axis-011-privacy-retention
content_kc_tag.updated_atfieldextendSet by PostgreSQL insert/upsert.Content-owned extend-only sidecarcontent-axis-011-privacy-retention
content_kc_tag.retired_atfieldextendSet by a governed retirement workflow; the initial public API writes null.Content-owned extend-only sidecarcontent-axis-011-privacy-retention
content_item.source_tablefieldextendalpha.content_item_extension.source_tableContent-owned extend-only sidecarcontent-itd-018-source-shaped-production-ingest
content_release_eligibilityobjectextendcontent-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 sidecarCurrent lifecycle and item-revocation answer for one exact Content version.content-itd-023-trust-based-release-eligibility
content_release_eligibility.content_idfieldextendContent base identity.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_release_eligibility.content_version_idfieldextendContent version identity.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_release_eligibility.content_kindfieldrestrict + extendContent item kind taxonomy.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_release_eligibility.usage_scopefieldextendContent item routing exposure contract.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_release_eligibility.trust_statusfieldextendContent current item revocation state.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_release_eligibility.serveablefieldextendContent lifecycle and item revocation state.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_release_eligibility.release_eligiblefieldextendContent lifecycle and item revocation state.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_release_eligibility.problem_codefieldextendContent problem envelope.Content-owned extend-only sidecarcontent-itd-023-trust-based-release-eligibility
content_test_serve_grantobjectextendcontent-itd-024-governed-test-serveContent-owned extend-only sidecarSoft-revocable demo authorization for one test/synthetic identity.content-itd-024-governed-test-serve
content_test_serve_grant.tenant_idreference-contract-fieldrestrictPlatform tenant identity.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_grant.test_serve_grant_idfieldextendContent test-serving policy.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_grant.test_identity_idreference-contract-fieldrestrictPeople & Orgs identity reference only; no person facts are copied.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_grant.producer_surfacefieldextendContent producer policy.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_grant.authorization_reffieldextendGovernance provenance.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_grant.expires_atfieldextendContent test-serving policy.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_grant.revoked_atfieldextendContent test-serving policy.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_grant.created_atfieldextendContent audit field.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_authorizationobjectextendcontent-itd-024-governed-test-serveContent-owned extend-only sidecarImmutable exact bank/form test-serve receipt.content-itd-024-governed-test-serve
content_test_serve_authorization.tenant_idreference-contract-fieldrestrictPlatform tenant identity.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.test_serve_authorization_idfieldextendContent authorization evidence.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_authorization.test_serve_grant_idreference-contract-fieldrestrictContent grant reference.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.caller_subjectfieldextendVerified JWT subject.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_authorization.test_identity_idreference-contract-fieldrestrictPeople & Orgs identity reference.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.producer_surfacefieldextendContent producer policy.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_authorization.gate_component_idreference-contract-fieldrestrictCurriculum gate identity.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.bank_content_idreference-contract-fieldrestrictContent bank identity.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.bank_content_version_idreference-contract-fieldrestrictContent version identity.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.form_content_idreference-contract-fieldrestrictContent form identity.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.form_content_version_idreference-contract-fieldrestrictContent version identity.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_authorization.request_idfieldextendPlatform request tracing.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_authorization.trace_idfieldextendPlatform request tracing.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_authorization.authorized_atfieldextendContent audit field.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_eligibilityobjectrestrictcontent-itd-024-governed-test-serveRead view / projection over named sourceCurrent fail-closed answer for one exact Content version and one test identity.content-itd-024-governed-test-serve
content_test_serve_eligibility.content_idread-dependency-fieldderiveContent identity.See named source and owner ITDcontent-itd-024-governed-test-serve
content_test_serve_eligibility.content_version_idread-dependency-fieldderiveContent version identity.See named source and owner ITDcontent-itd-024-governed-test-serve
content_test_serve_eligibility.content_kindread-dependency-fieldderiveContent kind.See named source and owner ITDcontent-itd-024-governed-test-serve
content_test_serve_eligibility.usage_scoperead-dependency-fieldderiveContent usage policy.See named source and owner ITDcontent-itd-024-governed-test-serve
content_test_serve_eligibility.producer_surfacefieldextendContent producer policy.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_eligibility.test_identity_idread-dependency-fieldderivePeople & Orgs identity reference.See named source and owner ITDcontent-itd-024-governed-test-serve
content_test_serve_eligibility.test_serve_grant_idreference-contract-fieldrestrictContent grant.Read view / projection over named sourcecontent-itd-024-governed-test-serve
content_test_serve_eligibility.test_serve_eligiblefieldextendContent test-serving policy.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve
content_test_serve_eligibility.problem_codefieldextendContent error contract.Content-owned extend-only sidecarcontent-itd-024-governed-test-serve

Generated cut map

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/objectChangeAudience tradeoff / owning boundaryOwner ITD
qti.delivery_sessioncutA 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.attemptcutAttempts 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.lineItemcutDurable 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-resourcecutThey 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 containerscutInstructional 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 membershipcutCollections 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 bundlecutA 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 graphcutThe 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 rowcutStimulus 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 bytescutRaw 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 browsingcutAlpha 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 APIscutAuthorized 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 controlcutThe 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 feedbackcutThese 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 tablescutProvider 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

Module-Placement QC

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.

BoundaryBelongs in ContentRoutes elsewhere
Blank instrument vs attemptReusable 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 gatesReusable 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 sequenceItem-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 resultThe 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 materialStudent-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 enrollmentTenant-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.

Cross-cutting rules applied to Content

Tag registry vs. real column

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.

Point-in-time default

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.

Typed field, never parsed

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.

Policy config

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.

Event/result/gradebook separation

Number: 5

ContentObligation: Content holds blank instruments and media. Events holds interactions. Results holds answers, scores, mastery, and gradebook rollups.

Two graphs, two homes

Number: 6

ContentObligation: Content links to Curriculum's shared graph. It never stores the student's knowledge graph or mastery state.

NWEA account boundary

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.

Secrets are not fields/tags

Number: 8

ContentObligation: Content metadata, media records, and tags cannot contain student app credentials, raw tokens, or secret values.

XP ledger separation

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.

Content effectiveness rollup

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.

Named ingest adapters

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.

Universal gate failure protocol

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.

Rewards and motivation are Policy

Number: 13

ContentObligation: 100for100 payouts, league promotions, streak bonuses, penalties, and reward amounts never live on Content rows or catalog facets.

Goal-to-effort conversion

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.

LOR clone storage

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.

Locked vocabulary

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.

Receipt-backed exact-version release eligibility

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.

Bounded test-bank graph writes

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.

Content owns the stored HTML body vocabulary; Curriculum selects renderer_ref per member; QTI owns XML/scoring; Events/Results own durable learner progress. The renderer and host must not create a private progress store.

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"]}

Architecture decision log

Numbered, deep-linked, and status-bearing. Supersession references link directly to the related decision.

#DecisionStatusDateOwnerSupersedesSuperseded by
1content-itd-001-module-boundary-and-alias-map
Module Boundary And Alias Map
Accepted2026-06-08platform3-loopnonenone
2content-itd-002-shared-qti-truth-no-content-fork
Shared Qti Truth No Content Fork
Accepted2026-06-08platform3-loopnonenone
3content-itd-003-content-kind-taxonomy
Content Kind Taxonomy
Accepted2026-06-08platform3-loopnonenone
4content-itd-004-non-qti-media-extension
Non Qti Media Extension
Accepted2026-06-08platform3-loopnonenone
5content-itd-005-curriculum-links-are-relationships
Curriculum Links Are Relationships
Accepted2026-07-16andymontgomery-bytecontent-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
6content-itd-006-typed-content-and-test-fields
Typed Content And Test Fields
Accepted2026-06-08platform3-loopnonenone
7content-itd-007-versioned-authoring-and-student-view
Versioned Authoring And Student View
Accepted Pending Implementation2026-06-08platform3-loopnonenone
8content-itd-008-answer-slots-not-student-answers
Answer Slots Not Student Answers
Accepted2026-06-08platform3-loopnonenone
9content-itd-009-content-effectiveness-derived-rollup
Content Effectiveness Derived Rollup
Accepted2026-06-08platform3-loopnonenone
10content-itd-010-external-test-family-identity
External Test Family Identity
Accepted2026-06-08platform3-loopnonenone
11content-itd-011-public-cuts-and-internal-provenance
Public Cuts And Internal Provenance
Accepted2026-06-08platform3-loopnonenone
12content-itd-012-plain-language-problems-and-provenance
Plain Language Problems And Provenance
Accepted2026-06-08platform3-loopnonenone
13content-itd-013-lossless-common-cartridge-round-trip
Lossless Common Cartridge Round Trip
Accepted Pending Implementation2026-06-08platform3-loopnonenone
14content-itd-014-catalog-facets-sidecar
Catalog Facets Sidecar
Accepted2026-06-08platform3-loopnonenone
15content-itd-015-expected-xp-content-config
Expected Xp Content Config
Accepted2026-06-08platform3-loopnonenone
16content-itd-016-uniform-content-reference-contract
Uniform Content Reference Contract
Accepted2026-06-08platform3-loopnonecontent-itd-005-curriculum-links-are-relationships (versioned coverage-link clause only)
17content-itd-017-lor-clones-no-specific-storage
Lor Clones No Specific Storage
Accepted2026-06-08platform3-loopnonecontent-itd-005-curriculum-links-are-relationships (KC-alignment clause only)
18content-itd-018-source-shaped-production-ingest
Source Shaped Production Ingest
Accepted2026-06-08platform3-loopnonenone
19content-itd-019-governed-subtypes-within-ten-kinds
Governed Subtypes Within Twelve Kinds
Accepted2026-06-12platform3-loopnonenone
20content-itd-020-test-spec-and-test-bank
Test Spec And Test Bank
Accepted Pending Implementation2026-06-12platform3-loopnonenone
21content-itd-021-tb-markup-body-contract
Tb Markup Body Contract
Accepted2026-07-03platform3-loopnonecontent-itd-022-interactive-composition (render-time composition only)
22content-itd-022-interactive-composition
Interactive Composition
Accepted2026-07-06andymontgomery-bytecontent-itd-021-tb-markup-body-contract (render-time composition only)content-itd-005-curriculum-links-are-relationships (content_curriculum_link gating clause only)
23content-itd-023-trust-based-release-eligibility
Trust Based Release Eligibility
Accepted2026-07-27platform3-operatornonenone
24content-axis-001-write-granularity
Content Api Axis Write Granularity
Accepted2026-06-08platform3-loopnonenone
25content-axis-002-read-shape
Content Api Axis Read Shape
Accepted Pending Implementation2026-06-08platform3-loopnonenone
26content-axis-003-query-model
Content Api Axis Query Model
Accepted2026-06-08platform3-loopnonenone
27content-axis-004-concurrency-model
Content Api Axis Concurrency Model
Accepted2026-06-08platform3-loopnonenone
28content-axis-005-idempotency-model
Content Api Axis Idempotency Model
Accepted2026-06-08platform3-loopnonenone
29content-axis-006-auth-shape
Content Api Axis Auth Shape
Accepted2026-06-08platform3-loopnonenone
30content-axis-007-eventing-model
Content Api Axis Eventing Model
Accepted2026-06-08platform3-loopnonenone
31content-axis-008-error-envelope
Content Api Axis Error Envelope
Accepted2026-06-08platform3-loopnonenone
32content-axis-009-tenant-routing
Content Api Axis Tenant Routing
Accepted2026-06-08platform3-loopnonenone
33content-axis-010-conformance-evidence
Content Api Axis Conformance Evidence
Accepted2026-06-08platform3-loopnonenone
34content-axis-011-privacy-retention
Content Api Axis Privacy Retention
Accepted Pending Implementation2026-06-08platform3-loopnonenone
35content-axis-012-list-endpoints
Content Api Axis List Endpoints
Accepted2026-06-08platform3-loopnonenone
36content-itd-024-governed-test-serve
Governed Test Serve
Accepted2026-07-24platform3-doernonenone
content-itd-001-module-boundary-and-alias-mapStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Module Boundary And Alias Map

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Keep QTI as the public Alpha modulePreserves 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 linksAllows 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Content must maintain a generated alias map and explicit module-placement rules whenever an upstream or Alpha name changes.
  • All twelve public kinds share one asset/reference model; Curriculum containers and learner runtime facts must be rejected or routed to their owning modules.

Fitness checks

  • The published content_kind set is exactly twelve values, every public name has one source/owner/storage row, and no public stimulus or routing container appears.

Downstream rules

  • Dictionary objects: content_item.
  • Owned fields: content_item.content_id, content_item.title, content_item.language, content_item.source_identifier, content_item.source_system.
  • Alias-map rows: Content module, content_item.title, content_item.language, content_item.source_identifier, content_item, content_item.source_system.

Trace

renderdiscoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-002-shared-qti-truth-no-content-forkStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Shared Qti Truth No Content Fork

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Fork Alpha content into alpha.content_* tables for every QTI objectGives 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 unchangedGuarantees 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Core assessment writes must go through the QTI surface, and Alpha reads must tolerate joins between QTI base rows and Content sidecars.
  • Any migration that copies a QTI-owned column into an Alpha core table is an architectural rollback, even if the copy improves a local query.

Fitness checks

  • Schema and dictionary checks find no forbidden alpha.content_item/content_version/answer_slot/scoring_plan/media copies, and every extension field is new Alpha meaning plus a base foreign key.

Downstream rules

  • Dictionary objects: content_item.
  • Alias-map rows: content_item.content_id.

Trace

renderdiscoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-003-content-kind-taxonomyStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Kind Taxonomy

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Pass through qti.artifact.artifact_kind unchangedRetains 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 pathCan 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Import adapters must normalize source kinds at write time and reject unknown values instead of teaching read clients aliases.
  • Adding a first-class kind is a data-dictionary and architecture change; subtypes such as diagram and decodable cannot silently expand the enum.

Fitness checks

  • Generated docs, API schemas, implementation, and skill pack all expose exactly question, test, test_spec, test_bank, article, video, audio, image, interactive, media_asset, external_link, and script.

Downstream rules

  • Dictionary objects: content_item, content_ref.
  • Owned fields: content_item.content_kind.
  • Alias-map rows: content_item.content_kind, question, test.
  • Resource shapes: question, test, test_spec, test_bank.

Trace

renderdiscoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-004-non-qti-media-extensionStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Non Qti Media Extension

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Force every non-QTI item into a raw QTI package workflow with no Alpha extensionUses 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 containersAvoids standalone media records and additional extension relationships.Makes reusable assets unaddressable, duplicates them across parents, and puts student-touchable material into the wrong module.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Some writes are two-stage operations that create or resolve a base resource before appending Alpha-only fields.
  • Media sidecars inherit base lifecycle and tenant scope, and they may not copy titles, versions, package fields, or Curriculum structure.

Fitness checks

  • Every non-QTI public kind resolves to one base asset plus documented sidecars without a parallel Content copy; lossless export/reimport remains the pending ITD-013 fitness proof.

Downstream rules

  • Dictionary objects: content_item, media_asset, external_link.
  • Owned fields: content_item.content_kind, media_asset.media_id, media_asset.content_id, media_asset.media_kind, media_asset.media_type, media_asset.byte_length, media_asset.content_hash, media_asset.storage_path, media_asset.delivery_url, media_asset.created_at, external_link.content_id, external_link.external_url, external_link.display_url, external_link.open_policy, content_catalog_facet_assignment.media_id.
  • Alias-map rows: article, video, audio, image, interactive, media_asset, external_link, script, media_asset.media_id, media_asset.media_kind, media_asset.media_type, media_asset.byte_length, external_link.external_url, media_asset.content_id, media_asset.content_hash, media_asset.storage_path, media_asset.delivery_url, media_asset.created_at, external_link.content_id, external_link.display_url, external_link.open_policy.
  • Resource shapes: article, script, video, audio, image, interactive, media_asset, external_link.

Trace

renderdiscoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-006-typed-content-and-test-fieldsStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Typed Content And Test Fields

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Parse subject, grade, and test type from titles or package pathsCan 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 tableLets 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Adapters must validate and normalize classifications before persistence; ambiguous source records fail rather than being guessed at read time.
  • Enum changes require coordinated dictionary, API, migration, and consumer updates instead of an unreviewed application mapping.

Fitness checks

  • Queries use typed columns only, test_type and is_mastery_gate remain independent, and media MIME/delivery values never substitute for content_kind.

Downstream rules

  • Dictionary objects: media_asset, assessment_test, content_catalog_facet.
  • Owned fields: content_item.subject_id, content_item.target_grade_id, media_asset.media_kind, external_link.open_policy, test.test_type, assessment_test.content_id, assessment_test.test_type, assessment_test.is_mastery_gate.
  • Alias-map rows: assessment_test.test_type, assessment_test.is_mastery_gate, content_item.subject_id, content_item.target_grade_id, test.test_type, assessment_test.content_id.

Trace

renderdiscover

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.

Supersedes: noneSuperseded by: none
content-itd-007-versioned-authoring-and-student-viewStatus: Accepted Pending ImplementationDate: 2026-06-08Owner: platform3-loop

Versioned Authoring And Student View

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Edit rows in placeSimplifies 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 shapeAvoids 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Item-metadata PATCH uses If-Match and ordinary version creation uses Idempotency-Key. TimeBack import canonicalizes source-table identity, replays an identical content hash, and appends changed bytes as a hash-bound UUIDv5 exact version with the next version_number and supersedes edge; a deployed legacy :v1 row is adopted only after complete-projection ownership checks.
  • Once implemented, launch becomes a resolution boundary: Content returns resolved_content_version_id and Events preserves it for Results and replay.

Fitness checks

  • Current fitness covers immutable exact-version reads for ordinary authoring and TimeBack source import, including update/delete database guards, identical replay, changed-byte append, and historical replay without pointer rollback. Target acceptance additionally requires an unversioned launch to return and record resolved_content_version_id so later publication cannot alter historical attempts.

Downstream rules

  • Dictionary objects: content_version.
  • Owned fields: content_item.latest_version_id, content_item.status, content_version.version_id, content_version.content_id, content_version.version_number, content_version.student_view, content_version.editing_draft, content_version.canonical_xml, content_version.supersedes_version_id, content_version.etag, content_version.created_at, media_asset.version_id, content_curriculum_link.version_id, content_catalog_facet_assignment.version_id.
  • Alias-map rows: content_item.latest_version_id, content_version.version_id, content_version.version_number, content_version.student_view, content_version.editing_draft, content_version.canonical_xml, content_item.status, content_version, content_version.content_id, content_version.supersedes_version_id, content_version.created_at, media_asset.version_id, content_curriculum_link.version_id.
  • Accepted architecture target with incomplete runtime coverage. The named dictionary contract may exist, but implementation and deployed proof remain pending; this decision is not yet active or fully deployed.

Trace

renderinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-008-answer-slots-not-student-answersStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Answer Slots Not Student Answers

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Put attempts and scores beside the question in ContentMakes 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 metadataEliminates 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Answer-key reads require read:answer_key. Current scoring-plan reads require only read:content, the same broad read capability as other blank Content projections; the stricter answer-key scope must not be inferred for scoring-plan.
  • Content cannot persist submissions, outcome_state, processing traces as results, AI grading audits, or post-attempt feedback.

Fitness checks

  • Dictionary and schema checks show only QTI-backed blank-component projections in Content and zero learner/attempt identifiers on Content rows.

Downstream rules

  • Dictionary objects: answer_slot, answer_key, assessment_component, scoring_plan.
  • Owned fields: answer_slot.answer_slot_id, answer_slot.content_id, answer_slot.version_id, answer_slot.identifier, answer_slot.cardinality, answer_slot.base_type, answer_slot.source_trace, answer_key.answer_slot_id, answer_key.correct_response, answer_key.mapping, assessment_component.assessment_component_id, assessment_component.content_id, assessment_component.version_id, assessment_component.parent_assessment_component_id, assessment_component.component_role, assessment_component.ordinal, assessment_component.element_name, assessment_component.qti_identifier, assessment_component.response_identifier, assessment_component.outcome_identifier, assessment_component.feedback_identifier, assessment_component.show_hide, assessment_component.display_text, assessment_component.source_trace, scoring_plan.scoring_plan_id, scoring_plan.content_id, scoring_plan.version_id, scoring_plan.parent_scoring_plan_id, scoring_plan.rule_scope, scoring_plan.rule_name, scoring_plan.sequence_number, scoring_plan.operands, scoring_plan.source_trace.
  • Alias-map rows: answer_slot.identifier, answer_slot.cardinality, answer_slot.base_type, answer_key.correct_response, assessment_component.component_role, assessment_component.display_text, answer_slot, answer_slot.answer_slot_id, answer_slot.content_id, answer_slot.version_id, answer_slot.source_trace, answer_key, answer_key.answer_slot_id, answer_key.mapping, assessment_component, assessment_component.assessment_component_id, assessment_component.content_id, assessment_component.version_id, assessment_component.parent_assessment_component_id, assessment_component.ordinal, assessment_component.element_name, assessment_component.qti_identifier, assessment_component.response_identifier, assessment_component.outcome_identifier, assessment_component.feedback_identifier, assessment_component.show_hide, assessment_component.source_trace, scoring_plan, scoring_plan.scoring_plan_id, scoring_plan.content_id, scoring_plan.version_id, scoring_plan.parent_scoring_plan_id, scoring_plan.rule_scope, scoring_plan.rule_name, scoring_plan.sequence_number, scoring_plan.operands, scoring_plan.source_trace.

Trace

renderinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-009-content-effectiveness-derived-rollupStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Effectiveness Derived Rollup

Audience constraint

Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Let each dashboard count successes and failures from raw resultsAllows 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 incrementProvides 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 itemKeeps 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Content effectiveness availability and freshness follow the Results projection; Content may display but never mutate or recompute it.
  • Consumers must distinguish insufficient_data from a real zero-rate observation through the typed freshness/provenance fields.

Fitness checks

  • All non-fallback effectiveness values trace to Results, and an absent Results row produces zero counts/null rate without inserting Content storage.

Downstream rules

  • Dictionary objects: content_effectiveness, content_policy.
  • Owned fields: content_policy.policy_key, content_policy.value_json, content_policy.policy_version, content_policy.updated_at.
  • Read-dependency fields (owned outside Content): content_effectiveness.content_id, content_effectiveness.content_version_id, content_effectiveness.kc_id, content_effectiveness.subject_id, content_effectiveness.validated_by_count, content_effectiveness.failed_by_count, content_effectiveness.effectiveness_rate, content_effectiveness.evidence_count, content_effectiveness.freshness_status, content_effectiveness.last_recomputed_at, content_effectiveness.policy_version, content_effectiveness.source_result_refs, content_effectiveness.provenance.
  • Alias-map rows: content_effectiveness.validated_by_count, content_effectiveness.failed_by_count, content_effectiveness.effectiveness_rate, content_effectiveness.last_recomputed_at, content_effectiveness, content_effectiveness.content_id, content_effectiveness.content_version_id, content_effectiveness.kc_id, content_effectiveness.subject_id, content_effectiveness.evidence_count, content_effectiveness.freshness_status, content_effectiveness.policy_version, content_effectiveness.source_result_refs, content_effectiveness.provenance, content_policy, content_policy.policy_key, content_policy.value_json, content_policy.policy_version, content_policy.updated_at.

Trace

discover

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.

Supersedes: noneSuperseded by: none
content-itd-010-external-test-family-identityStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

External Test Family Identity

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Let each adapter match by title, date, and producer URLAvoids 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 oneLets 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Current Content import does not populate or resolve test_family_id, and Results can persist producer family text without a Content foreign key; consumers cannot treat present text as reconciled Content identity.
  • A future resolver must map source_system/source identity and multiple forms to one governed Content family and fail unresolved families explicitly rather than silently accepting labels.

Fitness checks

  • Target acceptance requires multiple external forms to resolve to one Content family id and no Results/adapter table to become the identity authority; current nullable text storage alone does not pass this check.

Downstream rules

  • Dictionary objects: assessment_test.
  • Owned fields: assessment_test.test_family_id.
  • Alias-map rows: assessment_test.test_family_id.
  • Resource shapes: test.

Trace

discoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-011-public-cuts-and-internal-provenanceStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Public Cuts And Internal Provenance

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Expose every QTI and platform row for maximum powerGives 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 provenanceProduces 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Some expert jobs require following provenance to the sanctioned 1EdTech surface rather than using an Alpha endpoint.
  • New upstream objects default to non-public until an Alpha use case and module owner justify a projection.

Fitness checks

  • The public surface has no package-byte mutation, generic object graph, conformance-runner mutation, delivery-session, attempt, or gradebook browsing, while every projection names its source.

Downstream rules

  • Resource shapes: transport-only: imscp_v1p1 / Common Cartridge bundle.

Trace

interoperate

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.

Supersedes: noneSuperseded by: none
content-itd-012-plain-language-problems-and-provenanceStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Plain Language Problems And Provenance

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Return raw QTI/Schematron/database errorsPreserves 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 onlyIs 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Content must maintain a stable code/type registry and deliberately map validator details rather than passing arbitrary messages through.
  • Support and clients can key on code, requestId, traceId, and fieldErrors while treating human detail as presentation text.

Fitness checks

  • Every failure uses a non-200 status and typed Problem with a stable code plus request/trace identifiers and fieldErrors where applicable; no raw database or Schematron string or unimplemented provenance field becomes the public contract.

Downstream rules

  • Dictionary objects: content_problem.
  • Owned fields: content_trust_evidence.problem_code, content_problem.code.
  • Alias-map rows: content_trust_evidence.problem_code, content_problem, content_problem.code.

Trace

interoperate

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.

Supersedes: noneSuperseded by: none
content-itd-013-lossless-common-cartridge-round-tripStatus: Accepted Pending ImplementationDate: 2026-06-08Owner: platform3-loop

Lossless Common Cartridge Round Trip

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Store Common Cartridge bundles as ContentMakes 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/importGives Alpha one aesthetically uniform document schema independent of upstream standards.Creates lossy mappings and permanent translation code whose correctness becomes a second interchange authority.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Alpha extensions must remain separable from upstream resource facts and have an explicit preservation strategy when the transport supports them.
  • Common Cartridge export is an on-demand Curriculum-plus-Content operation, not a Content row lifecycle.

Fitness checks

  • Target acceptance must export and reimport to the same Content identities, versions, resource payloads, references, and supported sidecar meaning with no translation-only business rule; current export-only inspection does not satisfy this check.

Downstream rules

  • Dictionary objects: external_link, content_reference.
  • Resource shapes: question, test, article, script, video, audio, image, interactive, media_asset, external_link, transport-only: imscp_v1p1 / Common Cartridge bundle.
  • Accepted architecture target with incomplete runtime coverage. The named dictionary contract may exist, but implementation and deployed proof remain pending; this decision is not yet active or fully deployed.

Trace

interoperate

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.

Supersedes: noneSuperseded by: none
content-itd-014-catalog-facets-sidecarStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Catalog Facets Sidecar

Audience constraint

Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Put all discovery facets into qti.metadataKeeps 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 facetsMakes discovery reads local and denormalized for straightforward indexing.Duplicates titles, kinds, versions, and lifecycle, requiring synchronization with the authoritative Content/QTI rows.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Facet writes have their own lifecycle/provenance and must validate against named value spaces or referenced authorities.
  • Generic facet=KEY:VALUE reads require stored assignments. For facet=expected_xp_band:VALUE, runtime additionally checks the corresponding numeric expected_xp range, so an inconsistent stored assignment is vetoed. The separate expected_xp_band= convenience filter uses only the numeric range and can match without an assignment.

Fitness checks

  • The sidecar exposes exactly the twelve named facets and contains no copied QTI base fields. Current expected_xp_band query behavior is explicitly hybrid (stored assignment plus numeric range) or numeric-only for the shortcut; no broader no-derivation claim is made.

Downstream rules

  • Dictionary objects: content_catalog_facet, content_catalog_facet_assignment.
  • Owned fields: content_catalog_facet.facet_key, content_catalog_facet.label, content_catalog_facet.value_type, content_catalog_facet.grain, content_catalog_facet.allowed_values, content_catalog_facet.status, content_catalog_facet_assignment.facet_assignment_id, content_catalog_facet_assignment.facet_key, content_catalog_facet_assignment.content_id, content_catalog_facet_assignment.version_id, content_catalog_facet_assignment.media_id, content_catalog_facet_assignment.facet_value, content_catalog_facet_assignment.provenance, content_catalog_facet_assignment.retired_at.
  • Alias-map rows: catalog_facet.subject, catalog_facet.grade_band, catalog_facet.language, catalog_facet.accessibility_profile, catalog_facet.rights_license, catalog_facet.provenance, catalog_facet.quality_rating, catalog_facet.kc_refs, catalog_facet.standards_framework_refs, catalog_facet.content_kind, catalog_facet.expected_xp_band, catalog_facet.modality, content_catalog_facet, content_catalog_facet.facet_key, content_catalog_facet.label, content_catalog_facet.value_type, content_catalog_facet.grain, content_catalog_facet.allowed_values, content_catalog_facet.status, content_catalog_facet_assignment, content_catalog_facet_assignment.facet_assignment_id, content_catalog_facet_assignment.facet_key, content_catalog_facet_assignment.content_id, content_catalog_facet_assignment.version_id, content_catalog_facet_assignment.media_id, content_catalog_facet_assignment.facet_value, content_catalog_facet_assignment.provenance, content_catalog_facet_assignment.retired_at.

Trace

discover

Tracked decision content-itd-014-catalog-facets-sidecar; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover.

Supersedes: noneSuperseded by: none
content-itd-015-expected-xp-content-configStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Expected Xp Content Config

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Compute XP from each student's actual time or accuracyAdapts 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 componentsKeeps 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • The accepted authoring policy requires an approved speed-run/calibration observation for that exact version, but current runtime neither stores nor gates publication on that proof: HTTP/database validation checks only a finite expected_xp value greater than or equal to 0, and import/seed paths can publish policy-derived or default values.
  • Current recalibration updates artifact-scoped expected_xp in place through item PATCH; it does not create version-scoped calibration evidence and never rewrites historical awarded XP.

Fitness checks

  • Every public Content kind returns nonnegative policy-valid expected_xp, no value is averaged from learner actuals, and awarded/bonus/discount XP appears only in Results.

Downstream rules

  • Owned fields: content_item.expected_xp.
  • Alias-map rows: content_item.expected_xp.

Trace

renderdiscover

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.

Supersedes: noneSuperseded by: none
content-itd-016-uniform-content-reference-contractStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Uniform Content Reference Contract

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Create one relationship per content kindProvides 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 URLCan 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Reference resolution is the accepted shared Content operation and must eventually return typed not-found/version/eligibility outcomes for every kind. Current reference/media writes can pair an artifact with another artifact's or tenant's version UUID because same-item/tenant validation is absent; content_kc_tag has no version column by design.
  • Composite assets incur explicit relationship rows; nested child Content objects in stored or public payloads are invalid.

Fitness checks

  • Every Curriculum-to-Content and Content-to-Content link uses the same identity/version shape, and adding a kind requires no new shape; target acceptance additionally requires every optional version to be validated as belonging to the paired artifact and tenant.

Downstream rules

  • Dictionary objects: content_ref, content_reference.
  • Owned fields: content_ref.content_id, content_ref.content_version_id, content_ref.content_kind, content_reference.content_reference_id, content_reference.parent_content_id, content_reference.parent_version_id, content_reference.parent_content_kind, content_reference.referenced_content_id, content_reference.referenced_version_id, content_reference.referenced_content_kind, content_reference.relationship_kind, content_reference.display_order, content_reference.created_at, content_reference.retired_at.
  • Alias-map rows: content_ref.content_id, content_ref.content_version_id, content_ref.content_kind, content_reference.parent_content_id, content_reference.parent_version_id, content_reference.parent_content_kind, content_reference.referenced_content_id, content_reference.referenced_version_id, content_reference.referenced_content_kind, content_reference.relationship_kind, content_ref, content_reference, content_reference.content_reference_id, content_reference.display_order, content_reference.created_at, content_reference.retired_at.

Trace

renderinteroperate

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.

Supersedes: noneSuperseded by: content-itd-005-curriculum-links-are-relationships (versioned coverage-link clause only)
content-itd-017-lor-clones-no-specific-storageStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Lor Clones No Specific Storage

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Create provider-specific library tablesPreserves 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 ContentKeeps 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Adapters must publish explicit source-to-platform mappings and preserve provider provenance without creating clone-specific public objects.
  • Some provider-native features remain transport metadata or require an approved extension rather than a dedicated library schema.

Fitness checks

  • No Merlot/OER Commons/Khan/IXL/library/playlist/question-bank or generic lineage table exists, and the four named provenance/lineage primitives plus transport ownership cover the model. TimeBack preserves deployed tenant + source_record_id UUIDs while binding canonical source-table ownership; cross-table reuse fails typed. A future versioned identity cutover is needed only to store both same-named source records concurrently.

Downstream rules

  • Dictionary objects: content_kc_tag.
  • No clone-specific storage: this decision reuses adapter/source-table context + content_item.source_record_id (source_identifier is provenance/export spelling only), content_catalog_facet_assignment (facet_key=provenance|quality_rating), content_trust_evidence, content_reference (relationship_kind=source_file_for|source_script_for), Curriculum links/collections, and on-demand Common Cartridge transport.

Trace

discoverinteroperate

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.

Supersedes: noneSuperseded by: content-itd-005-curriculum-links-are-relationships (KC-alignment clause only)
content-itd-018-source-shaped-production-ingestStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Source Shaped Production Ingest

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Require migration callers to pre-normalize TimeBack records before calling ContentKeeps 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 packagesUses 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Content owns versioned normalization rules and the implemented 400 validation versus 422 semantic-rejection outcomes for this source. The frozen curriculum-link archive receives no import writes and source hints cannot create kc_refs discovery assignments. Deployed tenant + source_record_id UUID continuity is retained, but canonical source-table ownership is enforced with a typed collision; changed canonical bytes append immutable hash-bound versions instead of overwriting :v1.
  • Successful responses must prove materialization through public and raw-dictionary reads; partial or Problem-shaped HTTP 200 responses are forbidden.

Fitness checks

  • A source fixture materializes visible canonical items/references/facets with source ids, malformed requests fail 400, and implemented semantic rejections fail 422. Re-import proves identical-byte replay and changed-byte immutable append; cross-table reuse of one deployed source_record_id fails typed instead of aliasing; archive and governed-KC snapshots remain unchanged. A future identity cutover is required only if two source tables must concurrently own the same record id.

Downstream rules

  • Dictionary objects: content_reference.
  • Owned fields: content_item.source_table, content_item.source_record_id, content_item.source_activity_ref.
  • Alias-map rows: content_item.source_record_id, content_item.source_activity_ref, content_item.source_table.

Trace

discoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-019-governed-subtypes-within-ten-kindsStatus: AcceptedDate: 2026-06-12Owner: platform3-loop

Governed Subtypes Within Twelve Kinds

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Make diagram and decodable new content_kind valuesMakes 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 pathsAvoids new schema fields and lets each source retain its own vocabulary.Creates inconsistent classifications, string-parsing business rules, and no authoritative answer for consumers.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Importers must map authoring vocabulary to kind plus governed subtype/role at write time rather than inferring on reads.
  • Consumers needing diagram or decodable filters query subtype; they cannot assume a new content_kind or inspect names/MIME alone.

Fitness checks

  • Spec/bank/script are public kinds, diagram/decodable validate only under image/article, stimulus is non-public anatomy, and no undocumented inferred subtype appears.

Downstream rules

  • Dictionary objects: decodable, test, assessment_test.
  • Owned fields: content_item.subtype, decodable.content_id, decodable.phonics_constraint, decodable.constraint_set_id, decodable.policy_version, test.assessment_role, assessment_test.assessment_role, test_spec.assessment_role, test_bank.assessment_role.
  • Alias-map rows: content_item.subtype, decodable.phonics_constraint, decodable, decodable.content_id, decodable.constraint_set_id, decodable.policy_version, assessment_test, assessment_test.assessment_role, test_spec.assessment_role, test_bank.assessment_role.
  • Resource shapes: test, article, script, video, audio, image.

Trace

renderdiscoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-020-test-spec-and-test-bankStatus: Accepted Pending ImplementationDate: 2026-06-12Owner: platform3-loop

Test Spec And Test Bank

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / 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_roleReuses 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Authors send all seven blueprint fields with content_kind=test_spec and one Idempotency-Key, receive the native content_id, and immediately read the same identity as spec_id without any consumer-owned blueprint row.
  • A fresh test_bank create validates its active same-tenant spec target under the graph fence before idempotency claim or item mutation; prior completed/in-progress ledger state still arbitrates first, and a corrected invalid-target body may reuse the still-unclaimed key.
  • Bank-definition PATCH preserves omitted fields, rejects null required fields, freezes actual changes outside the unretired draft state, and rejects blueprint swaps or adaptive-rule changes while fixed-form or PP100 adaptive-shard bank_member rows remain; rejection preserves the bank and ETag.
  • Once semantic validation lands, bank membership is ordered and spec-validated; QTI-native adaptive pools remain inside their test rather than being duplicated.

Fitness checks

  • Current acceptance requires authenticated create and blueprint readback to preserve kc_coverage and passing_rule, a changed-body replay conflict, same-tenant active test_spec target validation before fresh test_bank claim with corrected-body same-key recovery, and atomic bank-definition PATCH rejection for null fields, frozen banks, and active-member conflicts on both surfaces. Full target acceptance still requires active KC/DOK and bank-form blueprint comparability validation.

Downstream rules

  • Dictionary objects: test, assessment_test, test_spec, test_bank.
  • Owned fields: content_item.dok, content_item.usage_scope, test.content_id, test.assessment_role, assessment_test.assessment_role, test_spec.spec_id, test_spec.assessment_role, test_spec.kc_coverage, test_spec.item_type_mix, test_spec.difficulty_constraints, test_spec.passing_rule, test_spec.min_forms, test_spec.max_item_overlap, test_spec.enemy_item_rule, test_bank.bank_id, test_bank.assessment_role, test_bank.spec_id, test_bank.membership_rule, content_reference.form_index, content_reference.usage_scope, content_reference.referenced_usage_scope.
  • Alias-map rows: content_item.dok, content_reference.form_index, test.assessment_role, test_spec, test_spec.kc_coverage, test_spec.passing_rule, test_spec.min_forms, test_spec.max_item_overlap, test_bank, test_bank.spec_id, content_item.usage_scope, test.content_id, test_spec.spec_id, test_spec.item_type_mix, test_spec.difficulty_constraints, test_spec.enemy_item_rule, test_bank.bank_id, test_bank.membership_rule, content_reference.usage_scope, content_reference.referenced_usage_scope.
  • Resource shapes: test, test_spec, test_bank.
  • Accepted architecture target with incomplete runtime coverage. The named dictionary contract may exist, but implementation and deployed proof remain pending; this decision is not yet active or fully deployed.

Trace

renderdiscoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-itd-021-tb-markup-body-contractStatus: AcceptedDate: 2026-07-03Owner: platform3-loop

Tb Markup Body Contract

Audience constraint

Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Leave the markup vocabulary only in the external renderer implementation documentLets 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 rowsMakes 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 progressAllows 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Markup vocabulary changes require Content versioning, publication validation, and renderer compatibility evidence rather than an application-only release.
  • Renderer state is ephemeral transport; durable learner actions and outcomes must return through Events/Results even when the markup is renderer-specific.

Fitness checks

  • Passing fitness check: every accepted tb-* token is documented, exact-version article publication rejects unknown tokens, ambiguous markup, unresolved same-body catalog references, and student-forbidden answer/scoring fields, renderer_ref remains on Curriculum members, and no durable learner progress exists only in contentState.

Downstream rules

  • Dictionary objects: content_html_body_contract.
  • Owned fields: content_version.student_view, content_html_body_contract.html, content_html_body_contract.tb_markup_token, content_html_body_contract.catalog_idref, content_html_body_contract.catalog_entry_id, content_html_body_contract.qti_xml_ref, content_html_body_contract.renderer_ref, content_html_body_contract.renderer_message, content_html_body_contract.progress_event_contract.
  • Alias-map rows: content_html_body_contract, content_html_body_contract.html, content_html_body_contract.tb_markup_token, content_html_body_contract.catalog_idref, content_html_body_contract.catalog_entry_id, content_html_body_contract.qti_xml_ref, content_html_body_contract.renderer_ref, content_html_body_contract.renderer_message, content_html_body_contract.progress_event_contract.

Trace

render

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.

Supersedes: noneSuperseded by: content-itd-022-interactive-composition (render-time composition only)
content-itd-022-interactive-compositionStatus: AcceptedDate: 2026-07-06Owner: andymontgomery-byte

Interactive Composition

Audience constraint

Render: Return the exact blank thing a student can use without exposing answers, attempts, or QTI internals.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Extend QTI to carry video cue timings, article segment order, narration, or instructional-delivery compositionKeeps 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 materialPlaces 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 timeAvoids 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Current composition edits replace active sidecar rows, retire the prior rows, and touch the parent item's updated_at; they do not create a new qti.artifact_version. The write validates ordered segments or strictly increasing cue timestamps plus resolvable media/question/component refs, while same-item optional-version ownership remains a documented gap.
  • Current PUT also accepts per-entry parent version_id values even though 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 is a pending coherence fix; consumers cannot treat the reported version as proof that every active row shares it.
  • QTI remains the authority for answer/scoring semantics, so composition cannot duplicate interaction or key data.

Fitness checks

  • Interactive article/video reads expose typed ordered bindings, invalid cue/order/reference fixtures fail, and the log links this decision to the superseded portion of ITD-021.

Downstream rules

  • Dictionary objects: interactive_composition.
  • Owned fields: interactive_composition.content_id, interactive_composition.version_id, interactive_composition.composition_type, interactive_composition.media_asset_ref, interactive_composition.duration_seconds, interactive_composition.segments, interactive_composition.cues, interactive_composition.validation_contract, interactive_composition.updated_at.
  • Alias-map rows: interactive_composition, interactive_composition.content_id, interactive_composition.version_id, interactive_composition.composition_type, interactive_composition.media_asset_ref, interactive_composition.duration_seconds, interactive_composition.segments, interactive_composition.cues, interactive_composition.validation_contract, interactive_composition.updated_at.

Trace

render

Tracked decision content-itd-022-interactive-composition; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: render.

Supersedes: content-itd-021-tb-markup-body-contract (render-time composition only)Superseded by: content-itd-005-curriculum-links-are-relationships (content_curriculum_link gating clause only)
content-itd-023-trust-based-release-eligibilityStatus: AcceptedDate: 2026-07-27Owner: platform3-operator

Trust Based Release Eligibility

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Require a second producer attestation in ContentKeeps 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 allowlistCan 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 verdictMakes 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 trustAvoids a dedicated Content release-eligibility read.Produces contradictory answers and makes every consumer duplicate Content-owned semantics.
Copy release_eligible into CurriculumAvoids the launch-time Content read.Goes stale when lifecycle or trust changes and prevents immediate fail-closed revocation.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • A published, unretired exact version is release eligible unless item trust is failed (owner ruling #2534: approval never gates visibility; positive trust and per-version receipts are recorded provenance). Draft, retirement, or failed item trust fails closed with content.version_not_serveable.
  • A later immutable version records its own QC receipt when it passes publication; receipts are provenance and never gate release.
  • The required producer_surface query remains as a validated compatibility input, but it no longer selects a requirement, quality-bar, evidence, or owner-acceptance policy lane. The response exposes current trust_status rather than producer evidence.
  • A version owned by another item fails closed with content.release_wrong_version; consumers cannot substitute an unversioned or latest pointer.
  • Historical release-evidence, requirement, and eligibility-view rows may remain physically present while guarded schema cleanup is planned, but ordinary release eligibility neither writes nor reads them. Dropping retained tables or views is a separate migration with its own dependency and retention proof.
  • Curriculum stores the exact Content identity and asks Content again at launch; it never copies release_eligible or recomputes lifecycle and trust.
  • POST /items/{contentId}/publish accepts only content_version_id, requires publish:content plus a service/reviewer/operator role, validates the exact current spec/bank graph or article body, and atomically records the server-computed publication.

Fitness checks

  • Boundary tests prove release_eligible equals serveable for trusted, degraded, unknown, and missing-trust inputs (all serve when published and unretired) plus failed, draft, and retired inputs, with the stable problem code for every fail-closed case.
  • A published exact version serves with no producer evidence, owner-acceptance row, or per-version receipt required, while a same-tenant wrong-item version returns content.release_wrong_version.
  • The public service descriptor exposes authenticated GET release-eligibility plus the narrowly guarded automatic publish command; no producer evidence append route, caller trust field, or append:content_release_evidence scope remains, and the ordinary repository read does not query legacy release tables or views.
  • Contract tests prove caller verdict fields are rejected, demo/author principals are forbidden, incomplete banks and malformed articles remain unknown, and complete exact specs, banks, and articles publish and replay atomically.

Downstream rules

  • Dictionary objects: content_release_eligibility.
  • Owned fields: content_release_eligibility.content_id, content_release_eligibility.content_version_id, content_release_eligibility.content_kind, content_release_eligibility.usage_scope, content_release_eligibility.trust_status, content_release_eligibility.serveable, content_release_eligibility.release_eligible, content_release_eligibility.problem_code.
  • Alias-map rows: content_release_eligibility, content_release_eligibility.content_id, content_release_eligibility.content_version_id, content_release_eligibility.content_kind, content_release_eligibility.usage_scope, content_release_eligibility.trust_status, content_release_eligibility.serveable, content_release_eligibility.release_eligible, content_release_eligibility.problem_code.

Trace

renderdiscoverinteroperate

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.

Supersedes: noneSuperseded by: none
content-axis-001-write-granularityStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Write Granularity

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Bulk-onlyOptimizes 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 immediatelyCovers every authoring and migration pattern from the first release.Doubles contracts, authorization/audit paths, and atomicity semantics before tenants prove arbitrary bulk is needed.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Clients orchestrate multiple commands for mixed ad hoc edits, and each retryable command needs its own idempotency/audit boundary.
  • A new bulk contract requires the documented tenant-demand trigger and cannot bypass QTI base writes or Content validation.

Fitness checks

  • Each shipped write maps to one named workflow with explicit atomicity, auth, idempotency, and readback; no generic endpoint mutates arbitrary tables.

Downstream rules

  • API-axis dictionary coverage: 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.

Trace

interoperate

Tracked decision content-axis-001-write-granularity; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-002-read-shapeStatus: Accepted Pending ImplementationDate: 2026-06-08Owner: platform3-loop

Content Api Axis Read Shape

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
List-onlyProvides 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-rememberAvoids 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • The module maintains several documented read collections and consistent paging/auth/provenance across them.
  • Raw expert QTI browsing remains on the upstream surface; Alpha cannot grow a generic include-everything response.

Fitness checks

  • Current fitness requires a cold consumer to discover an item, fetch exact version detail with its student_view field or use the unversioned student-view route, enumerate references/facets/components/effectiveness, and never parse a raw QTI table; target acceptance also requires explicit resolved_content_version_id on an unversioned launch.

Downstream rules

  • API-axis dictionary coverage: Each public object lists its collection/detail/sub-collection reads; raw QTI internals are isolated in Cuts and routing.
  • Accepted architecture target with incomplete runtime coverage. The named dictionary contract may exist, but implementation and deployed proof remain pending; this decision is not yet active or fully deployed.

Trace

renderdiscover

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.

Supersedes: noneSuperseded by: none
content-axis-003-query-modelStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Query Model

Audience constraint

Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
No query modelKeeps 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 parametersLets 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • New filters require a typed view/sidecar/provenance field and documentation before they enter the public contract.
  • Clients cannot send SQL-like expressions or caller-selected sort parameters and must compose within the supported filters and server-defined order.

Fitness checks

  • Paging yields each matching row once under stable ordering, modifiedSince/source filters reconcile fixtures, and unsupported or untyped filters fail validation.

Downstream rules

  • Dictionary objects: content_curriculum_link, content_catalog_facet_assignment, content_policy.
  • Owned fields: content_item.subject_id, content_item.target_grade_id.
  • API-axis dictionary coverage: 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.

Trace

discover

Tracked decision content-axis-003-query-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover.

Supersedes: noneSuperseded by: none
content-axis-004-concurrency-modelStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Concurrency Model

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Last-write-winsRequires 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 clientsCan 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Item-metadata PATCH writers must fetch an ETag and implement 428 missing-precondition plus 409 stale-version recovery; the server serializes simultaneous same-ETag writers and accepts exactly one.
  • Version creation and child-resource writers use their documented Idempotency-Key behavior; broader If-Match coverage requires separate implementation evidence.

Fitness checks

  • Concurrent item-metadata PATCH fixtures from one ETag produce one success and one typed conflict on both Content surfaces, and the rejected request preserves the winning representation.

Downstream rules

  • Dictionary objects: content_version.
  • Owned fields: content_version.content_hash, content_version.etag.
  • Alias-map rows: content_version.content_hash, content_version.etag.
  • API-axis dictionary coverage: 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.

Trace

interoperate

Tracked decision content-axis-004-concurrency-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-005-idempotency-modelStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Idempotency Model

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
No idempotencyAvoids request-ledger storage and lets each command execute independently.Turns harmless client retries into duplicate content, versions, links, or lifecycle transitions.
Content-local idempotency tableAllows 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Retryable clients must send stable keys scoped to the same tenant/operation/payload; changed payload reuse fails explicitly.
  • 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 atomically commit their effects and completed replay responses. The other five operations commit the domain mutation before completing the ledger, so a crash in between can leave a durable effect and an expired key that returns terminal 503 instead of replaying.

Fitness checks

  • Completed identical requests replay and changed-payload reuse produces the platform conflict Problem. Exactly-once crash-window proof applies to 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 until the other five operations gain atomic finalization.

Downstream rules

  • API-axis dictionary coverage: 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.

Trace

interoperate

Tracked decision content-axis-005-idempotency-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-006-auth-shapeStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Auth Shape

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Role-onlyIs 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 claimsLets 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Token minting and authorization policy must understand the five ordinary Content scopes plus content:*; there is no producer append scope or producer-identity claim contract.
  • Release-eligibility reads require the same tenant-scoped read:content authority as other Content reads. producer_surface remains a validated compatibility query value, not an authorization claim or policy selector.
  • Answer-key and weighted KC-tag routes require their explicit scopes even when a caller has general Content read or author access; archive GET needs read:content and archive POST always returns 410.
  • publish:content is enforced only on the narrow automatic publication route and must be paired with service/reviewer/operator role; no admin:content contract is advertised.

Fitness checks

  • A role/scope matrix proves each allowed and denied route; release eligibility follows read:content, automatic publication requires role plus publish:content, tenant mismatch fails closed, and no Content table or claim becomes school/class authority. The descriptor and HTTP source contain no caller trust field, producer evidence append endpoint, or append:content_release_evidence scope.

Downstream rules

  • Dictionary objects: answer_key.
  • Owned fields: answer_key.required_scope.
  • Alias-map rows: answer_key.required_scope.
  • API-axis dictionary coverage: 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.

Trace

interoperate

Tracked decision content-axis-006-auth-shape; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-007-eventing-modelStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Eventing Model

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
No eventing or modifiedSinceAvoids change-feed semantics and keeps Content reads stateless.Forces full catalog polling, increases load, and cannot bound incremental reconciliation windows.
Webhooks nowProvides low-latency push and avoids consumer polling.Adds subscription, signing, retry, ordering, dead-letter, and delivery-observability machinery before proven customer need.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Consumers accept polling latency and must checkpoint cursor/time boundaries safely.
  • Consumers that need facet, link, reference, trust, or effectiveness deltas must read those subcollections separately until a parent-touch or versioned change feed is implemented.
  • A future webhook proposal must satisfy the three-tenant demand trigger and carry catalog changes only, not learner activity.

Fitness checks

  • Incremental polling returns every changed content_item row without duplicates across a checkpoint fixture, the documented subcollection limitation remains explicit, and no learner event is emitted or stored by Content.

Downstream rules

  • Dictionary objects: content_effectiveness.
  • Owned fields: content_item.created_at, content_item.updated_at, decodable.updated_at, test_spec.updated_at, test_bank.updated_at, content_catalog_facet.updated_at, content_reference.created_at.
  • Read-dependency fields (owned outside Content): content_effectiveness.last_recomputed_at.
  • Alias-map rows: content_item.created_at, content_item.updated_at, decodable.updated_at, test_spec.updated_at, test_bank.updated_at, content_catalog_facet.updated_at.
  • API-axis dictionary coverage: 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.

Trace

interoperate

Tracked decision content-axis-007-eventing-model; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-008-error-envelopeStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Error Envelope

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Raw QTI or database errorsPreserves 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 errorsIs compact and readable without a structured error schema.Cannot carry stable codes, field errors, trace ids, or upstream provenance for automated recovery.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Each new failure mode needs a documented stable code/status mapping and safe public detail.
  • Internal causes and validator provenance remain in server-side trace context unless deliberately mapped to the implemented optional cause/docUrl fields; no distinct provenance member is promised.

Fitness checks

  • All route and adapter failure fixtures validate the implemented Problem schema, expected HTTP status, code, request/trace ids, and applicable fieldErrors without requiring an unimplemented provenance member.

Downstream rules

  • Dictionary objects: content_trust_evidence, content_release_eligibility, content_problem.
  • Owned fields: content_trust_evidence.problem_code, content_problem.type, content_problem.title, content_problem.status, content_problem.code, content_problem.detail, content_problem.requestId, content_problem.traceId, content_problem.cause, content_problem.docUrl, content_problem.fieldErrors.
  • Alias-map rows: content_problem.type, content_problem.title, content_problem.status, content_problem.detail, content_problem.requestId, content_problem.traceId, content_problem.cause, content_problem.docUrl, content_problem.fieldErrors.
  • API-axis dictionary coverage: 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.

Trace

interoperate

Tracked decision content-axis-008-error-envelope; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-009-tenant-routingStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Tenant Routing

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Tenant only in JWTKeeps 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 URLMakes 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Clients must keep URL and token tenant synchronized, and mismatches fail before any Content query.
  • QTI base and every Alpha sidecar/reference must preserve the same tenant scope through joins and writes. Current optional version UUIDs on media, content-reference, curriculum-link, and interactive-composition rows are independently constrained and still need same-artifact/tenant validation.

Fitness checks

  • Route/JWT cross-tenant and mismatch fixtures return denial with zero row leakage, while demo and real tenants use the identical routing implementation. This does not prove the pending same-item/tenant integrity of optional sidecar version references.

Downstream rules

  • Owned fields: content_item.workspace_id.
  • Alias-map rows: content_item.workspace_id.
  • API-axis dictionary coverage: content_item.workspace_id and related constraints require route tenant/JWT alignment.

Trace

interoperate

Tracked decision content-axis-009-tenant-routing; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-010-conformance-evidenceStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis Conformance Evidence

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
No conformance evidenceKeeps 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 runnerLets 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 migrationsCan release a reviewed cohort without adding an API.Makes normal course publication exceptional, slow, and dependent on bespoke database changes.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • QTI questions/forms retain their import-QC path and exact receipt; test specs/banks use automatic exact-version and dependency checks; articles use automatic exact-version hash and student-safe body checks; videos use automatic exact-version primary-byte, duration/cue, accessibility, and exact-QTI-dependency checks.
  • A failed publication changes neither lifecycle nor QC state, while a passing publication atomically records published state, compatible item trust, and one exact-version receipt.
  • Item trust_status=failed revokes every version immediately; trusted/degraded item state without a matching exact receipt never grants release.

Fitness checks

  • Contract tests prove exact-version pinning, incomplete graph/body/media/accessibility/dependency rejection, caller-verdict and forged-media rejection, role-plus-scope authorization, atomic success, successor-version non-inheritance, verified-video publication, and replay without a second fetch.

Downstream rules

  • Dictionary objects: content_trust_evidence.
  • Owned fields: content_item.source_system, external_link.last_validated_at, content_catalog_facet_assignment.provenance, content_trust_evidence.content_id, content_trust_evidence.trust_status, content_trust_evidence.evidence_url, content_trust_evidence.checked_at.
  • Alias-map rows: external_link.last_validated_at, content_trust_evidence, content_trust_evidence.content_id, content_trust_evidence.trust_status, content_trust_evidence.evidence_url, content_trust_evidence.checked_at.
  • API-axis dictionary coverage: 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.

Trace

interoperate

Tracked decision content-axis-010-conformance-evidence; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: interoperate.

Supersedes: noneSuperseded by: none
content-axis-011-privacy-retentionStatus: Accepted Pending ImplementationDate: 2026-06-08Owner: platform3-loop

Content Api Axis Privacy Retention

Audience constraint

Interoperate: Import, export, reference, and reconcile Content without a parallel store or translation-only business rules.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Hard-delete content immediatelyRemoves 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 rowsMakes 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • A coherent retirement command must return a deterministic success response, hide the asset from new discovery/launch, and preserve authorized historical exact-version reads.
  • Until that command is implemented and tested, callers must not rely on PATCH status/retired_at combinations as a retirement workflow.
  • Events/Results runtime deletion proceeds independently; Content rows may contain no direct student ids, tokens, credentials, or progress.

Fitness checks

  • Target acceptance requires one tested retirement write to return success, hide the asset from new discovery/launch, preserve authorized historical exact-version reads, and keep PII/secrets absent from Content views and sidecars.

Downstream rules

  • Dictionary objects: media_asset, external_link.
  • Owned fields: content_item.status, content_item.retired_at, content_kc_tag.created_at, content_kc_tag.updated_at, content_kc_tag.retired_at, content_version.created_by, media_asset.delivery_url, media_asset.metadata, external_link.external_url, content_curriculum_link.active_until, content_catalog_facet.status, content_catalog_facet_assignment.retired_at, content_reference.retired_at.
  • Alias-map rows: content_item.retired_at, content_version.created_by, media_asset.metadata, content_curriculum_link.active_until, content_kc_tag.created_at, content_kc_tag.updated_at, content_kc_tag.retired_at.
  • API-axis dictionary coverage: 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.
  • Accepted architecture target with incomplete runtime coverage. The named dictionary contract may exist, but implementation and deployed proof remain pending; this decision is not yet active or fully deployed.

Trace

interoperate

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.

Supersedes: noneSuperseded by: none
content-axis-012-list-endpointsStatus: AcceptedDate: 2026-06-08Owner: platform3-loop

Content Api Axis List Endpoints

Audience constraint

Discover: Find and compare reusable Content through governed fields, relationships, and catalog sidecars without inference.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Write-and-rememberAvoids 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 tableMaximizes 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.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • The API must keep paging, filtering, auth, and provenance consistent across item, version, media, reference, weighted KC-tag, archive-link, facet, and trust/effectiveness lists. /kc-tags is authoritative alignment readback; kc_refs and archive rows are not.
  • Expert raw-row jobs follow the QTI surface until the approved sub-surface or tenant-demand trigger is met.

Fitness checks

  • A new authorized client can enumerate every public collection and reconcile counts, while no endpoint generically lists arbitrary qti.* tables.

Downstream rules

  • API-axis dictionary coverage: Public collections, object query examples, catalog-facet registry/assignment objects, and Cuts and routing separate catalog lists from internal QTI provenance.

Trace

discover

Tracked decision content-axis-012-list-endpoints; generated from architecture/commitments.json and reconciled to the owning Content data dictionary. Use cases: discover.

Supersedes: noneSuperseded by: none
content-itd-024-governed-test-serveStatus: AcceptedDate: 2026-07-24Owner: platform3-doer

Governed Test Serve

Audience constraint

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.

Context

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.

Decision

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.

Alternatives and tradeoffs

AlternativeBenefitCost / reason rejected
Publish and trust draft banks only to exercise themReuses 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 flagEasy 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 allowlistKeeps gate launch decisions in one runtime.Duplicates identity and Content state authority, and lets a consumer override Content lifecycle/trust without a Content audit.

Why chosen

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.

Consequences and fitness

Accepted consequences

  • Curriculum must try ordinary exact-version release eligibility first for published gates and may enter test-serve only after an authoritative ineligible answer, or directly for a draft gate with a complete exact bank target.
  • Both the bank and selected form must remain practice eligible, unretired, and non-failed trust at final authorization time. Until test-serve is separately decoupled, a matching legacy producer-surface requirement is also mandatory and a latest rejected/revoked producer verdict or blocked/revoked legacy release status remains an additional deny condition; those legacy facts never gate ordinary release eligibility.
  • Every successful test serve records tenant, grant, caller, test identity, producer, gate, exact bank/form ids and versions, request/trace ids, and timestamp in an immutable row.
  • Revoking or expiring the grant stops new authorizations without rewriting prior receipts or changing Content lifecycle/trust.

Fitness checks

  • An enumerated demo identity with People & Orgs reality=test can launch an exact draft bank/form and receives an authorization id while both Content items remain draft and lifecycle/trust remain unchanged.
  • An ordinary student/teacher/reader token, real identity, non-demo tenant, wrong bank membership, failed trust, retired item, revoked grant, or transport/malformed response fails closed and creates no receipt.
  • Database constraints and an append-only trigger reject mutation or deletion of successful authorization receipts.

Downstream rules

  • Dictionary objects: content_test_serve_grant, content_test_serve_authorization, content_test_serve_eligibility.
  • Owned fields: content_test_serve_grant.tenant_id, content_test_serve_grant.test_serve_grant_id, content_test_serve_grant.test_identity_id, content_test_serve_grant.producer_surface, content_test_serve_grant.authorization_ref, content_test_serve_grant.expires_at, content_test_serve_grant.revoked_at, content_test_serve_grant.created_at, content_test_serve_authorization.tenant_id, content_test_serve_authorization.test_serve_authorization_id, content_test_serve_authorization.test_serve_grant_id, content_test_serve_authorization.caller_subject, content_test_serve_authorization.test_identity_id, content_test_serve_authorization.producer_surface, content_test_serve_authorization.gate_component_id, content_test_serve_authorization.bank_content_id, content_test_serve_authorization.bank_content_version_id, content_test_serve_authorization.form_content_id, content_test_serve_authorization.form_content_version_id, content_test_serve_authorization.request_id, content_test_serve_authorization.trace_id, content_test_serve_authorization.authorized_at, content_test_serve_eligibility.producer_surface, content_test_serve_eligibility.test_serve_grant_id, content_test_serve_eligibility.test_serve_eligible, content_test_serve_eligibility.problem_code.
  • Read-dependency fields (owned outside Content): content_test_serve_eligibility.content_id, content_test_serve_eligibility.content_version_id, content_test_serve_eligibility.content_kind, content_test_serve_eligibility.usage_scope, content_test_serve_eligibility.test_identity_id.
  • Alias-map rows: content_test_serve_grant, content_test_serve_grant.tenant_id, content_test_serve_grant.test_serve_grant_id, content_test_serve_grant.test_identity_id, content_test_serve_grant.producer_surface, content_test_serve_grant.authorization_ref, content_test_serve_grant.expires_at, content_test_serve_grant.revoked_at, content_test_serve_grant.created_at, content_test_serve_authorization, content_test_serve_authorization.tenant_id, content_test_serve_authorization.test_serve_authorization_id, content_test_serve_authorization.test_serve_grant_id, content_test_serve_authorization.caller_subject, content_test_serve_authorization.test_identity_id, content_test_serve_authorization.producer_surface, content_test_serve_authorization.gate_component_id, content_test_serve_authorization.bank_content_id, content_test_serve_authorization.bank_content_version_id, content_test_serve_authorization.form_content_id, content_test_serve_authorization.form_content_version_id, content_test_serve_authorization.request_id, content_test_serve_authorization.trace_id, content_test_serve_authorization.authorized_at, content_test_serve_eligibility, content_test_serve_eligibility.content_id, content_test_serve_eligibility.content_version_id, content_test_serve_eligibility.content_kind, content_test_serve_eligibility.usage_scope, content_test_serve_eligibility.producer_surface, content_test_serve_eligibility.test_identity_id, content_test_serve_eligibility.test_serve_grant_id, content_test_serve_eligibility.test_serve_eligible, content_test_serve_eligibility.problem_code.

Trace

renderinteroperate

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.

Supersedes: noneSuperseded by: none

Content API decision axes

AxisDecisionDictionary coverage
Write granularityPer-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 shapeList + 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 modelTyped 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 modelAtomic 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 modelIdempotency-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 shapePlatform 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 modelPoll 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 envelopeTyped 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 routingWorkspace/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 evidenceExpose 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 / retentionAccepted 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 endpointsOne 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 generation rules