TimeBack Platform / QTI / 1EdTech Surface / Architecture

Faithful QTI 3.0 persistence architecture

This document is the platform3 architecture deliverable for the 1EdTech QTI surface. It turns the prior QTI workspace and the bundled 1EdTech QTI 3.0 specification into platform3 truth, with stable ITD anchors for every gap fill the database, API, runtime, validation layer, and hosted documentation introduce.

Architecture Summary

The 1EdTech surface preserves QTI names and semantics. XML from the bundled 1EdTech QTI 3.0 sources is the interchange authority. A generated object graph is the canonical in-memory hub. PostgreSQL in the shared Supabase platform3 project stores lossless versioned persistence, query projections, delivery snapshots, attempts, and conformance evidence. Public JSON is a projection, not the source of truth.

Copy Exact First

QTI root elements, XSD types, VDEX terms, response-processing templates, manifest values, variable categories, identifiers, XML namespaces, and package resource values remain 1EdTech pass-through data.

Gap Fills Are Labeled

Tenancy, idempotency, versioning, sessions, attempts, API routes, Bearer authentication, hosted docs, conformance evidence, and SQL access paths are platform decisions. They are not represented as QTI content.

Alpha Is Out Of Scope

No Alpha cuts, restrictions, renames, or extensions are part of this deliverable. Any simplified teacher, student, parent, or LLM vocabulary belongs to the later Alpha architecture.

Source Authority

The authoritative 1EdTech source for this deliverable is the offline bundle under external/qti/spec_bundle/, adapted into platform3 only through this artifact. The prior external/qti workspace is reference material; nothing remains authoritative merely because it was authoritative there.

SourceEvidence UsedArchitecture Consequence
QTI 3.0 HTML specifications 12 documents: overview, guide, ASI information model, ASI XML binding, implementation guide, metadata, results reporting, usage data, migration, terms, conformance, shared vocabulary and CSS. The surface stays faithful to QTI 3.0 names, roots, conformance scope, package concepts, and implementation guidance.
XSD and validation bundle 27 XSD files, 683 global elements, 842 complex types, 181 simple types, 28,292 embedded Schematron assertions, and local schemaLocation rewrites for offline validation. Object model generation, XML validation, root classification, schema-file storage, and Schematron-derived business checks are generated from the bundle.
VDEX and templates 2 VDEX vocabularies with 20 terms and 6 official response-processing templates. Vocabulary values remain pass-through; template execution dispatches by bundled template names rather than platform constants.
Example corpus 327 XML examples, with 311 in-profile files passing full XSD validation and TypeScript XML/object/relational/JSON round trips in the prior workspace. Conformance evidence is a first-class persistence gap fill because platform3 must repeatedly prove QTI fidelity before Alpha work begins.
Brainlift and loop decisions Student and parent readability, deep data dictionary, shared Supabase platform3 database, Vercel-hosted documentation, and surface ordering. Architecture ITDs must be complete enough for later data dictionary, customer website, implementation, and QC deliverables to be generated without hidden taste decisions.

ITD Index

Every ITD has a stable anchor. Later data dictionary and customer website deliverables should link to these anchors rather than to the old external ADR files.

  1. ITD-001: Offline 1EdTech Source Bundle
  2. ITD-002: Generated Object Model Hub
  3. ITD-003: Shared Supabase PostgreSQL
  4. ITD-004: XML Authority And Canonical Hashes
  5. ITD-005: Lossless Relational Projection
  6. ITD-006: JSON Projection Boundaries
  7. ITD-007: Provenance Labels
  8. ITD-008: Tenant Boundary
  9. ITD-009: Package Resource And File Ingest
  10. ITD-010: Idempotency And Hashes
  11. ITD-011: Artifact Versioning
  12. ITD-012: Artifact Kind Allowed Values
  13. ITD-013: Variable Declaration Projection
  14. ITD-014: Processing Rule Projection
  15. ITD-015: Delivery Session Snapshots
  16. ITD-016: Attempt State And Processing Trace
  17. ITD-017: Conformance Evidence
  18. ITD-018: API Boundary
  19. ITD-019: Security Boundary
  20. ITD-020: Validation And Rejection Policy
  21. ITD-021: Runtime Execution Profile
  22. ITD-022: Operational DDL Discipline
  23. ITD-023: Hosted Documentation Identity
  24. ITD-024: Candidate And Learner Data Privacy

Important Technical Decisions

Source1EdTech pass-through authority

ITD-001: Offline 1EdTech Source Bundle

Decision. Treat the mirrored QTI 3.0 bundle as immutable spec input for the 1EdTech surface. Use the local HTML specs, XSDs, VDEX vocabularies, response-processing templates, and example corpus as the only QTI normative source available to this loop.

Trace. spec_bundle/MANIFEST.md, spec_bundle/v3p0/spec/, spec_bundle/v3p0/xsd/, spec_bundle/v3p0/rptemplates/, spec_bundle/v3p0/vocab/, spec_bundle/examples/.

Alternatives and tradeoffs.

  • Fetch live 1EdTech pages during generation: rejected because the loop must be repeatable and network availability should not change architecture truth.
  • Use only the prior generated TypeScript model: rejected because generated code is evidence, not the upstream spec.
  • Let implementation tests define the profile: rejected because tests can only prove behavior against an explicit source bundle.
Canonical hubAdapted from ADR 0001

ITD-002: Generated Object Model Hub

Decision. Generate the QTI object model from bundled XSDs and make the generated object graph the only canonical in-memory shape between XML, relational rows, JSON projections, processing, and XML export.

Trace. src/generated/qtiModel.generated.ts, generated/spec/qti-object-graph.schema.json, generated/spec/xsd-index.json, docs/adr/0001-generated-object-model.md.

Alternatives and tradeoffs.

  • XML DOM only: simpler at ingest, but it gives persistence and JSON no typed contract.
  • Hand-written domain classes: easier to read initially, but impossible to prove complete across hundreds of QTI elements and types.
  • Direct XML-to-SQL mapping: faster for a few tables, but rehydration, JSON generation, and processing would drift from each other.
PersistencePlatform3 requirement

ITD-003: Shared Supabase PostgreSQL

Decision. Store QTI persistence in PostgreSQL in the shared Supabase project named platform3. QTI and later Alpha read and write the same database; Alpha may expose different language later, but it must not fork persistence truth.

Trace. Brainlift persistence rule, migrations/001_qti_core.sql, supabase/migrations/, docs/adr/0002-postgresql-primary.md.

Alternatives and tradeoffs.

  • Separate database per API surface: isolates experiments, but creates two truths and makes rollback from Alpha to 1EdTech harder.
  • Document store only: stores object graphs easily, but loses SQL comments, constraints, indexes, and the deep data dictionary spine.
  • SQLite-first portability: useful locally, but it does not match Supabase, catalog comments, JSONB indexes, or forward migration expectations.
InterchangeRound-trip rule

ITD-004: XML Authority And Canonical Hashes

Decision. XML remains the interchange authority. Persist original XML and canonical XML, validate before object creation, revalidate serialized XML, and use canonical XML hashes for equivalence, idempotency, ETags, and export stability.

Trace. docs/conversion-contract.md, src/xml/qtiXmlConverter.ts, generated/conformance-full/conformance-report.md.

Alternatives and tradeoffs.

  • Byte-for-byte source preservation as equality: too strict because namespace and attribute order can differ without changing XML meaning.
  • Object graph equality only: useful internally, but not enough for 1EdTech interchange and package export.
  • JSON equality: rejected because JSON is a projection and may be intentionally lossy for delivery.
Gap fillRelational bridge

ITD-005: Lossless Relational Projection

Decision. Use qti.component as the ordered, namespace-aware relational projection of every generated object node. Promoted query tables index high-value concepts but cannot be the only reconstruction source.

Trace. qti.component, src/relational/objectGraphRows.ts, src/repository/postgresQtiRepository.ts.

Alternatives and tradeoffs.

  • One typed table per XSD type: strong typing, but hundreds of tables and painful migrations for little product value.
  • Only store object graph JSONB: compact, but harder to query variables, processing rules, identifiers, and conformance coverage.
  • Only promoted tables: fast for known queries, but cannot guarantee XML/object round trips.
Gap fillAdapted from ADR 0003

ITD-006: JSON Projection Boundaries

Decision. Store and serve three JSON shapes with explicit boundaries: internal object_graph, lossless public authoring_json, and declared-lossiness delivery_json. JSON does not become the QTI source of truth.

Trace. docs/conversion-contract.md, src/projections/qtiJsonProjectors.ts, OpenAPI schemas QtiDeliveryJsonEnvelope and QtiAuthoringJsonEnvelope.

Alternatives and tradeoffs.

  • One JSON shape for all users: easier API docs, but delivery and authoring need different lossiness guarantees.
  • JSON as primary persisted content: more convenient for web apps, but unsafe for QTI export and XSD conformance.
  • No public JSON: faithful but impractical for delivery runtimes and editors.
Gap fillAdapted from ADR 0005

ITD-007: Provenance Labels

Decision. Every table, field, constraint, and allowed value in the later data dictionary must be labeled either 1EdTech pass-through or Gap fill. Pass-through is allowed only for values copied from QTI XML, IMS manifests, XSDs, VDEX, Schematron, templates, examples, or generated traceability to those sources.

Trace. docs/adr/0005-data-dictionary-provenance.md, migrations/001_qti_core.sql comments, this page's field trace.

Alternatives and tradeoffs.

  • Infer provenance from SQL comments: comments explain meaning but do not prove source class.
  • Hard-code provenance in a renderer only: produces a nice page, but the architecture would not be generative.
  • Label all SQL as gap fill: safe but hides pass-through values inside those rows.
Gap fillAdapted from ADR 0006

ITD-008: Tenant Boundary

Decision. Add qti.tenant and require tenant-scoped reads, writes, exports, sessions, attempts, authorization checks, and learner-data deletion scope. Tenant identifiers are platform isolation metadata, not QTI content. Candidate identifiers inside a tenant follow the privacy rule in ITD-024.

Trace. qti.tenant, tenant foreign keys, OpenAPI /tenants/{tenantId}/... paths, repository tenant queries, delivery_session_candidate_idx.

Alternatives and tradeoffs.

  • Put tenant metadata in QTI metadata: corrupts the QTI content model and misses sessions and attempts.
  • One database per tenant: operationally clean but too heavy for the first shared platform surface.
  • API-only authorization with no tenant columns: fragile because repository bugs and operational queries could cross boundaries.
Gap fillAdapted from ADR 0007

ITD-009: Package Resource And File Ingest

Decision. Persist uploaded IMS/QTI packages as qti.content_package, manifest resources as qti.package_resource, and original package files as qti.package_file. Preserve manifest identifiers, resource types, hrefs, dependencies, and file bytes while validating package closure.

Trace. imsqtiv3p0_imscpv1p2_v1p0.xsd, qti.content_package, qti.package_resource, qti.package_file.

Alternatives and tradeoffs.

  • Persist only extracted QTI XML: loses manifest identity, resource dependencies, files, and export evidence.
  • Store ZIP bytes only: preserves source but prevents useful data dictionary, diagnostics, and resource APIs.
  • Rewrite resources into friendlier names: rejected for 1EdTech because manifest values must remain pass-through.
Gap fillRetry safety

ITD-010: Idempotency And Hashes

Decision. Use tenant-scoped Idempotency-Key, package hashes, file hashes, and canonical XML hashes to make ingest retries, duplicate uploads, and repeated authoring saves deterministic.

Trace. content_package_idempotency_key_unique, content_package_hash_unique, artifact_version_hash_unique, OpenAPI Idempotency-Key parameter.

Alternatives and tradeoffs.

  • No idempotency: simpler API, but retries can duplicate package and version rows.
  • Global idempotency keys: easy to enforce but can leak cross-tenant operational facts.
  • Database serial numbers only: good for identity but cannot prove duplicate content equivalence.
Gap fillAdapted from ADR 0008

ITD-011: Artifact Versioning

Decision. Represent each logical QTI document as qti.artifact and each saved edition as immutable qti.artifact_version. Store source XML, canonical XML, object graph, JSON projections, root metadata, schema file, spec trace, supersession, actor, and creation time on the version.

Trace. qti.artifact, qti.artifact_version, persistObjectGraph, exportXml, OpenAPI authoring and export endpoints.

Alternatives and tradeoffs.

  • Update artifacts in place: simplest storage, but edits would change historical delivery and export meaning.
  • Treat every upload as unrelated: avoids version complexity, but breaks authoring history and idempotency.
  • Store only rows and regenerate XML later: loses the 1EdTech interchange authority and export confidence.
Gap fillAllowed values

ITD-012: Artifact Kind Allowed Values

Decision. Classify persisted artifacts into a small allowed set derived from QTI root elements and package resources: item, test, section, stimulus, outcome-declaration, response-processing, result, usage-data, metadata, and manifest-resource.

Trace. artifact_kind_ck, artifactKindForRootElement, generated root element summary.

Alternatives and tradeoffs.

  • Store raw root element only: fully faithful but awkward for common repository queries.
  • Use broad values like content and result: too vague for data dictionary validation.
  • Create a kind for every XSD root variation: precise but unnecessary because schema file and root element are already stored.
Gap fillQuery projection

ITD-013: Variable Declaration Projection

Decision. Promote QTI response, outcome, template, and context variables into qti.variable_declaration for runtime lookup and data dictionary clarity. The promoted row remains a projection of the generated object graph.

Trace. qti.variable_declaration, variable_kind_ck, XSD variable declaration categories, runtime variable model.

Alternatives and tradeoffs.

  • Look up variables by traversing JSONB every time: avoids duplication, but makes runtime queries slower and harder to audit.
  • Separate table per variable kind: cleaner typing, but more joins and migration surface with no fidelity gain.
  • Store only identifiers: loses cardinality, base type, defaults, correct responses, mappings, and traceability.
Gap fillQuery projection

ITD-014: Processing Rule Projection

Decision. Promote QTI processing and expression trees into qti.processing_rule with scope, rule name, sequence, operands, parent rule, and source trace. Processing rows support runtime execution and audits but do not replace object graph reconstruction.

Trace. qti.processing_rule, processing_rule_scope_ck, src/runtime/qtiProcessingRuntime.ts, processing coverage evidence.

Alternatives and tradeoffs.

  • Execute directly from XML each time: faithful but inefficient and hard to snapshot.
  • Compile to opaque code: fast but poor for compliance and AI-maintained review.
  • Flatten only top-level rules: misses nested expressions and makes trace explanations incomplete.
Gap fillAdapted from ADR 0004

ITD-015: Delivery Session Snapshots

Decision. Store qti.delivery_session rows with tenant, opaque candidate reference, root artifact version, lifecycle status, delivery JSON snapshot, and session state. A session pins immutable content and learner-facing JSON at start. The candidate reference and any session state that can identify or characterize a learner are governed by ITD-024.

Trace. qti.delivery_session, candidate_ref, delivery_session_status_ck, OpenAPI startDeliverySession, docs/runtime-semantics.md, ITD-024.

Alternatives and tradeoffs.

  • Always render latest content: simple, but historical attempts can change after edits.
  • Store only candidate responses: insufficient to reconstruct exactly what the learner saw.
  • Duplicate all XML per session: very stable, but larger than needed because immutable artifact versions already store XML.
Gap fillAdapted from ADR 0009

ITD-016: Attempt State And Processing Trace

Decision. Store qti.attempt rows for learner response sets inside sessions. Persist response, template, outcome, lifecycle timestamps, and deterministic processing trace. Trace is platform audit data, not QTI content, and learner-derived values are protected and redacted according to ITD-024.

Trace. qti.attempt, response_state, template_state, outcome_state, processing_trace, attempt_status_ck, OpenAPI submitAttempt, src/runtime/qtiProcessingRuntime.ts, ITD-024.

Alternatives and tradeoffs.

  • Store only final score: compact but not explainable to students, teachers, support, or reviewers.
  • Recompute on every read: unstable when runtime code or content versions change.
  • Treat trace as pass-through: inaccurate because QTI defines processing semantics, not this stored trace shape.
Gap fillAdapted from ADR 0010

ITD-017: Conformance Evidence

Decision. Persist release evidence in qti.conformance_run and qti.conformance_assertion. Store profile, bundle hash, runner version, status, summary, assertion keys, artifact/spec refs, and diagnostics.

Trace. generated/conformance-full/conformance-report.md, generated/conformance-ts/conformance-report.md, generated/conformance/processing-coverage.md, conformance tables.

Alternatives and tradeoffs.

  • Loose JSON files only: easy for CI, but invisible to the platform data dictionary and later APIs.
  • Treat assertions as QTI content: confuses implementation evidence with normative assessment content.
  • Skip persistence until production: weakens the loop before Alpha depends on 1EdTech truth.
Gap fillAdapted from ADR 0011

ITD-018: API Boundary

Decision. Expose the 1EdTech persistence surface through tenant-scoped OpenAPI paths for package ingest, delivery JSON, authoring JSON saves, XML export, delivery sessions, attempts, and conformance runs. API names preserve QTI vocabulary and platform gap-fill vocabulary only.

Trace. contracts/qti-boundary.openapi.yaml, src/http/qtiHttpServer.ts, OpenAPI operation IDs.

Alternatives and tradeoffs.

  • Ship only SQL and TypeScript contracts: insufficient for external implementers and app-builder agents.
  • Use Alpha-style friendly endpoint names: rejected because this surface must stay faithful to 1EdTech.
  • Expose direct table CRUD: simple to generate, but leaks persistence internals and cannot enforce conversion contracts.
Gap fillSecurity rule

ITD-019: Security Boundary

Decision. Require Bearer JWT authentication on API operations, verify issuer, expiry, signature, subject, and tenant claim, and reject cross-tenant access. The token principal is platform metadata; it is never written into QTI XML, candidate_ref, session state, attempt state, processing trace, or logs unless the subject is already the tenant-scoped pseudonymous candidate UUID required by ITD-024.

Trace. OpenAPI bearerAuth, requireBearerPrincipal, assertTenantAuthorized, QTI_AUTH_SECRET, QTI_AUTH_ISSUER.

Alternatives and tradeoffs.

  • Leave auth unspecified: easier for prototypes but unsafe for multi-tenant persistence.
  • API key only: simpler, but weaker principal and tenant binding.
  • Encode tenant ownership in QTI metadata: rejected because auth is platform control data, not assessment content.
Gap fillValidation rule

ITD-020: Validation And Rejection Policy

Decision. Reject malformed XML, bundled-XSD failures, Schematron-derived business rule failures, missing package files, escaping package paths, broken dependency closure, stale authoring preconditions, invalid response shapes, and unauthenticated or unauthorized API calls. Runtime support limits are implementation-profile diagnostics, not changes to QTI validity.

Trace. BadQtiInput response, validate_offline.py, generated Schematron validator, package ingest repository, OpenAPI 400/401/403/409/428 semantics.

Alternatives and tradeoffs.

  • Best-effort ingest with warnings: user friendly for broken content but undermines conformance truth.
  • Reject any unsupported runtime feature at XML ingest: too restrictive because preservation and execution are separate concerns.
  • Trust package ZIP paths: unsafe because paths can escape the package root or omit referenced assets.
Gap fillRuntime profile

ITD-021: Runtime Execution Profile

Decision. Execute the bundled response-processing templates, all currently covered XSD-reachable inline processing elements, deterministic response/template/outcome rules, area mapping shapes, outcome lookup rules, and registered custom operators. Unregistered custom operators fail closed with trace. Template constraints retry up to 100 attempts before failing closed.

Trace. docs/runtime-semantics.md, generated/conformance/processing-coverage.md, tests/processingRuntimeCoverage.test.ts.

Alternatives and tradeoffs.

  • Execute only official templates: simpler, but many QTI items use inline processing.
  • Execute arbitrary vendor custom code: powerful but unsafe and not reviewable.
  • Silently ignore unsupported operators: rejected because students and teachers would get unexplained scores.
Gap fillOperations

ITD-022: Operational DDL Discipline

Decision. Use forward-only, idempotent PostgreSQL migrations with catalog comments on every table, field, constraint, and index. Name indexes by the query they serve so the data dictionary can explain operational intent.

Trace. migrations/001_qti_core.sql, migrations/002_qti_profile_default.sql, supabase/migrations/.

Alternatives and tradeoffs.

  • Generate DDL without comments: faster, but fails the deep data dictionary requirement.
  • Allow destructive migration rewrites: convenient during development, but unsafe for the shared platform database.
  • Skip index rationale: leaves performance behavior invisible to reviewers and future agents.
Gap fillHosted deliverable

ITD-023: Hosted Documentation Identity

Decision. Each documentation deliverable is a static HTML website deployed to Vercel under project platform3. The canonical URL is the unique production deployment URL returned by Vercel for this deliverable, not the shared alias.

Trace. Loop hosting rule, OUT_DIR/site/, Vercel deployment metadata in summary.json.

Alternatives and tradeoffs.

  • Markdown artifact only: easy to review locally, but not a hosted source of truth.
  • One shared docs alias for all deliverables: convenient, but each approved deliverable needs a stable permanent URL.
  • Auth-protected docs: rejected because the reviewer and customer audiences must fetch the page without a bypass secret.
Gap fillStudent/candidate privacy

ITD-024: Candidate And Learner Data Privacy

Decision. Treat delivery-session and attempt data as learner data. candidate_ref must be a tenant-scoped opaque pseudonymous UUID string; names, emails, phone numbers, SIS IDs, raw JWT subjects, access tokens, and parent/student contact details are forbidden in candidate_ref. response_state, template_state, outcome_state, and processing_trace may contain learner responses or learner-derived scoring state, so they require tenant authorization, service-role-only bulk access, redaction from logs, and candidate-scoped deletion. Platform-generated QTI XML and package metadata must not include direct learner PII, raw PNP records, auth tokens, or session-specific runtime state; when QTI_CONTEXT candidateIdentifier is needed at runtime, it receives the same pseudonymous candidate_ref.

Trace. qti.delivery_session.candidate_ref, qti.attempt.response_state, qti.attempt.template_state, qti.attempt.outcome_state, qti.attempt.processing_trace, QTI implementation guide context-declaration guidance for candidateIdentifier, OpenAPI startDeliverySession and submitAttempt, delivery_session_candidate_idx, learner data privacy trace.

Alternatives and tradeoffs.

  • Store direct student/account fields in QTI tables: convenient for joins, but it leaks learner identity into a standards persistence layer and makes package export unsafe.
  • Push learner identity and PNP records into QTI XML context defaults: interoperable-looking, but it turns reusable assessment content into a learner record and can leak accommodations.
  • Do not persist attempts or traces: minimizes learner data, but the platform could not explain scores, resume sessions, support disputes, or improve student outcomes.
  • Hash candidate references irreversibly: stronger isolation, but tenants could not reliably resume sessions or honor deletion requests without an external reverse index.

Gap-Fill Traceability

This section is the architectural contract for the next data dictionary deliverable. If a table, field, allowed value, validation rule, API behavior, persistence rule, or security rule appears here, it must link back to the listed ITD.

Tables

TableSource ClassITDReason
qti.tenantGap fillITD-008QTI does not define school, district, publisher, or app tenant isolation.
qti.content_packageGap fill table with pass-through manifest valuesITD-009, ITD-010Platform row for package import, status, idempotency, and audit.
qti.package_resourceGap fill table with pass-through IMS resource valuesITD-009Indexes manifest resources without renaming resource identifiers, types, hrefs, or dependencies.
qti.package_fileGap fill table with pass-through file bytesITD-009Preserves original manifest, XML, media, styles, and asset bytes for export and audit.
qti.artifactGap fillITD-011, ITD-012Logical identity across immutable editions is not defined by QTI.
qti.artifact_versionGap fill with pass-through XML/root/schema valuesITD-004, ITD-006, ITD-011Immutable edition binds XML, generated object graph, JSON projections, hashes, and trace.
qti.componentGap fill table with pass-through generated QTI node valuesITD-005Relational bridge for ordered object graph rehydration and queries.
qti.variable_declarationGap fill table with pass-through QTI variable valuesITD-013Promoted runtime lookup of response, outcome, template, and context declarations.
qti.processing_ruleGap fill table with pass-through QTI processing element namesITD-014, ITD-021Promoted processing and expression tree for runtime execution and audits.
qti.delivery_sessionGap fill with learner-data controlsITD-015, ITD-024QTI defines content and processing, not platform delivery session storage or learner privacy policy.
qti.attemptGap fill with learner-data controlsITD-016, ITD-024Stores learner responses, variable state, and processing trace for stable reporting under privacy constraints.
qti.conformance_runGap fillITD-017Release-gate evidence table, not QTI assessment content.
qti.conformance_assertionGap fill with pass-through example/spec referencesITD-017Per-example and per-feature release evidence.

Fields

TableFieldsITDProvenance Rule
qti.tenanttenant_id, tenant_key, display_name, created_atITD-008All fields are platform isolation metadata.
qti.content_packagepackage_id, tenant_id, source_uri, idempotency_key, package_hash, manifest_identifier, qti_profile, import_status, metadata, imported_atITD-009, ITD-010manifest_identifier is pass-through when present. IDs, tenant, URI, hashes, profile, status, metadata envelope, and timestamps are gap fills.
qti.package_resourceresource_id, package_id, resource_identifier, resource_type, href, dependencies, metadataITD-009Manifest identifier, type, href, dependencies, and manifest-derived metadata are pass-through values inside a gap-fill row. Row IDs and foreign keys are gap fills.
qti.package_filepackage_file_id, package_id, resource_id, package_path, media_type, byte_length, content_hash, content_bytes, metadata, created_atITD-009Package path and content bytes preserve package source. IDs, hashes, media detection, metadata envelope, and timestamps are gap fills.
qti.artifactartifact_id, tenant_id, package_id, resource_id, artifact_kind, qti_identifier, title, language, latest_version_id, created_atITD-011, ITD-012qti_identifier, title, and language are pass-through when copied from QTI XML or package defaults. IDs, kind, latest pointer, and timestamps are gap fills.
qti.artifact_versionartifact_version_id, artifact_id, version_number, source_xml, canonical_xml, xml_hash, root_element, root_type, schema_file, object_graph, delivery_json, authoring_json, spec_trace, supersedes_version_id, created_at, created_byITD-004, ITD-006, ITD-011XML, root, schema, and trace values are pass-through or generated from 1EdTech sources. Versioning, hashes, JSON projections, supersession, actor, and timestamps are gap fills.
qti.componentcomponent_id, artifact_version_id, parent_component_id, ordinal, element_name, qualified_name, namespace_uri, type_name, qti_identifier, component_path, attributes, text_value, tail_value, source_traceITD-005Element names, namespaces, types, QTI identifiers, attributes, text, tail, and source trace are pass-through/generated QTI values. Row identity, parent linkage, ordinal, and component path are gap fills.
qti.variable_declarationvariable_declaration_id, artifact_version_id, component_id, variable_kind, identifier, cardinality, base_type, default_value, correct_response, mapping, source_traceITD-013Variable kind, identifier, cardinality, base type, default, correct response, mapping, and trace are QTI pass-through/generated values. Row IDs and links are gap fills.
qti.processing_ruleprocessing_rule_id, artifact_version_id, component_id, parent_processing_rule_id, rule_scope, rule_name, sequence_number, operands, source_traceITD-014, ITD-021Rule names, operands, and source trace are generated QTI values. Row IDs, parent links, scope, and sequence are gap fills.
qti.delivery_sessiondelivery_session_id, tenant_id, candidate_ref, root_artifact_version_id, status, delivery_json_snapshot, session_state, created_at, updated_atITD-015, ITD-024All fields are platform runtime persistence gap fills, except the snapshot contains generated delivery projection values. candidate_ref is a pseudonymous learner reference, not direct PII.
qti.attemptattempt_id, delivery_session_id, artifact_version_id, attempt_number, status, response_state, template_state, outcome_state, processing_trace, started_at, suspended_at, submitted_atITD-016, ITD-024Response, template, and outcome state preserve QTI variable values and learner-derived scoring state. Attempt row identity, lifecycle, trace shape, timestamps, redaction, retention, and deletion are gap fills.
qti.conformance_runconformance_run_id, profile, bundle_hash, runner_version, started_at, finished_at, status, summaryITD-017Profile and bundle references tie to source evidence; the evidence row, status, runner version, timestamps, and summary are gap fills.
qti.conformance_assertionconformance_assertion_id, conformance_run_id, assertion_key, artifact_ref, spec_ref, status, detailsITD-017Artifact and spec references are pass-through references to examples or source specs. Assertion row identity, generated status, and diagnostics are gap fills.

Learner Data Privacy

Data Or BehaviorArchitecture RuleITD Trace
qti.delivery_session.candidate_refMust be an opaque tenant-scoped pseudonymous UUID string. Direct student or parent names, emails, phone numbers, SIS IDs, raw JWT subjects, access tokens, and contact details are rejected.ITD-024, ITD-008, ITD-019
qti.attempt.response_stateStores candidate response variable values needed for scoring and review. Legitimate free-text answers are allowed even if a learner types identifying text, but the values are learner data and are never copied to application logs or processing trace summaries unnecessarily.ITD-024, ITD-016
qti.attempt.template_state and qti.attempt.outcome_stateStores item template values and outcome variables required to reproduce scoring and feedback. These values inherit learner-data protection when bound to a delivery session.ITD-024, ITD-016, ITD-021
qti.attempt.processing_traceMay include rule names, variable identifiers, before/after values, diagnostics, and unsupported-operator evidence. It must exclude JWTs, request headers, access tokens, IP addresses, user agents, raw package bytes, and direct learner identity fields.ITD-024, ITD-016, ITD-021
Platform-generated QTI XML and QTI_CONTEXT valuesMust not include direct learner PII, raw PNP records, auth tokens, or session-specific runtime state. If a delivery-time candidateIdentifier is needed to resolve PNP or context, it receives the same pseudonymous candidate_ref.ITD-024, ITD-004, ITD-020
Retention and deletionContent packages and artifact versions are content records. Delivery sessions and attempts are learner-runtime records. The 1EdTech surface must support tenant-scoped deletion by candidate_ref, deleting the candidate's delivery sessions and cascading attempts while leaving reusable content artifacts intact. No age-based automatic deletion is assumed without tenant policy.ITD-024, ITD-008, ITD-022

Allowed Values

Constraint Or EnumValuesITDClass
content_package_import_status_ckimporting, imported, rejected, supersededITD-009Gap-fill package lifecycle.
artifact_kind_ckitem, test, section, stimulus, outcome-declaration, response-processing, result, usage-data, metadata, manifest-resourceITD-012Gap-fill classification derived from QTI roots and package resources.
variable_kind_ckresponse, outcome, template, contextITD-013QTI variable categories in a gap-fill projection table.
processing_rule_scope_ckresponse, outcome, template, expressionITD-014Gap-fill row scopes for generated QTI processing trees.
delivery_session_status_ckcreated, active, suspended, submitted, review, closed, voidedITD-015Gap-fill session lifecycle.
attempt_status_ckactive, suspended, submitted, reviewed, voidedITD-016Gap-fill attempt lifecycle.
conformance_run_status_ckrunning, passed, failed, errorITD-017Gap-fill evidence lifecycle.
conformance_assertion_status_ckpassed, failed, skipped, errorITD-017Gap-fill assertion lifecycle.
OpenAPI lossinessdeclared for delivery JSON, none for authoring JSONITD-006Gap-fill JSON projection contract.
QTI package resource typesimsqti_test_xmlv3p0, imsqti_section_xmlv3p0, imsqti_item_xmlv3p0, imsqti_resprocessing_xmlv3p0, imsqti_outcomes_xmlv3p0, imsqti_stimulus_xmlv3p0, imsqti_fragment_xmlv3p0, imsqti_rptemplate_xmlv3p0, associatedcontent/learning-application-resource, webcontent, imsbasiclti_xmlv1p3, controlfile, resourcemetadata/xml, resourceextmetadata/xml, qtiusagedata/xml, pls, css2, css3, extensionITD-0091EdTech pass-through from the IMS/QTI content-package XSD.

API Behavior Traceability

BehaviorContractITD Trace
Package ingestPOST /tenants/{tenantId}/qti/packages accepts ZIP/package bytes, requires Idempotency-Key, defaults profile to qti-3.0, validates package/XML, and returns accepted artifacts or a QTI input problem.ITD-009, ITD-010, ITD-018, ITD-020
Delivery JSON readGET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/delivery-json returns declared-lossiness delivery JSON with an ETag.ITD-006, ITD-018
Authoring JSON savePUT /tenants/{tenantId}/qti/artifacts/{artifactId}/authoring-json requires If-Match, requires lossiness: none, and creates a new immutable version.ITD-006, ITD-011, ITD-018, ITD-020
Canonical XML exportGET /tenants/{tenantId}/qti/artifact-versions/{artifactVersionId}/xml returns canonical XML and uses the XML hash as ETag.ITD-004, ITD-011, ITD-018
Delivery session startPOST /tenants/{tenantId}/qti/delivery-sessions pins a root artifact version and snapshots delivery JSON for one pseudonymous candidate reference. The request rejects direct PII in candidateRef.ITD-015, ITD-018, ITD-024
Attempt submitPOST /tenants/{tenantId}/qti/delivery-sessions/{deliverySessionId}/attempts applies responses, executes processing against the session snapshot, and returns response, template, outcome, and privacy-redacted trace state.ITD-016, ITD-021, ITD-018, ITD-024
Conformance runPOST /qti/conformance-runs starts profile evidence generation and records run state.ITD-017, ITD-018
Authentication and tenant authorizationBearer JWT must be signed, trusted, unexpired, contain subject and tenant claims, and match the tenant path for tenant-owned operations.ITD-008, ITD-019
Learner-runtime deletionDELETE /tenants/{tenantId}/qti/candidates/{candidateRef}/runtime-data deletes delivery sessions and cascading attempts for a pseudonymous candidate reference without deleting reusable package or artifact content.ITD-024, ITD-008, ITD-018

Validation, Persistence, And Security Rules

RuleOutcomeITD Trace
Bundled source onlyGeneration and validation use the local QTI 3.0 bundle; live network specs do not change a run.ITD-001
XML before object graphMalformed XML or XSD/Schematron failures reject ingest before persistence.ITD-002, ITD-004, ITD-020
Round-trip preservationXML to object graph to rows to object graph to XML must preserve canonical XML equivalence.ITD-004, ITD-005
Package closureManifest references that escape package root or point to missing files reject the package.ITD-009, ITD-020
Idempotent ingestSame tenant plus same idempotency key or package hash returns stable behavior; conflicting bytes fail rather than duplicate silently.ITD-010
Immutable versioningAuthoring edits append a version. Existing sessions and exports remain pinned to prior versions.ITD-011, ITD-015
Delivery JSON lossinessDelivery JSON may omit only declared authoring-only source trace and mixed-content tail detail; authoring JSON must recover the object graph.ITD-006
Unsupported runtime featureUnsupported execution fails closed or reports implementation-profile diagnostics; the system must preserve XSD-allowed content when possible.ITD-020, ITD-021
Custom operatorsRegistered custom operator handlers may execute. Unregistered custom operators fail closed and write trace evidence.ITD-021
Tenant isolationEvery tenant-owned operation checks path tenant against authenticated tenant claim and repository scope.ITD-008, ITD-019
Candidate reference validationSession creation rejects direct PII and accepts only tenant-scoped pseudonymous UUID candidate references.ITD-024, ITD-020
Learner-data redactionApplication logs, processing trace summaries, and conformance diagnostics must not include auth tokens, request headers, IP addresses, user agents, raw PNP records, or direct learner identity fields.ITD-024, ITD-019, ITD-021
Hosted docs fetchabilityThe canonical documentation URL must be a public Vercel production deployment URL that returns 2xx without auth.ITD-023

Design Intent And Constraints

This section records constraints, non-goals, and residual risks for downstream deliverables. It does not grade this architecture.

ConstraintDownstream Rule
1EdTech fidelityUse source-bundle authority, XSD/Schematron generation, XML authority, root/schema storage, pass-through labels, and no Alpha renames.
Shared durable persistenceUse PostgreSQL/Supabase as the shared platform target. Avoid per-XSD table explosion while making variables, processing, sessions, attempts, privacy state, and conformance queryable.
Student outcome supportStore response, template, outcome, and trace state for scores, feedback, review evidence, and support, while treating those values as learner data under ITD-024.
Data dictionary derivationDocument every table, field group, allowed-value set, validation rule, API behavior, persistence rule, privacy rule, and security rule against an ITD anchor.
AI maintenanceRegenerate artifacts from the source bundle, keep conformance evidence visible, and publish each documentation deliverable at one public Vercel deployment URL.

Residual risk: the architecture imports evidence from the prior workspace for conformance counts and implementation behavior. That evidence must be re-run when the implementation deliverable is built under platform3.

External QTI Use

The prior workspace was not copied wholesale. I adapted its architecture decisions, conversion contract, runtime semantics, OpenAPI boundary, migration comments, generated spec summary, conformance reports, source audit, and package contract. I rebuilt this deliverable as a platform3-specific architecture site because platform3 separates deliverables and requires a hosted architecture URL with stable ITD anchors.

Read From external/qtiUse In This Deliverable
ARCHITECTURE.md, docs/qti-3-relational-json-architecture.mdAdapted into the architecture summary and representation decisions.
docs/adr/0001 through 0011Converted from prior ADR format into platform3 ITDs with stable anchors and fuller gap traceability.
docs/conversion-contract.md, docs/runtime-semantics.mdAdapted into XML, JSON, delivery session, attempt, and runtime ITDs.
contracts/qti-boundary.openapi.yaml, src/http/qtiHttpServer.ts, src/repository/qtiRepository.tsUsed to trace API behavior, auth behavior, repository contract, and allowed statuses.
migrations/001_qti_core.sql, migrations/002_qti_profile_default.sql, supabase/migrations/Used to enumerate every table, field, constraint, allowed value, index rationale, and Supabase alignment point.
spec_bundle/, generated/spec/summary.md, generated/spec/xsd-index.jsonUsed as 1EdTech source evidence for root elements, schemas, templates, vocabularies, and package resource values.
generated/conformance-full/conformance-report.md, generated/conformance-ts/conformance-report.md, generated/conformance/processing-coverage.mdUsed as prior evidence for source-bundle coverage, runtime behavior, and the conformance ITD.
site/index.htmlInspected for prior combined docs structure, then rebuilt as a dedicated architecture deliverable rather than copying the combined site.